diff options
| author | Chloe M <chloe@faracom.org> | 2026-08-19 21:54:30 -0500 |
|---|---|---|
| committer | Chloe M <chloe@faracom.org> | 2026-08-19 21:55:30 -0500 |
| commit | 6a65f0c024c56caa0a9f81db42f4881be6838f1d (patch) | |
| tree | 990f0bbcd16763188952204ed935be74cfe45a10 /packer | |
| parent | 291945f30496d2c41960b98c75e61871358f40a2 (diff) | |
project: core -> packer
Signed-off-by: Chloe M <chloe@faracom.org>
Diffstat (limited to 'packer')
| -rw-r--r-- | packer/.bop/posthook.sh | 1 | ||||
| -rw-r--r-- | packer/rdar.c | 453 |
2 files changed, 454 insertions, 0 deletions
diff --git a/packer/.bop/posthook.sh b/packer/.bop/posthook.sh new file mode 100644 index 0000000..42df38b --- /dev/null +++ b/packer/.bop/posthook.sh @@ -0,0 +1 @@ +clang packer/*.c -o rdar diff --git a/packer/rdar.c b/packer/rdar.c new file mode 100644 index 0000000..ba67c69 --- /dev/null +++ b/packer/rdar.c @@ -0,0 +1,453 @@ +/* + * Copyright (c) 2026, Chloe M. + * Provided under the BSD-3 clause. + */ + +#define _DEFAULT_SOURCE +#include <sys/stat.h> +#include <sys/mman.h> +#include <sys/queue.h> +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdint.h> +#include <stddef.h> +#include <stdlib.h> +#include <stdbool.h> +#include <dirent.h> +#include <string.h> + +/* Forward declarations */ +struct idata_desc; + +/* Data descriptor constants */ +#define RDAR_MAX_PATHLEN 32 +#define RDAR_MAGIC ">RDAR!" +#define RDAR_MAGIC_LEN 6 + +/* Data descriptor file flags */ +#define DATA_ROOT (1 << 0) /* Is root data descriptor */ +#define DATA_AVL (1 << 1) /* Available to consumer software; not touched by us */ + +/* Version conversion macros */ +#define VERSION_U16(MAJOR, MINOR) \ + (((MINOR) << 8) | (MAJOR)) +#define VERSION_MAJOR(VERSION) \ + ((VERSION) & 0xFF) +#define VERSION_MINOR(VERSION) \ + (((VERSION) >> 8) & 0xFF) + +/* Pushes idata descriptor to table */ +#define IDATA_PUSH(IDATA_P) \ + ++d_desc_count; \ + TAILQ_INSERT_TAIL(&d_desc_table, (IDATA_P), link); + +/* + * Software version + * + * XXX: We encode it as a U16 so that we may bundle it + * with the file data as binary. + */ +#define RDAR_VERSION VERSION_U16(1, 0) /* v1.0 */ + +/* Fallback path if '-o' is not specified */ +#define FALLBACK_OUTPUT_PATH "out.rdar" + +/* Globals */ +static const char *input_dir = NULL; +static const char *output_path = NULL; +static TAILQ_HEAD(, idata_desc) d_desc_table; +static size_t d_desc_count = 0; + +/* + * The root data region (RDR) contains information about the + * entire archive. + * + * @desc_count: Number of descriptors + * @revision: Format revision + */ +struct root_data_region { + size_t desc_count; + uint16_t revision; +}; + +/* + * Represents a file that has been mapped into memory + * + * @data: File data + * @length: Length of data + */ +struct file_view { + char *data; + size_t length; +}; + +/* + * A data descriptor holds information about a specific + * file along with an offset to which it is located at + * in the offset. + * + * @magic: Format magic + * @path: File path + * @off: File data offset + * @flags: File flags + * @length: File length in bytes + */ +struct data_desc { + char magic[RDAR_MAGIC_LEN]; + char path[RDAR_MAX_PATHLEN]; + off_t off; + uint16_t flags; + size_t length; +}; + +/* + * Internal data descriptor, used to hold data + * before being flushed to the archive + * + * @data: Actual data descriptor + * @file: File view this descriptor references + * @link: TAILQ link + */ +struct idata_desc { + struct data_desc data; + struct file_view file; + TAILQ_ENTRY(idata_desc) link; +}; + +static void +help(void) +{ + printf("usage: ./rdar <... flags>\n"); + printf("[-h] Display this help menu\n"); + printf("[-v] Display the program version\n"); + printf("[-i] Input directory to pack\n"); + printf("[-o] Output path for archive file\n"); +} + +static void +version(void) +{ + uint16_t Major, Minor; + + Major = VERSION_MAJOR(RDAR_VERSION); + Minor = VERSION_MINOR(RDAR_VERSION); + printf("Version v%d.%d\n", Major, Minor); +} + +/* + * Destroy a file view + * + * @view: View to destory + */ +static void +view_destroy(struct file_view *view) +{ + munmap(view->data, view->length); + view->data = NULL; + view->length = 0; +} + +/* + * Create a mapped file view from a file path + */ +static int +view_from_path(const char *path, struct file_view *result) +{ + struct stat sb; + int fd; + + if (path == NULL || result == NULL) { + return -1; + } + + fd = open(path, O_RDONLY); + if (fd < 0) { + printf("fatal: failed to open '%s'\n", path); + perror("open"); + return -1; + } + + if (stat(path, &sb) < 0) { + printf("fatal: failed to stat '%s'\n", path); + perror("stat"); + close(fd); + return -1; + } + + result->data = mmap( + NULL, + sb.st_size, + PROT_READ, + MAP_SHARED, + fd, + 0 + ); + + if (result->data == NULL) { + printf("fatal: failed to map '%s'\n", path); + perror("mmap"); + close(fd); + return -1; + } + + result->length = sb.st_size; + close(fd); + return 0; +} + +/* + * Allocate an internal data descriptor + * + * @path: Path of file + * @is_root: If set, don't create a view + */ +static struct idata_desc * +idata_from_file(const char *path, bool is_root) +{ + struct idata_desc *idata_desc; + struct data_desc *data_desc; + size_t path_len; + const char *p; + + path_len = strlen(path); + if (path_len >= RDAR_MAX_PATHLEN - 1) { + printf("fatal: path '%s' is too long!\n", path); + return NULL; + } + + idata_desc = malloc(sizeof(*idata_desc)); + if (idata_desc == NULL) { + printf("fatal: failed to allocate idata_desc\n"); + printf("* last file processed: %s\n", path); + return NULL; + } + + if (!is_root) { + if (view_from_path(path, &idata_desc->file) < 0) { + free(idata_desc); + return NULL; + } + } + + p = path; + + /* Strip leading path stuff */ + while (*p == '.' && *p != '\0') + ++p; + while (*p == '/' && *p != '\0') + ++p; + + /* Initialize the data descriptor */ + data_desc = &idata_desc->data; + memcpy(data_desc->magic, RDAR_MAGIC, RDAR_MAGIC_LEN); + memcpy(data_desc->path, p, strlen(p)); + data_desc->path[path_len] = '\0'; + data_desc->flags = 0; + data_desc->off = 0; + return idata_desc; +} + +/* + * Push the root descriptor to the table. + * + * The root descriptor is to contain an offset into a special + * %root% file entry which contains data (e.g., version, file count) + * encoded within. + */ +static int +idata_push_root(void) +{ + struct idata_desc *root; + struct data_desc *desc; + + root = idata_from_file("%root%", true); + if (root == NULL) { + return -1; + } + + desc = &root->data; + desc->length = sizeof(struct root_data_region); + desc->flags = DATA_ROOT; + IDATA_PUSH(root); + return 0; +} + +static void +idata_table_destroy(void) +{ + struct idata_desc *desc, *next; + + desc = TAILQ_FIRST(&d_desc_table); + while (desc != NULL) { + view_destroy(&desc->file); + next = TAILQ_NEXT(desc, link); + TAILQ_REMOVE(&d_desc_table, desc, link); + desc = next; + } +} + +static void +flush_root_desc(struct data_desc *d_desc, int out_fd) +{ + struct root_data_region rdr = { + .desc_count = d_desc_count, + .revision = RDAR_VERSION + }; + + d_desc->off = lseek(out_fd, 0, SEEK_CUR); + write(out_fd, &rdr, sizeof(rdr)); +} + +static void +idata_table_flush(void) +{ + const struct file_view *view; + struct idata_desc *desc_iter; + struct data_desc *d_desc; + off_t new_off; + int fd; + + fd = open(output_path, O_WRONLY | O_TRUNC | O_CREAT, 0666); + if (fd < 0) { + printf("fatal: failed to open output path\n"); + perror("open"); + return; + } + + new_off = d_desc_count * sizeof(struct data_desc); + lseek(fd, new_off, SEEK_SET); + + /* First pass write the file data */ + TAILQ_FOREACH(desc_iter, &d_desc_table, link) { + view = &desc_iter->file; + d_desc = &desc_iter->data; + + /* + * Check if we need to flush the root descriptor, we ignore any files that start + * with the '%' modifier. + */ + if (d_desc->path[0] == '%') { + if (strcmp(d_desc->path, "%root%") == 0) + flush_root_desc(d_desc, fd); + + continue; + } + + d_desc->off = lseek(fd, 0, SEEK_CUR); + d_desc->length = view->length; + write(fd, view->data, view->length); + } + + /* Data descriptors go at the start */ + lseek(fd, 0, SEEK_SET); + + /* Second pass write the data descriptors */ + TAILQ_FOREACH(desc_iter, &d_desc_table, link) { + view = &desc_iter->file; + d_desc = &desc_iter->data; + write(fd, d_desc, sizeof(*d_desc)); + } + + close(fd); +} + +static void +recurse_dir(const char *dirpath) +{ + DIR *dir; + struct dirent *dirent; + struct idata_desc *idata_desc; + char pathbuf[264]; + + dir = opendir(dirpath); + if (dir == NULL) { + printf("fatal: failed to open '%s'\n", input_dir); + perror("opendir"); + return; + } + + while ((dirent = readdir(dir)) != NULL) { + if (dirent->d_name[0] == '.') { + continue; + } + + switch (dirent->d_type) { + case DT_REG: + snprintf(pathbuf, sizeof(pathbuf), "%s/%s", dirpath, dirent->d_name); + printf("[f] %s\n", pathbuf); + + idata_desc = idata_from_file(pathbuf, false); + if (idata_desc == NULL) { + printf("warn: ignoring '%s'\n", pathbuf); + break; + } + + IDATA_PUSH(idata_desc); + break; + case DT_DIR: + snprintf(pathbuf, sizeof(pathbuf), "%s/%s", dirpath, dirent->d_name); + printf("[d] %s\n", pathbuf); + recurse_dir(pathbuf); + break; + } + } + + closedir(dir); +} + +int +main(int argc, char **argv) +{ + int opt; + + if (argc < 2) { + printf("fatal: too few arguments\n"); + help(); + return -1; + } + + while ((opt = getopt(argc, argv, "hvi:o:")) != -1) { + switch (opt) { + case 'h': + help(); + return -1; + case 'v': + version(); + return -1; + case 'i': + input_dir = strdup(optarg); + if (input_dir == NULL) { + printf("fatal: failed to allocate input_dir\n"); + return -1; + } + + break; + case 'o': + output_path = strdup(optarg); + if (output_path == NULL) { + printf("fatal: failed to allocate output dir\n"); + return -1; + } + + break; + } + } + + if (input_dir == NULL) { + printf("fatal: input path not specified\n"); + help(); + return -1; + } + + if (output_path == NULL) { + output_path = FALLBACK_OUTPUT_PATH; + } + + TAILQ_INIT(&d_desc_table); + idata_push_root(); + recurse_dir(input_dir); + idata_table_flush(); + idata_table_destroy(); + return 0; +} |
