#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>
#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 =
"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. */
/* 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;
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
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. */
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. */
* - 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.
*/
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;
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