diff options
author | Tharre <tharre3@gmail.com> | 2016-07-26 12:12:15 +0200 |
---|---|---|
committer | Tharre <tharre3@gmail.com> | 2016-07-26 12:12:15 +0200 |
commit | ca1b481d6e8606db85e82c236c8edf29b49126c8 (patch) | |
tree | f2067117a75da2562b7acf9a2e52d42eff6e647f | |
parent | ca3cb1fd17adb1ec8c86372cd11333f6e345e013 (diff) | |
download | redo-ca1b481d6e8606db85e82c236c8edf29b49126c8.tar.gz redo-ca1b481d6e8606db85e82c236c8edf29b49126c8.tar.xz redo-ca1b481d6e8606db85e82c236c8edf29b49126c8.zip |
Implement new dependency storage system
-rwxr-xr-x | build.sh | 4 | ||||
-rw-r--r-- | out/redo.do | 2 | ||||
-rw-r--r-- | src/DSV.c | 188 | ||||
-rw-r--r-- | src/DSV.h | 41 | ||||
-rw-r--r-- | src/build.c | 216 | ||||
-rw-r--r-- | src/build.h | 2 | ||||
-rw-r--r-- | src/redo.c | 6 |
7 files changed, 349 insertions, 110 deletions
@@ -10,8 +10,10 @@ $CC $CFLAGS -o out/util.o -c src/util.c $CC $CFLAGS -o out/build.o -c src/build.c $CC $CFLAGS -o out/filepath.o -c src/filepath.c $CC $CFLAGS -o out/sha1.o -c src/sha1.c +$CC $CFLAGS -o out/DSV.o -c src/DSV.c $CC $CFLAGS -o out/redo.o -c src/redo.c -$CC -o out/redo out/redo.o out/util.o out/build.o out/filepath.o out/sha1.o $LDFLAGS +$CC -o out/redo out/redo.o out/util.o out/build.o out/filepath.o out/sha1.o \ + out/DSV.o $LDFLAGS # TODO: just for convenience, should be removed as soon as redo can build itself sudo install out/redo /usr/local/bin diff --git a/out/redo.do b/out/redo.do index 58e7152..ee098a0 100644 --- a/out/redo.do +++ b/out/redo.do @@ -1,5 +1,5 @@ . ./config.sh -DEPS="redo.o build.o util.o filepath.o sha1.o" +DEPS="redo.o build.o util.o filepath.o sha1.o DSV.o" redo-ifchange $DEPS config.sh $CC -o $3 $DEPS $LDFLAGS diff --git a/src/DSV.c b/src/DSV.c new file mode 100644 index 0000000..91cc7ee --- /dev/null +++ b/src/DSV.c @@ -0,0 +1,188 @@ +/* DSV.c + * + * Copyright (c) 2016 Tharre + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include "util.h" +#include "dbg.h" +#include "DSV.h" + +/* dest must be 2x src */ +size_t encode_string(char *dest, const char *src) { + char *start = dest, c; + + while ((c = *(src++))) { + switch (c) { + case '\n': + *(dest++) = '\\'; + *(dest++) = 'n'; + break; + case '\\': + *(dest++) = '\\'; + *(dest++) = '\\'; + break; + case ':': + *(dest++) = '\\'; + /* fall-through */ + default: + *(dest++) = c; + } + } + + *dest = '\0'; + return dest - start; +} + +static void decode_string(char *dest, const char *src, size_t len) { + for (size_t i = 0; i < len-1; ++i) { + char c = src[i]; + if (c != '\\') { + *(dest++) = c; + continue; + } + + /* check if we are at the end of the string */ + if (*src == '\0') { + debug("DSV: ignored extra '\\' at end of string\n"); + break; + } + + switch (c = *(src++)) { + case 'n': + *(dest++) = '\n'; + break; + default: + *(dest++) = c; + } + } + + *dest = '\0'; +} + +void dsv_init(struct dsv_ctx *ctx, size_t fields_count) { + ctx->fields_count = fields_count; + ctx->fields = xmalloc(fields_count * sizeof(char*)); + ctx->offset = 0; + ctx->bufsize = 1024; + ctx->buf = xmalloc(ctx->bufsize); + ctx->buflen = 0; +} + +void dsv_free(struct dsv_ctx *ctx) { + free(ctx->fields); + free(ctx->buf); +} + +int dsv_parse_next_line(struct dsv_ctx *context, const char *src, size_t len) { + char *newline = memchr(src, '\n', len); + + if (!newline) { + context->status = E_NO_NEWLINE_FOUND; + return 1; + } + + context->processed = newline-src+1; + if (context->processed == 1) { + debug("DSV: empty newline detected\n"); + context->status = E_EMPTY_NEWLINE; + return 1; + } + + char *start = (char*) src; + size_t i = 0; + ptrdiff_t size; + + while (1) { + char *colon = memchr(start, ':', newline-start+1); + if (colon) + size = colon-start; + else + size = newline-start; + + if (size <= 0) { + if (!size) { + debug("DSV: empty field detected\n"); + context->status = E_EMPTY_FIELD; + goto error; + } + + break; + } + + char *buf = xmalloc(size+1); + decode_string(buf, start, size+1); + context->fields[i] = buf; + + start += size + 1; + ++i; + if (i > context->fields_count) { + debug("DSV: too many fields\n"); + context->status = E_TOO_MANY_FIELDS; + goto error; + } + } + + if (i+1 < context->fields_count) { + debug("DSV: too few fields (%zu)\n", i+1); + context->status = E_TOO_FEW_FIELDS; + goto error; + } + + context->status = E_SUCCESS; + return 0; + +error: + for (size_t j = 0; j < i; ++j) + free(context->fields[j]); + + return 1; +} + +int dsv_parse_file(struct dsv_ctx *ctx, FILE *fp) { + size_t read = ctx->buflen; + if (!read) { + read = fread(ctx->buf, 1, ctx->bufsize, fp); + ctx->buflen = read; + } + + while (read > 0) { + if (dsv_parse_next_line(ctx, ctx->buf+ctx->offset, read-ctx->offset)) { + if (ctx->status != E_NO_NEWLINE_FOUND) + return 1; + + /* are we using the full buffer already? */ + if (!ctx->offset) { + /* then make it bigger! */ + ctx->bufsize *= 2; + ctx->buf = xrealloc(ctx->buf, ctx->bufsize); + } else { + /* then move stuff around */ + memmove(ctx->buf, ctx->buf+ctx->offset, read-ctx->offset); + ctx->buflen -= ctx->offset; + ctx->offset = 0; + } + + read = fread(ctx->buf+ctx->buflen, 1, ctx->bufsize-ctx->buflen, fp); + ctx->buflen += read; + } else { + ctx->offset += ctx->processed; + ctx->status = E_SUCCESS; + return 0; + } + } + + if (ferror(fp)) { + ctx->status = E_FREAD_ERROR; + return 1; + } + + return 1; +} diff --git a/src/DSV.h b/src/DSV.h new file mode 100644 index 0000000..6b0c315 --- /dev/null +++ b/src/DSV.h @@ -0,0 +1,41 @@ +/* build.h + * + * Copyright (c) 2016 Tharre + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +#ifndef __RDSV_H__ +#define __RDSV_H__ + +enum dsv_status { + E_SUCCESS = 0, + E_NO_NEWLINE_FOUND, + E_EMPTY_NEWLINE, + E_EMPTY_FIELD, + E_TOO_MANY_FIELDS, + E_TOO_FEW_FIELDS, + E_FREAD_ERROR, +}; + +struct dsv_ctx { + size_t processed; + size_t fields_count; + size_t offset; + size_t bufsize; + size_t buflen; + + char **fields; + char *buf; + + enum dsv_status status; +}; + +size_t encode_string(char *dest, const char *src); +void dsv_init(struct dsv_ctx *context, size_t fields_count); +void dsv_free(struct dsv_ctx *context); +int dsv_parse_next_line(struct dsv_ctx *context, const char *src, size_t len); +int dsv_parse_file(struct dsv_ctx *ctx, FILE *fp); + +#endif diff --git a/src/build.c b/src/build.c index 5f1de78..8903563 100644 --- a/src/build.c +++ b/src/build.c @@ -24,6 +24,7 @@ #include "build.h" #include "util.h" #include "filepath.h" +#include "DSV.h" #define _FILENAME "build.c" #include "dbg.h" @@ -50,7 +51,7 @@ static char **parsecmd(char *cmd, size_t *i, size_t keep_free); static char *get_relpath(const char *target); static char *xrealpath(const char *path); static char *get_dep_path(const char *target); -static void write_dep_header(dep_info *dep); +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); @@ -70,7 +71,7 @@ static int build_target(dep_info *dep) { if (!dep->hash) dep->hash = hash_file(dep->target); - write_dep_header(dep); + write_dep_information(dep); goto exit; } @@ -86,6 +87,12 @@ static int build_target(dep_info *dep) { if (remove(dep->path) && errno != ENOENT) fatal("redo: failed to remove %s", dep->path); + char *prereq = concat(2, dep->path, ".prereq"); + if (remove(prereq) && errno != ENOENT) + fatal("redo: failed to remove %s", prereq); + + free(prereq); + char *temp_output = concat(2, dep->target, ".redoing.tmp"); pid_t pid = fork(); @@ -154,7 +161,7 @@ static int build_target(dep_info *dep) { free(old_hash); - write_dep_header(dep); + write_dep_information(dep); } else { if (remove(temp_output) && errno != ENOENT) fatal("redo: failed to remove %s", temp_output); @@ -168,16 +175,16 @@ static int build_target(dep_info *dep) { if (!fexists(dep2.path)) { dep2.hash = hash_file(doscripts->chosen); - write_dep_header(&dep2); + write_dep_information(&dep2); free(dep2.hash); } free(dep2.path); - add_dep(doscripts->chosen, dep->target, 'c'); + add_prereq(doscripts->chosen, dep->target, 'c'); /* redo-ifcreate on specific if general was chosen */ if (doscripts->general == doscripts->chosen) - add_dep(doscripts->specific, dep->target, 'e'); + add_prereq(doscripts->specific, dep->target, 'e'); free(temp_output); exit: @@ -332,38 +339,24 @@ static char *get_dep_path(const char *target) { } /* Declare that `parent` depends on `target`. */ -void add_dep(const char *target, const char *parent, int ident) { - char *dep_path = get_dep_path(parent); - - if (strchr(target, '\n')) - die("redo: newlines in targets are not supported\n"); - - int fd = open(dep_path, O_WRONLY | O_APPEND); - if (fd < 0) { - if (errno != ENOENT) - fatal("redo: failed to open %s", dep_path); - - /* no dependency record was found, so we create one */ - fd = open(dep_path, O_WRONLY | O_APPEND | O_CREAT, 0644); - if (fd < 0) - fatal("redo: failed to open %s", dep_path); - } +void add_prereq(const char *target, const char *parent, int ident) { + char *base_path = get_dep_path(parent); + char *dep_path = concat(2, base_path, ".prereq"); - char garbage[HEADERSIZE]; - memset(garbage, 'Z', HEADERSIZE); - - /* skip header */ - if (lseek(fd, 0, SEEK_END) < (off_t) HEADERSIZE) - pwrite(fd, garbage, HEADERSIZE, 0); + int fd = open(dep_path, O_WRONLY | O_APPEND | O_CREAT, 0644); + if (fd < 0) + fatal("redo: failed to open %s", dep_path); char *reltarget = get_relpath(target); - int bufsize = strlen(reltarget) + 3; + size_t bufsize = strlen(reltarget)*2 + 3; char *buf = xmalloc(bufsize); + buf[0] = ident; - buf[1] = '\t'; - strcpy(buf+2, reltarget); - buf[bufsize-1] = '\n'; - if (write(fd, buf, bufsize) < bufsize) + buf[1] = ':'; + size_t encoded_len = encode_string(buf+2, reltarget); + buf[encoded_len+2] = '\n'; + + if (write(fd, buf, encoded_len+3) < (ssize_t) encoded_len+3) fatal("redo: failed to write to %s", dep_path); if (close(fd)) @@ -372,6 +365,7 @@ void add_dep(const char *target, const char *parent, int ident) { free(buf); free(reltarget); free(dep_path); + free(base_path); } /* Hash the target file, returning a pointer to the heap allocated hash. */ @@ -416,26 +410,22 @@ static void hex_to_sha1(const char *s, unsigned char *sha1) { } /* Write the dependency information into the specified path. */ -static void write_dep_header(dep_info *dep) { - mode_t mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH; - int out = open(dep->path, O_WRONLY | O_CREAT, mode); - if (out < 0) +static void write_dep_information(dep_info *dep) { + FILE *fd = fopen(dep->path, "w+"); + if (!fd) fatal("redo: failed to open %s", dep->path); - char buf[60]; - sprintf(buf, "%010u", atoi(getenv("REDO_MAGIC"))); - buf[10] = '\t'; - sha1_to_hex(dep->hash, buf+11); - buf[51] = '\t'; - memset(buf+52, '-', 7); - if (dep->flags & DEP_SOURCE) - buf[52] = 'S'; - buf[59] = '\n'; + char hash[41]; + sha1_to_hex(dep->hash, hash); + hash[40] = '\0'; + char *flags = dep->flags & DEP_SOURCE ? "s" : "l"; - if (write(out, buf, sizeof buf) < (ssize_t) sizeof buf) - fatal("redo: failed to write dependency record to '%s'", dep->path); + int magic = atoi(getenv("REDO_MAGIC")); - if (close(out)) + if (fprintf(fd, "%s:%010u:%s\n", hash, magic, flags) < 0) + fatal("redo: failed to write to %s", dep->path); + + if (fclose(fd)) fatal("redo: failed to close %s", dep->path); } @@ -471,85 +461,103 @@ static int handle_ident(dep_info *dep, int ident) { static int handle_c(dep_info *dep) { FILE *fp = fopen(dep->path, "rb"); if (!fp) { - if (errno == ENOENT) + if (errno == ENOENT) { /* dependency record does not exist */ return build_target(dep); - else + } else { fatal("redo: failed to open %s", dep->path); + } } - char buf[FILENAME_MAX]; + int retval = 0; + struct dsv_ctx ctx; + dsv_init(&ctx, 3); - if (fread(buf, 1, HEADERSIZE, fp) < HEADERSIZE) - fatal("redo: failed to read %zu bytes from %s", HEADERSIZE, dep->path); + if (dsv_parse_file(&ctx, fp)) { + retval = build_target(dep); + goto exit; + } errno = 0; - buf[10] = '\0'; - long magic = strtol(buf, NULL, 10); - if (errno) - return build_target(dep); + long magic = strtol(ctx.fields[1], NULL, 10); + if (errno) { + retval = build_target(dep); + goto exit; + } if (!fexists(dep->target)) { - if (buf[52] == 'S') /* source flag set */ + if (ctx.fields[2][0] == 's') { /* target is a source and must not be rebuild */ - return 1; - else - return build_target(dep); + retval = 1; + goto exit; + } else { + retval = build_target(dep); + goto exit; + } } - if (magic == atoi(getenv("REDO_MAGIC"))) + if (magic == atoi(getenv("REDO_MAGIC"))) { /* magic number matches */ - return 1; + retval = 1; + goto exit; + } unsigned char old_hash[20]; - buf[51] = '\0'; - hex_to_sha1(buf+11, old_hash); /* TODO: error checking */ + hex_to_sha1(ctx.fields[0], old_hash); /* TODO: error checking */ dep->hash = hash_file(dep->target); - if (memcmp(old_hash, dep->hash, 20)) - return build_target(dep); + if (memcmp(old_hash, dep->hash, 20)) { + retval = build_target(dep); + goto exit; + } - char *ptr; - char *root = getenv("REDO_ROOT"); - bool rebuild = false; - - while (!feof(fp)) { - ptr = buf; - - size_t read = fread(buf, 1, sizeof buf, fp); - if (ferror(fp)) - fatal("redo: failed to read %zu bytes from descriptor", sizeof buf); - - for (size_t i = 0; i < read; ++i) { - if (buf[i] != '\n') - continue; - buf[i] = '\0'; - if (!is_absolute(&ptr[2])) { - /* if our path is relative we need to prefix it with the - root project directory or the path will be invalid */ - char *abs = concat(3, root, "/", &ptr[2]); - if (update_target(abs, ptr[0])) - rebuild = true; - - free(abs); - } else { - if (update_target(&ptr[2], ptr[0])) - rebuild = true; - } - ptr = &buf[i+1]; + for (size_t i = 0; i < ctx.fields_count; ++i) + free(ctx.fields[i]); + + dsv_free(&ctx); + fclose(fp); + + + char *prereq_path = concat(2, dep->path, ".prereq"); + fp = fopen(prereq_path, "rb"); + free(prereq_path); + if (!fp) { + if (errno == ENOENT) + return 0; + else + fatal("redo: failed to open %s", dep->path); + } + + dsv_init(&ctx, 2); + + while (!dsv_parse_file(&ctx, fp)) { + char *target, *abs = NULL; + if (!is_absolute(ctx.fields[1])) { + abs = concat(3, getenv("REDO_ROOT"), "/", ctx.fields[1]); + target = abs; + } else { + target = ctx.fields[1]; } - if (read && buf[read-1] != '\n') { - if (buf != ptr) - memmove(buf, ptr, buf-ptr + sizeof buf); - else - die("redo: dependency record contains insanely long paths\n"); + if (update_target(target, ctx.fields[0][0])) { + retval = build_target(dep); + free(abs); + goto exit; } + + free(abs); + free(ctx.fields[0]); + free(ctx.fields[1]); } + goto exit2; + +exit: + for (size_t i = 0; i < ctx.fields_count; ++i) + free(ctx.fields[i]); +exit2: + dsv_free(&ctx); fclose(fp); - if (rebuild) - return build_target(dep); - return 0; + return retval; } diff --git a/src/build.h b/src/build.h index 9735080..2611479 100644 --- a/src/build.h +++ b/src/build.h @@ -11,7 +11,7 @@ #include <stdbool.h> -extern void add_dep(const char *target, const char *parent, int ident); +extern void add_prereq(const char *target, const char *parent, int ident); extern int update_target(const char *target, int ident); #endif @@ -1,6 +1,6 @@ /* redo.c * - * Copyright (c) 2014 Tharre + * Copyright (c) 2014-2016 Tharre * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. @@ -79,7 +79,7 @@ int main(int argc, char *argv[]) { die("%s must be called inside a .do script\n", argv[0]); if (ident == 'a') - add_dep(parent, parent, ident); + add_prereq(parent, parent, ident); else for (int i = 1; i < argc; ++i) { do { @@ -87,7 +87,7 @@ int main(int argc, char *argv[]) { } while (!*temp); update_target(*temp, ident); - add_dep(*temp, xbasename(parent), ident); + add_prereq(*temp, xbasename(parent), ident); *temp = NULL; } |