summaryrefslogtreecommitdiff
path: root/packer/rdar.c
diff options
context:
space:
mode:
authorChloe M <chloe@faracom.org>2026-08-19 21:54:30 -0500
committerChloe M <chloe@faracom.org>2026-08-19 21:55:30 -0500
commit6a65f0c024c56caa0a9f81db42f4881be6838f1d (patch)
tree990f0bbcd16763188952204ed935be74cfe45a10 /packer/rdar.c
parent291945f30496d2c41960b98c75e61871358f40a2 (diff)
project: core -> packer
Signed-off-by: Chloe M <chloe@faracom.org>
Diffstat (limited to 'packer/rdar.c')
-rw-r--r--packer/rdar.c453
1 files changed, 453 insertions, 0 deletions
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;
+}