1 /* Routines required for instrumenting a program.  */
2 /* Compile this one with gcc.  */
3 /* Copyright (C) 1989-2021 Free Software Foundation, Inc.
4 
5 This file is part of GCC.
6 
7 GCC is free software; you can redistribute it and/or modify it under
8 the terms of the GNU General Public License as published by the Free
9 Software Foundation; either version 3, or (at your option) any later
10 version.
11 
12 GCC is distributed in the hope that it will be useful, but WITHOUT ANY
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15 for more details.
16 
17 Under Section 7 of GPL version 3, you are granted additional
18 permissions described in the GCC Runtime Library Exception, version
19 3.1, as published by the Free Software Foundation.
20 
21 You should have received a copy of the GNU General Public License and
22 a copy of the GCC Runtime Library Exception along with this program;
23 see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
24 <http://www.gnu.org/licenses/>.  */
25 
26 #if !IN_GCOV_TOOL
27 /* Configured via the GCOV_ERROR_FILE environment variable;
28    it will either be stderr, or a file of the user's choosing.
29    Non-static to prevent multiple gcov-aware shared objects from
30    instantiating their own copies. */
31 FILE *__gcov_error_file = NULL;
32 #endif
33 
34 /* A utility function to populate the __gcov_error_file pointer.
35    This should NOT be called outside of the gcov system driver code. */
36 
37 static FILE *
get_gcov_error_file(void)38 get_gcov_error_file (void)
39 {
40 #if IN_GCOV_TOOL
41   return stderr;
42 #else
43   if (!__gcov_error_file)
44     {
45       const char *gcov_error_filename = getenv ("GCOV_ERROR_FILE");
46 
47       if (gcov_error_filename)
48 	__gcov_error_file = fopen (gcov_error_filename, "a");
49       if (!__gcov_error_file)
50 	__gcov_error_file = stderr;
51     }
52   return __gcov_error_file;
53 #endif
54 }
55 
56 /* A utility function for outputting errors.  */
57 
58 static int __attribute__((format(printf, 1, 2)))
gcov_error(const char * fmt,...)59 gcov_error (const char *fmt, ...)
60 {
61   int ret;
62   va_list argp;
63 
64   va_start (argp, fmt);
65   FILE *f = get_gcov_error_file ();
66   ret = vfprintf (f, fmt, argp);
67   va_end (argp);
68 
69   if (getenv ("GCOV_EXIT_AT_ERROR"))
70     {
71       fprintf (f, "profiling:exiting after an error\n");
72       exit (1);
73     }
74 
75   return ret;
76 }
77 
78 #if !IN_GCOV_TOOL
79 static void
gcov_error_exit(void)80 gcov_error_exit (void)
81 {
82   if (__gcov_error_file && __gcov_error_file != stderr)
83     {
84       fclose (__gcov_error_file);
85       __gcov_error_file = NULL;
86     }
87 }
88 #endif
89 
90 /* Make sure path component of the given FILENAME exists, create
91    missing directories. FILENAME must be writable.
92    Returns zero on success, or -1 if an error occurred.  */
93 
94 static int
create_file_directory(char * filename)95 create_file_directory (char *filename)
96 {
97 #if !defined(TARGET_POSIX_IO) && !defined(_WIN32)
98   (void) filename;
99   return -1;
100 #else
101   char *s;
102 
103   s = filename;
104 
105   if (HAS_DRIVE_SPEC(s))
106     s += 2;
107   if (IS_DIR_SEPARATOR(*s))
108     ++s;
109   for (; *s != '\0'; s++)
110     if (IS_DIR_SEPARATOR(*s))
111       {
112         char sep = *s;
113         *s  = '\0';
114 
115         /* Try to make directory if it doesn't already exist.  */
116         if (access (filename, F_OK) == -1
117 #ifdef TARGET_POSIX_IO
118 	    && mkdir (filename, 0777) == -1
119 #else
120 #ifdef mkdir
121 #undef mkdir
122 #endif
123             && mkdir (filename) == -1
124 #endif
125             /* The directory might have been made by another process.  */
126             && errno != EEXIST)
127           {
128             gcov_error ("profiling:%s:Cannot create directory\n", filename);
129             *s = sep;
130             return -1;
131           };
132 
133         *s = sep;
134       };
135   return 0;
136 #endif
137 }
138 
139 /* Replace filename variables in FILENAME.  We currently support expansion:
140 
141    %p - process ID
142    %q{ENV} - value of environment variable ENV
143    */
144 
145 static char *
replace_filename_variables(char * filename)146 replace_filename_variables (char *filename)
147 {
148   char buffer[16];
149   char empty[] = "";
150   for (char *p = filename; *p != '\0'; p++)
151     {
152       unsigned length = strlen (filename);
153       if (*p == '%' && *(p + 1) != '\0')
154 	{
155 	  unsigned start = p - filename;
156 	  p++;
157 	  char *replacement = NULL;
158 	  switch (*p)
159 	    {
160 	    case 'p':
161 	      sprintf (buffer, "%d", getpid ());
162 	      replacement = buffer;
163 	      p++;
164 	      break;
165 	    case 'q':
166 	      if (*(p + 1) == '{')
167 		{
168 		  p += 2;
169 		  char *e = strchr (p, '}');
170 		  if (e)
171 		    {
172 		      *e = '\0';
173 		      replacement = getenv (p);
174 		      if (replacement == NULL)
175 			replacement = empty;
176 		      p = e + 1;
177 		    }
178 		  else
179 		    return filename;
180 		}
181 	      break;
182 	    default:
183 	      return filename;
184 	    }
185 
186 	  /* Concat beginning of the path, replacement and
187 	     ending of the path.  */
188 	  unsigned end = length - (p - filename);
189 	  unsigned repl_length = replacement != NULL ? strlen (replacement) : 0;
190 
191 	  char *buffer = (char *)xmalloc (start + end + repl_length + 1);
192 	  char *buffer_ptr = buffer;
193 	  buffer_ptr = (char *)memcpy (buffer_ptr, filename, start);
194 	  buffer_ptr += start;
195 	  if (replacement != NULL)
196 	    buffer_ptr = (char *)memcpy (buffer_ptr, replacement, repl_length);
197 	  buffer_ptr += repl_length;
198 	  buffer_ptr = (char *)memcpy (buffer_ptr, p, end);
199 	  buffer_ptr += end;
200 	  *buffer_ptr = '\0';
201 
202 	  free (filename);
203 	  filename = buffer;
204 	  p = buffer + start + repl_length;
205 	}
206     }
207 
208   return filename;
209 }
210 
211 static void
allocate_filename_struct(struct gcov_filename * gf)212 allocate_filename_struct (struct gcov_filename *gf)
213 {
214   const char *gcov_prefix;
215   size_t prefix_length;
216   int strip = 0;
217   gf->filename = NULL;
218 
219   {
220     /* Check if the level of dirs to strip off specified. */
221     char *tmp = getenv("GCOV_PREFIX_STRIP");
222     if (tmp)
223       {
224         strip = atoi (tmp);
225         /* Do not consider negative values. */
226         if (strip < 0)
227           strip = 0;
228       }
229   }
230   gf->strip = strip;
231 
232   /* Get file name relocation prefix.  Non-absolute values are ignored. */
233   gcov_prefix = getenv("GCOV_PREFIX");
234   prefix_length = gcov_prefix ? strlen (gcov_prefix) : 0;
235 
236   /* Remove an unnecessary trailing '/' */
237   if (prefix_length && IS_DIR_SEPARATOR (gcov_prefix[prefix_length - 1]))
238     prefix_length--;
239 
240   /* If no prefix was specified and a prefix stip, then we assume
241      relative.  */
242   if (!prefix_length && gf->strip)
243     {
244       gcov_prefix = ".";
245       prefix_length = 1;
246     }
247 
248   /* Allocate and initialize the filename scratch space.  */
249   if (prefix_length)
250     {
251       gf->prefix = (char *) xmalloc (prefix_length + 1);
252       char *p = (char *) memcpy (gf->prefix, gcov_prefix, prefix_length);
253       *(p + prefix_length) = '\0';
254     }
255   else
256     gf->prefix = NULL;
257 }
258 
259 /* Open a gcda file specified by GI_FILENAME.
260    Return -1 on error.  Return 0 on success.  */
261 
262 static int
gcov_exit_open_gcda_file(struct gcov_info * gi_ptr,struct gcov_filename * gf)263 gcov_exit_open_gcda_file (struct gcov_info *gi_ptr,
264 			  struct gcov_filename *gf)
265 {
266   int append_slash = 0;
267   const char *fname = gi_ptr->filename;
268 
269   /* Build relocated filename, stripping off leading
270      directories from the initial filename if requested. */
271   if (gf->strip > 0)
272     {
273       const char *probe = fname;
274       int level;
275 
276       /* Remove a leading separator, without counting it.  */
277       if (IS_DIR_SEPARATOR (*probe))
278 	probe++;
279 
280       /* Skip selected directory levels.  If we fall off the end, we
281 	 keep the final part.  */
282       for (level = gf->strip; *probe && level; probe++)
283         if (IS_DIR_SEPARATOR (*probe))
284           {
285             fname = probe;
286             level--;
287           }
288     }
289 
290   /* Update complete filename with stripped original. */
291   if (gf->prefix)
292     {
293       /* Avoid to add multiple drive letters into combined path.  */
294       if (HAS_DRIVE_SPEC(fname))
295 	fname += 2;
296 
297       if (!IS_DIR_SEPARATOR (*fname))
298 	append_slash = 1;
299     }
300 
301   size_t prefix_length = gf->prefix ? strlen (gf->prefix) : 0;
302   gf->filename = (char *) xmalloc (prefix_length + strlen (fname) + 2);
303   *gf->filename = '\0';
304   if (prefix_length)
305     strcat (gf->filename, gf->prefix);
306   if (append_slash)
307     *gf->filename++ = '/';
308   strcat (gf->filename, fname);
309 
310   gf->filename = replace_filename_variables (gf->filename);
311 
312   if (!gcov_open (gf->filename))
313     {
314       /* Open failed likely due to missed directory.
315          Create directory and retry to open file. */
316       if (create_file_directory (gf->filename))
317         {
318           fprintf (stderr, "profiling:%s:Skip\n", gf->filename);
319           return -1;
320         }
321       if (!gcov_open (gf->filename))
322         {
323           fprintf (stderr, "profiling:%s:Cannot open\n", gf->filename);
324           return -1;
325         }
326     }
327 
328   return 0;
329 }
330