diff options
-rw-r--r-- | src/build.c | 108 |
1 files changed, 74 insertions, 34 deletions
diff --git a/src/build.c b/src/build.c index cd6f8dc..d601e42 100644 --- a/src/build.c +++ b/src/build.c @@ -6,7 +6,7 @@ * of the MIT license. See the LICENSE file for details. */ -#define _XOPEN_SOURCE 600 +#define _XOPEN_SOURCE 700 #include <stdbool.h> #include <stdio.h> #include <stdlib.h> @@ -17,6 +17,7 @@ #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> +#include <time.h> #include <libgen.h> /* dirname(), basename() */ @@ -40,6 +41,7 @@ typedef struct dep_info { const char *target; char *path; unsigned char *hash; + struct timespec ctime; int32_t flags; #define DEP_SOURCE (1 << 1) } dep_info; @@ -54,7 +56,7 @@ static char *get_dep_path(const char *target); static void write_dep_information(dep_info *dep); static int handle_ident(dep_info *dep, int ident); static int handle_c(dep_info *dep); -static unsigned char *hash_file(const char *target); +static void update_dep_info(dep_info *dep, const char *target); /* Build given target, using it's .do script. */ @@ -68,8 +70,8 @@ static int build_target(dep_info *dep) { /* if our target file has no .do script associated but exists, then we treat it as a source */ dep->flags |= DEP_SOURCE; - if (!dep->hash) - dep->hash = hash_file(dep->target); + if (!dep->hash) /* if the hash was retrieved, ctime was too */ + update_dep_info(dep, dep->target); write_dep_information(dep); goto exit; @@ -155,7 +157,7 @@ static int build_target(dep_info *dep) { /* recalculate hash after successful build */ unsigned char *old_hash = dep->hash; - dep->hash = hash_file(dep->target); + update_dep_info(dep, dep->target); if (old_hash) retval = memcmp(dep->hash, old_hash, 20); @@ -174,7 +176,7 @@ static int build_target(dep_info *dep) { }; if (!fexists(dep2.path)) { - dep2.hash = hash_file(doscripts->chosen); + update_dep_info(&dep2, doscripts->chosen); write_dep_information(&dep2); free(dep2.hash); } @@ -369,11 +371,7 @@ void add_prereq(const char *target, const char *parent, int ident) { } /* Hash the target file, returning a pointer to the heap allocated hash. */ -static unsigned char *hash_file(const char *target) { - FILE *in = fopen(target, "rb"); - if (!in) - fatal("redo: failed to open %s", target); - +static unsigned char *hash_file(FILE *fp) { unsigned char *hash = xmalloc(20); SHA_CTX context; @@ -381,17 +379,31 @@ static unsigned char *hash_file(const char *target) { size_t read; SHA1_Init(&context); - while ((read = fread(data, 1, sizeof data, in))) + while ((read = fread(data, 1, sizeof data, fp))) SHA1_Update(&context, data, read); - if (ferror(in)) - fatal("redo: failed to read from %s", target); + if (ferror(fp)) + fatal("redo: failed to read data"); SHA1_Final(hash, &context); - fclose(in); return hash; } +/* Update hash & ctime information stored in the given dep_info struct */ +static void update_dep_info(dep_info *dep, const char *target) { + FILE *fp = fopen(target, "rb"); + if (!fp) + fatal("redo: failed to open %s", target); + + dep->hash = hash_file(fp); + struct stat st; + if (fstat(fileno(fp), &st)) + fatal("redo: failed to aquire stat() %s", target); + + dep->ctime = st.st_ctim; + fclose(fp); +} + static void sha1_to_hex(const unsigned char *sha1, char *buf) { static const char hex[] = "0123456789abcdef"; @@ -422,7 +434,9 @@ static void write_dep_information(dep_info *dep) { int magic = atoi(getenv("REDO_MAGIC")); - if (fprintf(fd, "%s:%010u:%s\n", hash, magic, flags) < 0) + /* TODO: casting time_t to long long is probably not entirely portable */ + if (fprintf(fd, "%s:%lld.%.9ld:%010u:%s\n", hash, + (long long)dep->ctime.tv_sec, dep->ctime.tv_nsec, magic, flags) < 0) fatal("redo: failed to write to %s", dep->path); if (fclose(fd)) @@ -459,8 +473,8 @@ static int handle_ident(dep_info *dep, int ident) { } static int handle_c(dep_info *dep) { - FILE *fp = fopen(dep->path, "rb"); - if (!fp) { + FILE *pathfd = fopen(dep->path, "rb"); + if (!pathfd) { if (errno == ENOENT) { /* dependency record does not exist */ return build_target(dep); @@ -471,22 +485,25 @@ static int handle_c(dep_info *dep) { int retval = 0; struct dsv_ctx ctx; - dsv_init(&ctx, 3); + dsv_init(&ctx, 4); - if (dsv_parse_file(&ctx, fp)) { + if (dsv_parse_file(&ctx, pathfd)) { retval = build_target(dep); goto exit2; } errno = 0; - long magic = strtol(ctx.fields[1], NULL, 10); + long magic = strtol(ctx.fields[2], NULL, 10); if (errno) { retval = build_target(dep); goto exit; } - if (!fexists(dep->target)) { - if (ctx.fields[2][0] == 's') { + FILE *targetfd = fopen(dep->target, "rb"); + if (!targetfd) { + if (errno != ENOENT) { + fatal("redo: failed to open %s\n", dep->target); + } else if (ctx.fields[3][0] == 's') { /* target is a source and must not be rebuild */ retval = 1; goto exit; @@ -498,30 +515,53 @@ static int handle_c(dep_info *dep) { if (magic == atoi(getenv("REDO_MAGIC"))) { /* magic number matches */ + fclose(targetfd); retval = 1; goto exit; } - unsigned char old_hash[20]; - hex_to_sha1(ctx.fields[0], old_hash); /* TODO: error checking */ - dep->hash = hash_file(dep->target); - - if (memcmp(old_hash, dep->hash, 20)) { + struct stat curr_st; + if (sscanf(ctx.fields[1], "%lld.%ld", (long long*)&dep->ctime.tv_sec, + &dep->ctime.tv_nsec) < 2) { + fclose(targetfd); retval = build_target(dep); goto exit; } + if (fstat(fileno(targetfd), &curr_st)) + fatal("redo: failed to stat() %s", dep->target); + + if (dep->ctime.tv_sec != curr_st.st_ctim.tv_sec + || dep->ctime.tv_nsec != curr_st.st_ctim.tv_nsec) { + /* ctime doesn't match */ + dep->ctime = curr_st.st_ctim; + + /* so check the hash */ + unsigned char old_hash[20]; + hex_to_sha1(ctx.fields[0], old_hash); /* TODO: error checking */ + dep->hash = hash_file(targetfd); + + if (memcmp(old_hash, dep->hash, 20)) { + fclose(targetfd); + retval = build_target(dep); + goto exit; + } + + /* update ctime hash */ + write_dep_information(dep); + } + for (size_t i = 0; i < ctx.fields_count; ++i) free(ctx.fields[i]); dsv_free(&ctx); - fclose(fp); - + fclose(targetfd); + fclose(pathfd); char *prereq_path = concat(2, dep->path, ".prereq"); - fp = fopen(prereq_path, "rb"); + pathfd = fopen(prereq_path, "rb"); free(prereq_path); - if (!fp) { + if (!pathfd) { if (errno == ENOENT) return 0; else @@ -530,7 +570,7 @@ static int handle_c(dep_info *dep) { dsv_init(&ctx, 2); - while (!dsv_parse_file(&ctx, fp)) { + while (!dsv_parse_file(&ctx, pathfd)) { char *target, *abs = NULL; if (!is_absolute(ctx.fields[1])) { abs = concat(3, getenv("REDO_ROOT"), "/", ctx.fields[1]); @@ -557,7 +597,7 @@ exit: free(ctx.fields[i]); exit2: dsv_free(&ctx); - fclose(fp); + fclose(pathfd); return retval; } |