1 /* Routines required for instrumenting a program.  */
2 /* Compile this one with gcc.  */
3 /* Copyright (C) 1989-2019 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, 0755) == -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 = strlen (replacement);
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 	  buffer_ptr = (char *)memcpy (buffer_ptr, replacement, repl_length);
196 	  buffer_ptr += repl_length;
197 	  buffer_ptr = (char *)memcpy (buffer_ptr, p, end);
198 	  buffer_ptr += end;
199 	  *buffer_ptr = '\0';
200 
201 	  free (filename);
202 	  filename = buffer;
203 	  p = buffer + start + repl_length;
204 	}
205     }
206 
207   return filename;
208 }
209 
210 static void
allocate_filename_struct(struct gcov_filename * gf)211 allocate_filename_struct (struct gcov_filename *gf)
212 {
213   const char *gcov_prefix;
214   size_t prefix_length;
215   int strip = 0;
216   gf->filename = NULL;
217 
218   {
219     /* Check if the level of dirs to strip off specified. */
220     char *tmp = getenv("GCOV_PREFIX_STRIP");
221     if (tmp)
222       {
223         strip = atoi (tmp);
224         /* Do not consider negative values. */
225         if (strip < 0)
226           strip = 0;
227       }
228   }
229   gf->strip = strip;
230 
231   /* Get file name relocation prefix.  Non-absolute values are ignored. */
232   gcov_prefix = getenv("GCOV_PREFIX");
233   prefix_length = gcov_prefix ? strlen (gcov_prefix) : 0;
234 
235   /* Remove an unnecessary trailing '/' */
236   if (prefix_length && IS_DIR_SEPARATOR (gcov_prefix[prefix_length - 1]))
237     prefix_length--;
238 
239   /* If no prefix was specified and a prefix stip, then we assume
240      relative.  */
241   if (!prefix_length && gf->strip)
242     {
243       gcov_prefix = ".";
244       prefix_length = 1;
245     }
246 
247   /* Allocate and initialize the filename scratch space.  */
248   if (prefix_length)
249     {
250       gf->prefix = (char *) xmalloc (prefix_length + 1);
251       char *p = (char *) memcpy (gf->prefix, gcov_prefix, prefix_length);
252       *(p + prefix_length) = '\0';
253     }
254   else
255     gf->prefix = NULL;
256 }
257 
258 /* Open a gcda file specified by GI_FILENAME.
259    Return -1 on error.  Return 0 on success.  */
260 
261 static int
gcov_exit_open_gcda_file(struct gcov_info * gi_ptr,struct gcov_filename * gf)262 gcov_exit_open_gcda_file (struct gcov_info *gi_ptr,
263 			  struct gcov_filename *gf)
264 {
265   const char *fname = gi_ptr->filename;
266   int append_slash = 0;
267 
268   fname = gi_ptr->filename;
269 
270   /* Build relocated filename, stripping off leading
271      directories from the initial filename if requested. */
272   if (gf->strip > 0)
273     {
274       const char *probe = fname;
275       int level;
276 
277       /* Remove a leading separator, without counting it.  */
278       if (IS_DIR_SEPARATOR (*probe))
279 	probe++;
280 
281       /* Skip selected directory levels.  If we fall off the end, we
282 	 keep the final part.  */
283       for (level = gf->strip; *probe && level; probe++)
284         if (IS_DIR_SEPARATOR (*probe))
285           {
286             fname = probe;
287             level--;
288           }
289     }
290 
291   /* Update complete filename with stripped original. */
292   if (gf->prefix)
293     {
294       /* Avoid to add multiple drive letters into combined path.  */
295       if (HAS_DRIVE_SPEC(fname))
296 	fname += 2;
297 
298       if (!IS_DIR_SEPARATOR (*fname))
299 	append_slash = 1;
300     }
301 
302   size_t prefix_length = gf->prefix ? strlen (gf->prefix) : 0;
303   gf->filename = (char *) xmalloc (prefix_length + strlen (fname) + 2);
304   *gf->filename = '\0';
305   if (prefix_length)
306     strcat (gf->filename, gf->prefix);
307   if (append_slash)
308     *gf->filename++ = '/';
309   strcat (gf->filename, fname);
310 
311   gf->filename = replace_filename_variables (gf->filename);
312 
313   if (!gcov_open (gf->filename))
314     {
315       /* Open failed likely due to missed directory.
316          Create directory and retry to open file. */
317       if (create_file_directory (gf->filename))
318         {
319           fprintf (stderr, "profiling:%s:Skip\n", gf->filename);
320           return -1;
321         }
322       if (!gcov_open (gf->filename))
323         {
324           fprintf (stderr, "profiling:%s:Cannot open\n", gf->filename);
325           return -1;
326         }
327     }
328 
329   return 0;
330 }
331