summaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
authorChloe M. <chloe@aptel.org>2026-08-22 06:48:01 +0000
committerChloe M. <chloe@aptel.org>2026-08-22 09:45:33 +0000
commit7683205c69c76b6515c242429a927974aedfb5c0 (patch)
tree0d6661f646f7789681b99afab5f3eb2eb7ecb7e4 /core
parentf0691430d86a727b4ffee0f6ea3b355baecd6414 (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.sh2
-rw-r--r--core/build.c324
-rw-r--r--core/hash.c54
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(&params, sizeof(params));
- if ((error = build_init_cflags(dirpath, &params)) < 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, &params);
+ 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(&params, sizeof(params));
+ if ((error = build_init_cflags(dirpath, &params)) < 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, &params);
+
+ /* 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, &params);
+ 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;
+}