1/* File format for coverage information
2   Copyright (C) 1996-2022 Free Software Foundation, Inc.
3   Contributed by Bob Manson <manson@cygnus.com>.
4   Completely remangled by Nathan Sidwell <nathan@codesourcery.com>.
5
6This file is part of GCC.
7
8GCC is free software; you can redistribute it and/or modify it under
9the terms of the GNU General Public License as published by the Free
10Software Foundation; either version 3, or (at your option) any later
11version.
12
13GCC is distributed in the hope that it will be useful, but WITHOUT ANY
14WARRANTY; without even the implied warranty of MERCHANTABILITY or
15FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
16for more details.
17
18Under Section 7 of GPL version 3, you are granted additional
19permissions described in the GCC Runtime Library Exception, version
203.1, as published by the Free Software Foundation.
21
22You should have received a copy of the GNU General Public License and
23a copy of the GCC Runtime Library Exception along with this program;
24see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
25<http://www.gnu.org/licenses/>.  */
26
27/* Routines declared in gcov-io.h.  This file should be #included by
28   another source file, after having #included gcov-io.h.  */
29
30static gcov_unsigned_t *gcov_read_words (void *buffer, unsigned);
31
32struct gcov_var
33{
34  FILE *file;
35  int error;			/* < 0 overflow, > 0 disk error.  */
36  int mode;			/* < 0 writing, > 0 reading.  */
37  int endian;			/* Swap endianness.  */
38} gcov_var;
39
40/* Save the current position in the gcov file.  */
41/* We need to expose this function when compiling for gcov-tool.  */
42#ifndef IN_GCOV_TOOL
43static inline
44#endif
45gcov_position_t
46gcov_position (void)
47{
48  return ftell (gcov_var.file);
49}
50
51/* Return nonzero if the error flag is set.  */
52/* We need to expose this function when compiling for gcov-tool.  */
53#ifndef IN_GCOV_TOOL
54static inline
55#endif
56int
57gcov_is_error (void)
58{
59  return gcov_var.file ? gcov_var.error : 1;
60}
61
62#if IN_LIBGCOV
63/* Move to beginning of file and initialize for writing.  */
64GCOV_LINKAGE inline void
65gcov_rewrite (void)
66{
67  gcov_var.mode = -1;
68  fseek (gcov_var.file, 0L, SEEK_SET);
69}
70#endif
71
72static inline gcov_unsigned_t
73from_file (gcov_unsigned_t value)
74{
75#if !IN_LIBGCOV || defined (IN_GCOV_TOOL)
76  if (gcov_var.endian)
77    return __builtin_bswap32 (value);
78#endif
79  return value;
80}
81
82/* Open a gcov file. NAME is the name of the file to open and MODE
83   indicates whether a new file should be created, or an existing file
84   opened. If MODE is >= 0 an existing file will be opened, if
85   possible, and if MODE is <= 0, a new file will be created. Use
86   MODE=0 to attempt to reopen an existing file and then fall back on
87   creating a new one.  If MODE > 0, the file will be opened in
88   read-only mode.  Otherwise it will be opened for modification.
89   Return zero on failure, non-zero on success.  */
90
91GCOV_LINKAGE int
92#if IN_LIBGCOV
93gcov_open (const char *name)
94#else
95gcov_open (const char *name, int mode)
96#endif
97{
98#if IN_LIBGCOV
99  int mode = 0;
100#endif
101#if GCOV_LOCKED
102  struct flock s_flock;
103  int fd;
104
105  s_flock.l_whence = SEEK_SET;
106  s_flock.l_start = 0;
107  s_flock.l_len = 0; /* Until EOF.  */
108  s_flock.l_pid = getpid ();
109#elif GCOV_LOCKED_WITH_LOCKING
110  int fd;
111#endif
112
113  gcov_nonruntime_assert (!gcov_var.file);
114  gcov_var.error = 0;
115#if !IN_LIBGCOV || defined (IN_GCOV_TOOL)
116  gcov_var.endian = 0;
117#endif
118#if GCOV_LOCKED
119  if (mode > 0)
120    {
121      /* Read-only mode - acquire a read-lock.  */
122      s_flock.l_type = F_RDLCK;
123      /* pass mode (ignored) for compatibility */
124      fd = open (name, O_RDONLY, S_IRUSR | S_IWUSR);
125    }
126  else
127     {
128       /* Write mode - acquire a write-lock.  */
129       s_flock.l_type = F_WRLCK;
130       /* Truncate if force new mode.  */
131       fd = open (name, O_RDWR | O_CREAT | (mode < 0 ? O_TRUNC : 0), 0666);
132    }
133  if (fd < 0)
134    return 0;
135
136  while (fcntl (fd, F_SETLKW, &s_flock) && errno == EINTR)
137    continue;
138
139  gcov_var.file = fdopen (fd, (mode > 0) ? "rb" : "r+b");
140
141  if (!gcov_var.file)
142    {
143      close (fd);
144      return 0;
145    }
146#elif GCOV_LOCKED_WITH_LOCKING
147  if (mode > 0)
148    {
149      /* pass mode (ignored) for compatibility */
150      fd = open (name, O_RDONLY | O_BINARY, S_IRUSR | S_IWUSR);
151    }
152  else
153     {
154       /* Truncate if force new mode.  */
155       fd = open (name, O_RDWR | O_BINARY | O_CREAT | (mode < 0 ? O_TRUNC : 0),
156		  0666);
157    }
158  if (fd < 0)
159    return 0;
160
161  if (_locking (fd, _LK_LOCK, LONG_MAX) < 0)
162    {
163      close (fd);
164      return 0;
165    }
166
167  gcov_var.file = fdopen (fd, (mode > 0) ? "rb" : "r+b");
168
169  if (!gcov_var.file)
170    {
171      close (fd);
172      return 0;
173    }
174#else
175  if (mode >= 0)
176    /* Open an existing file.  */
177    gcov_var.file = fopen (name, (mode > 0) ? "rb" : "r+b");
178
179  if (gcov_var.file)
180    mode = 1;
181  else if (mode <= 0)
182    /* Create a new file.  */
183    gcov_var.file = fopen (name, "w+b");
184
185  if (!gcov_var.file)
186    return 0;
187#endif
188
189  gcov_var.mode = mode ? mode : 1;
190
191  return 1;
192}
193
194/* Close the current gcov file. Flushes data to disk. Returns nonzero
195   on failure or error flag set.  */
196
197GCOV_LINKAGE int
198gcov_close (void)
199{
200  if (gcov_var.file)
201    {
202      if (fclose (gcov_var.file))
203	gcov_var.error = 1;
204
205      gcov_var.file = 0;
206    }
207  gcov_var.mode = 0;
208  return gcov_var.error;
209}
210
211#if !IN_LIBGCOV || defined (IN_GCOV_TOOL)
212/* Check if MAGIC is EXPECTED. Use it to determine endianness of the
213   file. Returns +1 for same endian, -1 for other endian and zero for
214   not EXPECTED.  */
215
216GCOV_LINKAGE int
217gcov_magic (gcov_unsigned_t magic, gcov_unsigned_t expected)
218{
219  if (magic == expected)
220    return 1;
221
222  if (__builtin_bswap32 (magic) == expected)
223    {
224      gcov_var.endian = 1;
225      return -1;
226    }
227  return 0;
228}
229#endif
230
231#if !IN_GCOV
232/* Write DATA of LENGTH characters to coverage file.  */
233
234GCOV_LINKAGE void
235gcov_write (const void *data, unsigned length)
236{
237  gcov_unsigned_t r = fwrite (data, length, 1, gcov_var.file);
238  if (r != 1)
239    gcov_var.error = 1;
240}
241
242/* Write unsigned VALUE to coverage file.  */
243
244GCOV_LINKAGE void
245gcov_write_unsigned (gcov_unsigned_t value)
246{
247  gcov_unsigned_t r = fwrite (&value, sizeof (value), 1, gcov_var.file);
248  if (r != 1)
249    gcov_var.error = 1;
250}
251
252#if !IN_LIBGCOV
253/* Write STRING to coverage file.  Sets error flag on file
254   error, overflow flag on overflow */
255
256GCOV_LINKAGE void
257gcov_write_string (const char *string)
258{
259  unsigned length = 0;
260
261  if (string)
262    length = strlen (string) + 1;
263
264  gcov_write_unsigned (length);
265  if (length > 0)
266    {
267      gcov_unsigned_t r = fwrite (string, length, 1, gcov_var.file);
268      if (r != 1)
269	gcov_var.error = 1;
270    }
271}
272#endif
273
274#if !IN_LIBGCOV
275/* Write FILENAME to coverage file.  Sets error flag on file
276   error, overflow flag on overflow */
277
278GCOV_LINKAGE void
279gcov_write_filename (const char *filename)
280{
281  if (profile_abs_path_flag && filename && filename[0]
282      && !(IS_DIR_SEPARATOR (filename[0])
283#if HAVE_DOS_BASED_FILE_SYSTEM
284	   || filename[1] == ':'
285#endif
286	  ))
287    {
288      char *buf = getcwd (NULL, 0);
289      if (buf != NULL && buf[0])
290	{
291	  size_t len = strlen (buf);
292	  buf = (char*)xrealloc (buf, len + strlen (filename) + 2);
293	  if (!IS_DIR_SEPARATOR (buf[len - 1]))
294	    strcat (buf, "/");
295	  strcat (buf, filename);
296	  gcov_write_string (buf);
297	  free (buf);
298	  return;
299	}
300    }
301
302  gcov_write_string (filename);
303}
304#endif
305
306/* Move to a given position in a gcov file.  */
307
308GCOV_LINKAGE void
309gcov_seek (gcov_position_t base)
310{
311  fseek (gcov_var.file, base, SEEK_SET);
312}
313
314#if !IN_LIBGCOV
315/* Write a tag TAG and reserve space for the record length. Return a
316   value to be used for gcov_write_length.  */
317
318GCOV_LINKAGE gcov_position_t
319gcov_write_tag (gcov_unsigned_t tag)
320{
321  gcov_position_t result = gcov_position ();
322  gcov_write_unsigned (tag);
323  gcov_write_unsigned (0);
324
325  return result;
326}
327
328/* Write a record length using POSITION, which was returned by
329   gcov_write_tag.  The current file position is the end of the
330   record, and is restored before returning.  Returns nonzero on
331   overflow.  */
332
333GCOV_LINKAGE void
334gcov_write_length (gcov_position_t position)
335{
336  gcov_position_t current_position = gcov_position ();
337  gcov_nonruntime_assert (gcov_var.mode < 0);
338  gcov_nonruntime_assert (current_position >= position + 2 * GCOV_WORD_SIZE);
339
340  gcov_seek (position + GCOV_WORD_SIZE);
341  gcov_write_unsigned (current_position - position - 2 * GCOV_WORD_SIZE);
342  gcov_seek (current_position);
343}
344
345#else /* IN_LIBGCOV */
346
347/* Write a summary structure to the gcov file.  */
348
349GCOV_LINKAGE void
350gcov_write_summary (gcov_unsigned_t tag, const struct gcov_summary *summary)
351{
352  gcov_write_unsigned (tag);
353  gcov_write_unsigned (GCOV_TAG_SUMMARY_LENGTH);
354  gcov_write_unsigned (summary->runs);
355  gcov_write_unsigned (summary->sum_max);
356}
357
358#endif /* IN_LIBGCOV */
359
360#endif /*!IN_GCOV */
361
362/* Return a pointer to read COUNT bytes from the gcov file.  Returns
363   NULL on failure (read past EOF).  */
364
365static void *
366gcov_read_bytes (void *buffer, unsigned count)
367{
368  if (gcov_var.mode <= 0)
369    return NULL;
370
371  unsigned read = fread (buffer, count, 1, gcov_var.file);
372  if (read != 1)
373    return NULL;
374
375  return buffer;
376}
377
378/* Read WORDS gcov_unsigned_t values from gcov file.  */
379
380static gcov_unsigned_t *
381gcov_read_words (void *buffer, unsigned words)
382{
383  return (gcov_unsigned_t *)gcov_read_bytes (buffer, GCOV_WORD_SIZE * words);
384}
385
386/* Read unsigned value from a coverage file. Sets error flag on file
387   error, overflow flag on overflow */
388
389GCOV_LINKAGE gcov_unsigned_t
390gcov_read_unsigned (void)
391{
392  gcov_unsigned_t value;
393  gcov_unsigned_t allocated_buffer[1];
394  gcov_unsigned_t *buffer = gcov_read_words (&allocated_buffer, 1);
395
396  if (!buffer)
397    return 0;
398
399  value = from_file (buffer[0]);
400  return value;
401}
402
403/* Read counter value from a coverage file. Sets error flag on file
404   error, overflow flag on overflow */
405
406GCOV_LINKAGE gcov_type
407gcov_read_counter (void)
408{
409  gcov_type value;
410  gcov_unsigned_t allocated_buffer[2];
411  gcov_unsigned_t *buffer = gcov_read_words (&allocated_buffer, 2);
412
413  if (!buffer)
414    return 0;
415  value = from_file (buffer[0]);
416  if (sizeof (value) > sizeof (gcov_unsigned_t))
417    value |= ((gcov_type) from_file (buffer[1])) << 32;
418  else if (buffer[1])
419    gcov_var.error = -1;
420
421  return value;
422}
423
424/* Mangle filename path of BASE and output new allocated pointer with
425   mangled path.  */
426
427char *
428mangle_path (char const *base)
429{
430  /* Convert '/' to '#', convert '..' to '^',
431     convert ':' to '~' on DOS based file system.  */
432  const char *probe;
433  char *buffer = (char *)xmalloc (strlen (base) + 1);
434  char *ptr = buffer;
435
436#if HAVE_DOS_BASED_FILE_SYSTEM
437  if (base[0] && base[1] == ':')
438    {
439      ptr[0] = base[0];
440      ptr[1] = '~';
441      ptr += 2;
442      base += 2;
443    }
444#endif
445  for (; *base; base = probe)
446    {
447      size_t len;
448
449      for (probe = base; *probe; probe++)
450	if (*probe == '/')
451	  break;
452      len = probe - base;
453      if (len == 2 && base[0] == '.' && base[1] == '.')
454	*ptr++ = '^';
455      else
456	{
457	  memcpy (ptr, base, len);
458	  ptr += len;
459	}
460      if (*probe)
461	{
462	  *ptr++ = '#';
463	  probe++;
464	}
465    }
466
467  /* Terminate the string.  */
468  *ptr = '\0';
469
470  return buffer;
471}
472
473/* We need to expose the below function when compiling for gcov-tool.  */
474
475#if !IN_LIBGCOV || defined (IN_GCOV_TOOL)
476/* Read string from coverage file.  Allocate the buffer for the string
477   from the heap or die.  Return a pointer to the string, or NULL on
478   empty string.  */
479
480GCOV_LINKAGE const char *
481gcov_read_string (void)
482{
483  unsigned length = gcov_read_unsigned ();
484
485  if (!length)
486    return 0;
487
488  void *buffer = XNEWVEC (char *, length);
489  return (const char *) gcov_read_bytes (buffer, length);
490}
491#endif
492
493GCOV_LINKAGE void
494gcov_read_summary (struct gcov_summary *summary)
495{
496  summary->runs = gcov_read_unsigned ();
497  summary->sum_max = gcov_read_unsigned ();
498}
499
500/* We need to expose the below function when compiling for gcov-tool.  */
501
502#if !IN_LIBGCOV || defined (IN_GCOV_TOOL)
503/* Reset to a known position.  BASE should have been obtained from
504   gcov_position, LENGTH should be a record length.  */
505
506GCOV_LINKAGE void
507gcov_sync (gcov_position_t base, gcov_unsigned_t length)
508{
509  gcov_nonruntime_assert (gcov_var.mode > 0);
510  base += length;
511  fseek (gcov_var.file, base, SEEK_SET);
512}
513#endif
514
515#if IN_GCOV > 0
516/* Return the modification time of the current gcov file.  */
517
518GCOV_LINKAGE time_t
519gcov_time (void)
520{
521  struct stat status;
522
523  if (fstat (fileno (gcov_var.file), &status))
524    return 0;
525  else
526    return status.st_mtime;
527}
528#endif /* IN_GCOV */
529