diff options
| author | Chloe M. <chloe@aptel.org> | 2026-08-22 06:48:01 +0000 |
|---|---|---|
| committer | Chloe M. <chloe@aptel.org> | 2026-08-22 09:45:33 +0000 |
| commit | 7683205c69c76b6515c242429a927974aedfb5c0 (patch) | |
| tree | 0d6661f646f7789681b99afab5f3eb2eb7ecb7e4 /core | |
| parent | f0691430d86a727b4ffee0f6ea3b355baecd6414 (diff) | |
core: build: Add manifest handling
This resolves redundant rebuilds by detecting when there are differences
in a built file.
Signed-off-by: Chloe M. <chloe@aptel.org>
Diffstat (limited to 'core')
| -rw-r--r-- | core/.bop/posthook.sh | 2 | ||||
| -rw-r--r-- | core/build.c | 324 | ||||
| -rw-r--r-- | core/hash.c | 54 |
3 files changed, 357 insertions, 23 deletions
diff --git a/core/.bop/posthook.sh b/core/.bop/posthook.sh index 05f3860..c0f6b10 100644 --- a/core/.bop/posthook.sh +++ b/core/.bop/posthook.sh @@ -1 +1 @@ -clang $BOP_PROJECT_ROOT/core/*.o -o bop +clang $BOP_PROJECT_ROOT/core/*.o -lcrypto -lssl -o bop diff --git a/core/build.c b/core/build.c index d0a4627..8bb7a3c 100644 --- a/core/build.c +++ b/core/build.c @@ -13,16 +13,24 @@ #include <unistd.h> #include <string.h> #include <stdio.h> +#include <assert.h> +#include "bop/hash.h" #include "bop/common.h" #include "bop/build.h" /* * Build parameters * - * @cflags: List of CFLAGS + * @cflags: List of CFLAGS + * @hashfd: Manifest file descriptor + * @n_file: Number of files to build + * @gen_manifest: If true, generate a manifest */ struct build_params { char *cflags; + int hashfd; + size_t n_file; + bool gen_manifest; }; /* @@ -83,6 +91,58 @@ trunc_extension(const char *filename) return NULL; } +/* + * Obtain the filename from a manifest line + * + * XXX: Returns memory allocate with strdup(), remember to free! + */ +static char * +manifest_filename(const char *manifest_line) +{ + char *p, *p1; + + if (manifest_line == NULL) { + return NULL; + } + + if ((p = strdup(manifest_line)) == NULL) { + printf("fatal: out of memory\n"); + return NULL; + } + + p1 = p; + while (*p1 != '\0') { + if (*p1++ == ':') { + *--p1 = '\0'; + return p; + } + } + + free(p); + return NULL; +} + +/* + * Returns true if the given file is accepted as a buildable entity + * + * @filename: Filename to check + */ +static bool +file_accepted(const char *filename) +{ + const char *ext; + + if (filename == NULL) { + return false; + } + + ext = get_extension(filename); + if (strcmp(ext, ".c") == 0) + return true; + + return false; +} + static int invoke_cc(const char *cfile_path, struct build_params *params) { @@ -120,22 +180,22 @@ invoke_cc(const char *cfile_path, struct build_params *params) } static int -compile(const char *dirpath, struct dirent *dirent, struct build_params *params) +compile(const char *dirpath, const char *filename, struct build_params *params) { - const char *filename, *ext; + const char *ext; char pathbuf[256]; + char hash[BOP_HASH_LEN]; + char manifest_entry[128]; int error; - if (dirpath == NULL || dirent == NULL) { + if (dirpath == NULL || filename == NULL) { return -1; } - /* Ignore non regulars */ - if (dirent->d_type != DT_REG) { - return 0; + if (params == NULL) { + return -1; } - filename = dirent->d_name; ext = get_extension(filename); /* Ignore files with no extensions */ @@ -145,14 +205,31 @@ compile(const char *dirpath, struct dirent *dirent, struct build_params *params) snprintf(pathbuf, sizeof(pathbuf), "%s/%s", dirpath, filename); + if ((error = bop_hash_file(pathbuf, hash)) < 0) { + printf("fatal: failed to hash %s\n", pathbuf); + return error; + } + + /* Used per accepted file to write hashes */ +#define WRITE_HASH() \ + snprintf(manifest_entry, sizeof(manifest_entry), "%s:%s\n", filename, hash); \ + write(params->hashfd, manifest_entry, strlen(manifest_entry)); + /* Build all CFILES */ if (strcmp(ext, ".c") == 0) { error = invoke_cc(pathbuf, params); - if (error < 0) + if (error < 0) { printf("fatal: cc invocation failure\n"); + return error; + } + + if (params->gen_manifest) { + WRITE_HASH(); + } return error; } +#undef WRITE_HASH return 0; } @@ -238,26 +315,17 @@ build_init_cflags(const char *dirpath, struct build_params *params) return 0; } -int -bop_build_dir(const char *dirpath, build_op_t bop) +static int +build_from_dir(const char *dirpath, DIR *dir, build_op_t bop, struct build_params *params) { - struct build_params params; struct dirent *dirent; - DIR *dir; int error; - dir = opendir(dirpath); if (dir == NULL) { - perror("opendir"); return -1; } - bzero(¶ms, sizeof(params)); - if ((error = build_init_cflags(dirpath, ¶ms)) < 0) { - closedir(dir); - return error; - } - + /* Iterate each file */ while ((dirent = readdir(dir)) != NULL) { if (dirent->d_name[0] == '.') { continue; @@ -265,7 +333,7 @@ bop_build_dir(const char *dirpath, build_op_t bop) switch (bop) { case BUILD_OP_BUILD: - error = compile(dirpath, dirent, ¶ms); + error = compile(dirpath, dirent->d_name, params); if (error < 0) { printf("fatal: failed to build '%s'\n", dirent->d_name); return error; @@ -279,10 +347,222 @@ bop_build_dir(const char *dirpath, build_op_t bop) } + return 0; +} + +static int +try_mf_make(const char *dirpath, const char *manifest_entry, + struct build_params *params, char hashres[BOP_HASH_LEN], + bool *rehash) +{ + char *p, *tok, *name; + char pathbuf[256]; + char hash[BOP_HASH_LEN]; + int error; + + if (dirpath == NULL || manifest_entry == NULL) { + return -1; + } + + if (rehash == NULL) { + return -1; + } + + *rehash = false; + if ((p = strdup(manifest_entry)) == NULL) { + printf("fatal: out of memory\n"); + return -1; + } + + /* Grab the filename and construct a path */ + tok = strtok(p, ":"); + snprintf(pathbuf, sizeof(pathbuf), "%s/%s", dirpath, tok); + + /* Copy the name */ + if ((name = strdup(tok)) == NULL) { + printf("fatal: out of memory\n"); + return -1; + } + + /* Grab the old hash and create a new hash */ + tok = strtok(NULL, ":"); + if ((error = bop_hash_file(pathbuf, hash)) < 0) { + printf("[!] failed to hash %s\n", pathbuf); + free(p); + free(name); + return -1; + } + + tok[strlen(tok) - 1] = '\0'; + if (strncmp(tok, hash, sizeof(hash)) != 0) { + error = compile(dirpath, name, params); + if (error < 0) { + printf("fatal: failed to build '%s'\n", name); + free(p); + free(name); + return -1; + } + + *rehash = true; + } + + memcpy(hashres, hash, sizeof(hash)); + free(p); + free(name); + return 0; +} + +static int +build_from_manifest(const char *dirpath, int hashfd, struct build_params *params) +{ + char line[128], *filename; + char updated_line[128]; + char newhash[BOP_HASH_LEN]; + FILE *fp; + size_t exp_n_file, pos; + size_t oldpos; + bool rehash; + + if (dirpath == NULL || params == NULL) { + return -1; + } + + if ((fp = fdopen(hashfd, "r+")) == NULL) { + perror("fdopen"); + return -1; + } + + if (fgets(line, sizeof(line), fp) == NULL) { + printf("fatal: unable to read manifest\n"); + fclose(fp); + return -1; + } + + /* + * The first line is the expected file count, if they do not match then we are to + * rebuild the entire manifest. + */ + exp_n_file = atoi(line); + if (params->n_file != exp_n_file) { + return -1; + } + + params->gen_manifest = false; + pos = ftell(fp); + + while (fgets(line, sizeof(line), fp) != NULL) { + if (try_mf_make(dirpath, line, params, newhash, &rehash) < 0) { + fclose(fp); + return -1; + } + + if (rehash) { + oldpos = ftell(fp); + if ((filename = manifest_filename(line)) == NULL) + return -1; + + /* Update the line */ + fseek(fp, pos, SEEK_SET); + snprintf(updated_line, sizeof(updated_line), "%s:%s", filename, newhash); + fwrite(updated_line, 1, strlen(updated_line), fp); + + /* Reset position and free the filename */ + fseek(fp, oldpos, SEEK_SET); + free(filename); + } + + pos = ftell(fp); + } + + fclose(fp); + return 0; +} + +int +bop_build_dir(const char *dirpath, build_op_t bop) +{ + struct build_params params; + DIR *dir; + struct dirent *dirent; + char pathbuf[128]; + char countbuf[16]; + int error, hashfd = -1; + bool build_dirs = true; + + dir = opendir(dirpath); + if (dir == NULL) { + perror("opendir"); + return -1; + } + + bzero(¶ms, sizeof(params)); + if ((error = build_init_cflags(dirpath, ¶ms)) < 0) { + closedir(dir); + return error; + } + + /* Count the number of directory entries */ + while ((dirent = readdir(dir)) != NULL) { + if (dirent->d_name[0] == '.') { + continue; + } + + if (file_accepted(dirent->d_name)) { + ++params.n_file; + } + } + + rewinddir(dir); + params.gen_manifest = true; + + /* + * If a manifest is not present then we should create one, otherwise + * we are to read it and populate the hashmap. + */ + snprintf(pathbuf, sizeof(pathbuf), "%s/.bop/manifest.hash", dirpath); + if (access(pathbuf, F_OK) != 0) { + hashfd = open(pathbuf, O_RDWR | O_CREAT, 0666); + if (hashfd < 0) { + perror("open"); + printf("fatal: failed to open manifest\n"); + return -1; + } + + params.hashfd = hashfd; + snprintf(countbuf, sizeof(countbuf), "%zu\n", params.n_file); + write(hashfd, countbuf, strlen(countbuf)); + } else { + hashfd = open(pathbuf, O_RDWR); + if (hashfd < 0) { + perror("open"); + printf("fatal: failed to open manifest\n"); + return -1; + } + + build_dirs = false; + params.hashfd = hashfd; + error = build_from_manifest(dirpath, hashfd, ¶ms); + + /* Remove manifest and build from dir if error */ + if (error < 0) { + printf("[*] flushing manifest\n"); + remove(pathbuf); + build_dirs = true; + } + } + + /* Build from directory if not manifest */ + if (build_dirs) { + error = build_from_dir(dirpath, dir, bop, ¶ms); + if (error < 0) + return error; + } + if (params.cflags != NULL) { free(params.cflags); params.cflags = NULL; } + close(hashfd); return 0; } diff --git a/core/hash.c b/core/hash.c new file mode 100644 index 0000000..557e406 --- /dev/null +++ b/core/hash.c @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2026, Chloe M., et al. + * Provided under the BSD-3 clause. + */ + +#include <sys/mman.h> +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include "bop/hash.h" + +int +bop_hash_file(const char *path, char hashres[BOP_HASH_LEN]) +{ + int fd; + size_t len, i; + uint8_t hash[SHA256_DIGEST_LENGTH]; + void *mem; + + if (path == NULL) { + return -1; + } + + if ((fd = open(path, O_RDONLY)) < 0) { + perror("open"); + return -1; + } + + /* Grab the file length */ + len = lseek(fd, 0, SEEK_END); + lseek(fd, 0, SEEK_SET); + + /* Map the file */ + mem = mmap( + NULL, + len, + PROT_READ, + MAP_SHARED, + fd, + 0 + ); + + if (mem == NULL) { + perror("mmap"); + close(fd); + return -1; + } + + SHA256(mem, len, hash); + for (i = 0; i < SHA256_DIGEST_LENGTH; ++i) { + sprintf((char *)hashres + i*2, "%02X", hash[i]); + } + return 0; +} |
