From: Justin Seyster Date: Mon, 19 Jul 2010 23:34:14 +0000 (-0400) Subject: Support for reading in a header file's contents before overwriting it. X-Git-Tag: release-v1.0~48^2~15 X-Git-Url: https://git.fsl.cs.stonybrook.edu/?a=commitdiff_plain;h=95cc9e7080d7f2f91058850a34319867d9383680;p=interaspect.git Support for reading in a header file's contents before overwriting it. Includes locking to prevent race conditions with parallel builds. --- diff --git a/src/aop-header.c b/src/aop-header.c index f98e374..b3cad5a 100644 --- a/src/aop-header.c +++ b/src/aop-header.c @@ -25,9 +25,15 @@ #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 #include #include #include +#include #include #include @@ -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