Support for reading in a header file's contents before overwriting it.
authorJustin Seyster <jseyster@cs.sunysb.edu>
Mon, 19 Jul 2010 23:34:14 +0000 (19:34 -0400)
committerJustin Seyster <jseyster@cs.sunysb.edu>
Mon, 19 Jul 2010 23:34:14 +0000 (19:34 -0400)
Includes locking to prevent race conditions with parallel builds.

src/aop-header.c

index f98e3747fbbcf83c602d431d6878a80ba9bc236b..b3cad5a05fdf385a6588e49d954a9685f59c11b5 100644 (file)
 #undef PACKAGE_TARNAME
 #undef PACKAGE_VERSION
 
+/* This is to make sure that stdio.h includes the prototype for
+   fgets_unlocked, which GCC prefers over regular fgets.  */
+#define _GNU_SOURCE
+
+#include <fcntl.h>
 #include <locale.h>
 #include <stdarg.h>
 #include <stdio.h>
+#include <string.h>
 
 #include <config.h>
 #include <system.h>
@@ -40,7 +46,6 @@
 #include "aop-header.h"
 #include "aop-type.h"
 
-
 /* The default license text for InterAspect-generated headers.  This
    license text _does not_ apply to this file, of course! */
 static const char *default_license =
@@ -64,7 +69,8 @@ static const char *header_typedefs =
   "typedef int ALL_SIGNED_T;\n"
   "typedef unsigned int ALL_UNSIGNED_T;\n"
   "typedef double ALL_FP_T;\n"
-  "typedef void *ALL_POINTER_T;\n";
+  "typedef void *ALL_POINTER_T;\n\n"
+  "/* BEGIN PROTOTYPES */\n";
 
 /* An aop_prototype struct represents the prototype of an advice
    function, including its name, return type and parameters. */
@@ -92,7 +98,7 @@ static htab_t prototype_table = NULL;
 /* This is the equality function that the hashtable uses to determine
    if a prototype entry in the table matches a key. */
 static int
-htab_str_eq (const void *table_entry, const void *key)
+htab_protoype_eq (const void *table_entry, const void *key)
 {
   const struct aop_prototype *prototype =
     (const struct aop_prototype *)table_entry;
@@ -100,6 +106,14 @@ htab_str_eq (const void *table_entry, const void *key)
   return (strcmp (prototype->name, key) == 0);
 }
 
+/* This is the equality function that the hashtable uses to determine
+   if a string entry in a table matches a key. */
+static int
+htab_str_eq (const void *table_entry, const void *key)
+{
+  return (strcmp ((const char *)table_entry, (const char *)key) == 0);
+}
+
 /* Return true if a prototype has the same return value and parameters
    as a potential new prototype. */
 static bool
@@ -258,72 +272,169 @@ is_valid_c_symbol (const char *symbol)
   return true;  /* No invalid characters. */
 }
 
-/* Quick-and-dirty printf variant that will check errors for us. */
-#define CHECK_PRINTF(format, ...)                      \
+/* A buffer for fgets to read lines into and for write_protoype to
+   write lines into. . */
+static char line[2048];
+
+/* Read an auto-generated header file to find the prototypes already
+   in it.  These prototypes are output as strings to a hash table.
+   Returns 0 on success or a UNIX error code on failure. */
+static int
+read_header_prototypes (FILE *header, htab_t prototype_strings)
+{
+  /* Read until we get to the beginning of the list of prototypes. */
+  while (fgets (line, sizeof (line), header) != NULL)
+    {
+      if (strstr (line, "BEGIN PROTOTYPES") != NULL)
+       break;  /* Found it. */
+    }
+
+  if (feof (header))
+    return 0;  /* There are no prototypes in this header. */
+  else if  (ferror (header))
+    return errno;
+
+  /* Now read each line and check if it is a prototype. */
+  while (fgets (line, sizeof (line), header) != NULL)
+    {
+      const char **hash_slot;
+
+      /* TODO: This is better suited to a regex match. */
+      if (line[0] == '\0')
+       continue;
+      else if (line[0] == '\n')
+       continue;
+      else if (line[0] == '#')
+       continue;
+
+      /* Found a prototype!  Insert it in the hash table. */
+      hash_slot =
+       (const char **)htab_find_slot (prototype_strings, line, INSERT);
+      if (*hash_slot == NULL)
+       *hash_slot = xstrdup (line);
+    }
+
+  if (feof (header))
+    return 0;  /* Success. */
+  else if  (ferror (header))
+    return errno;
+  else
+    aop_assert (0);  /* Why did fgets return NULL, then? */
+}
+
+/* Quick-and-dirty printf variant that will output to a buffer without
+   overrunning it. */
+#define BUF_PRINTF(format, ...)                                \
   do {                                                 \
-    size = fprintf (header, format, __VA_ARGS__);      \
-    if (size < 0)                                      \
-      return 0;                                                \
+    int bytes;                                         \
+    bytes = snprintf (out, size, format, __VA_ARGS__); \
+    out += bytes;                                      \
+    size -= bytes;                                     \
   } while (0)
 
-/* Write a single aop_prototype to a header.  This is used as a
-   callback for htab_traverse.  Returns 0 to stop the table traversal
-   in the event of an error. */
+/* Format a single aop_prototype as a string and put that string in
+   the hash table of protoype strings.  This is used as a callback for
+   htab_traverse. */
 static int
 write_prototype (void **table_entry, void *info)
 {
   int i;
-  int size;
+  char *out;
+  size_t size;
   struct aop_prototype *prototype = *table_entry;
-  FILE *header = info;
-  char c_type[256];
+  htab_t prototype_strings = (htab_t)info;
+  const char **hash_slot;
 
-  /* Note that an error will set the stream's error, so we don't need
-     to return anything. */
-  CHECK_PRINTF ("void %s(", prototype->name);
+  out = line;
+  size = sizeof (line);
+
+  BUF_PRINTF ("void %s(", prototype->name);
 
   /* Print each of the types. */
   for (i = 0; i < prototype->num_params; i++)
     {
-      const char *separator;
       const struct aop_type *param_type = prototype->param_types[i];
-      size = format_c_type (param_type, sizeof (c_type), c_type);
 
-      /* There's not much we can do to fix this! */
-      if (size >= sizeof (c_type))
-       sprintf (c_type, "#OVERLONG_TYPE_NAME#");
+      {
+       int bytes;
+       bytes = format_c_type (param_type, size, out);
+       out += bytes;
+       size -= bytes;
+      }
 
-      /* Use a comma separator if there are more parameters to
+      /* Add a comma separator if there are more parameters to
         list. */
       if (i < prototype->num_params - 1)
-       separator = ", ";
-      else
-       separator = "";
+       BUF_PRINTF("%s", ", ");
+    }
 
-      CHECK_PRINTF ("%s%s", c_type, separator);
+  BUF_PRINTF ("%s", ");\n");
+
+  /* Oops!  This prototype was too long. */
+  if (size <= 0)
+    sprintf (line, "#warning Overlong protoype.\n");
+
+  /* Save this prototype to the hash table. */
+  hash_slot = (const char **)htab_find_slot (prototype_strings, line, INSERT);
+  if (*hash_slot == NULL)
+    {
+      fprintf (stderr, "Insert\n");
+      *hash_slot = xstrdup (line);
     }
 
-  CHECK_PRINTF ("%s", ");\n");
+  return 1;  /* Continue the traversal. */
+}
+
+#undef BUF_PRINTF
+
+/* Place a string from the hash table and place it in an array.  This
+   is used as a callback for htab_traverse. */
+static int
+dump_prototype (void **table_entry, void *info)
+{
+  const char *prototype = (const char *)*table_entry;
+
+  /* This is a pointer to an array of strings. */
+  const char ***iterator = (const char ***)info;
+
+  fprintf (stderr, "Dumping prototype: %s\n", prototype);
+
+  (*iterator)[0] = prototype;
+  (*iterator)++;
 
   return 1;  /* Continue the traversal. */
 }
 
-#undef CHECK_PRINTF
+/* Comparator for quicksort.  GCC complains about type if you pass
+   strcmp directly to qsort. */
+static int
+qsort_strcmp (const void *a, const void *b)
+{
+  return strcmp (a, b);
+}
 
-/* Write the actual header contents to an already opened file.
-   Returns 0 on success or a UNIX error on failure. */
+/* Write the actual header contents to an already opened file.  The
+   prototype_strings table contains prototypes that were in the
+   original header (which we are overwriting) formatted as strings.
+   These original prototypes will also appear in the output.  Returns
+   0 on success or a UNIX error on failure. */
 static int
 write_header_contents (FILE *header, const char *guard, const char *license,
-                      const char *preamble)
+                      const char *preamble, htab_t prototype_strings)
 {
+  int i;
   int size;
+  const char **prototype_array;
+
+  /* Used by dump_prototype to iterate prototype_array. */
+  const char **iterator;
 
   /* Print the guard. */
   if (guard != NULL)
     {
       size = fprintf (header, "#ifndef %s\n#define %s\n\n", guard, guard);
       if (size < 0)
-       return errno;
+       return EIO;
     }
 
   /* Print the license and preamble text and header typedefs. */
@@ -334,20 +445,43 @@ write_header_contents (FILE *header, const char *guard, const char *license,
 
   size = fprintf (header, "%s\n%s\n%s\n", license, preamble, header_typedefs);
   if (size < 0)
-    return errno;
+    return EIO;
 
-  /* Print each of the function prototypes by traversing the hash
-     table. */
-  htab_traverse (prototype_table, write_prototype, header);
+  /* Format the prototypes into strings. */
+  htab_traverse (prototype_table, write_prototype, prototype_strings);
   if (ferror (header))
-    return errno;
+    return EIO;
+
+  /* Pull all the formatted prototypes into an array. */
+  prototype_array = xmalloc (sizeof (const char *) *
+                            htab_elements (prototype_strings));
+  iterator = prototype_array;
+  htab_traverse (prototype_strings, dump_prototype, &iterator);
+
+  /* Sort the array.  This is so that header files are identical, no
+     matter the order of compilation. */
+  qsort (prototype_array, htab_elements (prototype_strings),
+        sizeof (const char *), qsort_strcmp);
+
+  for (i = 0 ; i < htab_elements (prototype_strings); i++)
+    {
+      fprintf (stderr, "Writing prototype %d: %s", i, prototype_array[i]);
+      size = fprintf (header, "%s", prototype_array[i]);
+      if (size < 0)
+       {
+         free (prototype_array);
+         return EIO;
+       }
+    }
+
+  free (prototype_array);
 
   /* Close the guard. */
   if (guard != NULL)
     {
       size = fprintf (header, "\n#endif\n");
       if (size < 0)
-       return errno;
+       return EIO;
     }
 
   return 0; /* Success. */
@@ -389,7 +523,6 @@ write_header_contents (FILE *header, const char *guard, const char *license,
  * - Any of the possible errno results from the open(2) function.
  * - Any of the possible errno results from the write(2) function.
  * - Any of the possilbe errno results from the fcntl(2) function.
- * - Any of the possilbe errno results from the fprintf(3) function.
  * Failure will also set errno to the returned error value, so you can
  * use the perror(3) function to print a user-readable error message.
  */
@@ -398,7 +531,14 @@ aop_write_c_header (const char *filename, const char *guard,
                    const char *license, const char *preamble)
 {
   int result = 0;
+  int fcntl_res;
   FILE *header;
+  struct flock header_lock;
+
+  /* This table stores each prototype as a string, as it should be
+     written out to the header file.  Using a hash table makes it easy
+     to avoid duplicate lines. */
+  htab_t prototype_strings;
 
   if (filename == NULL)
     return EINVAL;
@@ -406,31 +546,112 @@ aop_write_c_header (const char *filename, const char *guard,
   if (guard != NULL && !is_valid_c_symbol (guard))
     return EINVAL;
 
-  header = fopen (filename, "w+");
-  if (header == NULL)
-    return errno;
+  prototype_strings = htab_create_alloc (16, htab_hash_string, htab_str_eq,
+                                        NULL, xcalloc, free);
 
-  result = write_header_contents (header, guard, license, preamble);
+  /* Create the file if it doesn't exist.  Open _without_ truncating
+     if it does. */
+  if ((header = fopen (filename, "a+")) == NULL)
+    {
+      result = errno;
+      goto out_free;
+    }
 
-  /* TODO: Flush */
+  /* Lock the file.  We use fcntl over flock because it works over
+     network file systems (on some operating systems). */
+  header_lock.l_start = 0;
+  header_lock.l_len = 0;
+  header_lock.l_pid = getpid();
+  header_lock.l_type = F_WRLCK;
+  header_lock.l_whence = SEEK_SET;
+  while ((fcntl_res = fcntl (fileno (header), F_SETLKW, &header_lock)) == -1)
+    {
+      /* A bad file descriptor means a bug in InterAsepct.
+        InterAspect never attempts to take more than one lock, so a
+        deadlock also means some kind of bug (probably in
+        InterAspect). */
+      aop_assert (errno != EBADF && errno != EDEADLK);
 
-  if (fclose (header) != 0)
+      if (errno == EINVAL)
+       {
+         verbatim ("(InterAspect) %s: Locking not supported for generated "
+                   "header.  Parallel compiles (for example with make -j) "
+                   "may corrupt this header file.", filename);
+
+         /* Give up on trying to take a lock. */
+         break;
+       }
+      else if (errno != EINTR)
+       {
+         /* Some other I/O error. */
+         result = errno;
+         goto out_close;
+       }
+
+      /* errno was EINTR, meaning we got interrupted while we were
+        trying to take the lock.  So try again! */
+      aop_assert (errno == EINTR);
+    }
+
+  /* Get the prototypes that are already in the header file. */
+  read_header_prototypes (header, prototype_strings);
+
+  /* Now erase everything in the file so we can rewrite it. */
+  if (ftruncate (fileno (header), 0) != 0)
     {
-      /* A bad file descriptor means an error in InterAspect. */
       aop_assert (errno != EBADF);
 
-      /* Anything else is a regular I/O error. */
       result = errno;
+      goto out_unlock;
     }
 
+  result = write_header_contents (header, guard, license, preamble,
+                                 prototype_strings);
+  if (result != 0)
+    goto out_unlock;
+
+  /* Flush any buffered I/O. */
+  if (fflush(header) != 0)
+    {
+      aop_assert (errno != EBADF);
+      result = errno;
+      goto out_unlock;
+    }
+
+  /* Do our best to make sure that the header sees the disk.  We don't
+     even check the error code because what are we going to do if
+     there is an error anyway? */
+  (void)fsync (fileno (header));
+
+ out_unlock:
+  /* Release the file lock.  Again, there's nothing to be done about
+     errors. */
+  header_lock.l_type = F_UNLCK;
+  (void)fcntl(fileno (header), F_SETLK, &header_lock);
+
+ out_close:
+  if (fclose (header) != 0)
+    {
+      aop_assert (errno != EBADF);
+
+      /* If we already encountered an error, report the earlier error
+        rather than this one. */
+      if (result != 0)
+       result = errno;
+    }
+
+ out_free:
+  htab_delete (prototype_strings);
+
+  errno = result;
   return result;
 }
 
 void
 init_prototype_table()
 {
-  prototype_table = htab_create_alloc (16, htab_hash_string, htab_str_eq, NULL,
-                                      xcalloc, free);
+  prototype_table = htab_create_alloc (16, htab_hash_string, htab_protoype_eq,
+                                      NULL, xcalloc, free);
 }
 
 void