/* * Copyright (c) 2026, Chloe M. * Provided under the BSD-3 clause. */ #define _DEFAULT_SOURCE #include #include #include #include #include #include #include #include /* 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) \ 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; /* * 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 */ struct data_desc { char magic[RDAR_MAGIC_LEN]; char path[RDAR_MAX_PATHLEN]; off_t off; uint16_t flags; }; /* * Internal data descriptor, used to hold data * before being flushed to the archive * * @data: Actual data descriptor * @link: TAILQ link */ struct idata_desc { struct data_desc data; 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); } /* * Allocate an internal data descriptor * * @path: Path of file */ static struct idata_desc * idata_from_file(const char *path) { struct idata_desc *idata_desc; struct data_desc *data_desc; size_t path_len; 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; } /* Initialize the data descriptor */ data_desc = &idata_desc->data; memcpy(data_desc->magic, RDAR_MAGIC, RDAR_MAGIC_LEN); memcpy(data_desc->path, path, path_len); 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; root = idata_from_file("%root%"); if (root == NULL) { return -1; } IDATA_PUSH(root); return 0; } static void idata_table_destroy(void) { struct idata_desc *desc, *next; printf("* dumping idata_table\n"); desc = TAILQ_FIRST(&d_desc_table); while (desc != NULL) { printf("-- %s\n", desc->data.path); next = TAILQ_NEXT(desc, link); TAILQ_REMOVE(&d_desc_table, desc, link); desc = next; } } 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); 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_destroy(); return 0; }