1/* Routines required for instrumenting a program.  */
2/* Compile this one with gcc.  */
3/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
4
5This file is part of GCC.
6
7GCC is free software; you can redistribute it and/or modify it under
8the terms of the GNU General Public License as published by the Free
9Software Foundation; either version 3, or (at your option) any later
10version.
11
12GCC is distributed in the hope that it will be useful, but WITHOUT ANY
13WARRANTY; without even the implied warranty of MERCHANTABILITY or
14FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15for more details.
16
17Under Section 7 of GPL version 3, you are granted additional
18permissions described in the GCC Runtime Library Exception, version
193.1, as published by the Free Software Foundation.
20
21You should have received a copy of the GNU General Public License and
22a copy of the GCC Runtime Library Exception along with this program;
23see 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. */
31FILE *__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
37static FILE *
38get_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
58static int __attribute__((format(printf, 1, 2)))
59gcov_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
79static void
80gcov_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
94static int
95create_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
145static char *
146replace_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
211static void
212allocate_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
262static int
263gcov_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