6d53d19b2d
Signed-off-by: Chloe M. <chloe@mensia.org>
348 lines
6.8 KiB
C
348 lines
6.8 KiB
C
/*
|
|
* Copyright (c) 2026, Chloe M.
|
|
* Provided under the BSD-3 clause.
|
|
*
|
|
* Description: CUMHOLE pre-boot image format
|
|
* Author: Chloe M.
|
|
*/
|
|
|
|
#define _DEFAULT_SOURCE
|
|
#include <sys/mman.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <sys/queue.h>
|
|
#include <stdio.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <dirent.h>
|
|
#include <stdbool.h>
|
|
|
|
#define REF_MAGIC "CUMHOLE"
|
|
#define REF_MAGIC_LEN 8
|
|
#define REF_PATH_LEN 256
|
|
|
|
/* Set to 1 for verbose */
|
|
#define DEBUG 0
|
|
|
|
#if DEBUG
|
|
#define DTRACE(fmt, ...) printf(fmt, ##__VA_ARGS__)
|
|
#else
|
|
#define DTRACE(...) (void)0
|
|
#endif
|
|
|
|
/*
|
|
* A reference is an entry within the table that refers
|
|
* to a block of data after the reference table.
|
|
*
|
|
* @magic: Reference magic
|
|
* @path: File path
|
|
* @size: File size
|
|
* @data_off: Data offset
|
|
* @real_path: Real path
|
|
* @link: Queue link
|
|
*
|
|
* XXX: Do not reorder anything ABOVE 'link'
|
|
*/
|
|
struct hole_ref {
|
|
char magic[REF_MAGIC_LEN];
|
|
char path[REF_PATH_LEN];
|
|
size_t size;
|
|
off_t data_off;
|
|
char *real_path;
|
|
TAILQ_ENTRY(hole_ref) link;
|
|
};
|
|
|
|
/* Globals */
|
|
static bool output_override = false;
|
|
static char *output_path = "out.hole";
|
|
static char *fill_dirpath = NULL;
|
|
static size_t file_count = 0;
|
|
static TAILQ_HEAD(, hole_ref) hole_files;
|
|
|
|
/*
|
|
* Display the help options
|
|
*/
|
|
static void
|
|
help(void)
|
|
{
|
|
printf("usage: ./hole [flags]\n");
|
|
printf("[-h] Display this help menu\n");
|
|
printf("[-f] Fill hole with directory\n");
|
|
printf("[-o] Output blob path\n");
|
|
}
|
|
|
|
/*
|
|
* Destroy the hole files table
|
|
*/
|
|
static void
|
|
hole_files_destroy(void)
|
|
{
|
|
struct hole_ref *ref, *tmp;
|
|
|
|
while ((ref = TAILQ_FIRST(&hole_files)) != NULL) {
|
|
DTRACE("delete %s\n", ref->real_path);
|
|
free(ref->real_path);
|
|
|
|
tmp = ref;
|
|
TAILQ_REMOVE(&hole_files, ref, link);
|
|
free(tmp);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Used to fix up offsets after
|
|
*/
|
|
static void
|
|
hole_table_fixup(void)
|
|
{
|
|
struct stat sb;
|
|
struct hole_ref *ref;
|
|
size_t table_end, offset;
|
|
size_t hdr_size;
|
|
|
|
hdr_size = sizeof(struct hole_ref);
|
|
hdr_size -= sizeof(ref->real_path);
|
|
hdr_size -= sizeof(ref->link);
|
|
table_end = file_count * hdr_size;
|
|
offset = table_end;
|
|
|
|
printf("* fixing up offsets...\n");
|
|
printf("* table end @ %zx\n", table_end);
|
|
|
|
TAILQ_FOREACH(ref, &hole_files, link) {
|
|
if (stat(ref->real_path, &sb) < 0) {
|
|
perror("stat");
|
|
continue;
|
|
}
|
|
|
|
ref->size = sb.st_size;
|
|
ref->data_off = offset;
|
|
|
|
DTRACE("data_off=%zx\n", ref->data_off);
|
|
offset += ref->size;
|
|
}
|
|
}
|
|
|
|
static void
|
|
blob_write(void)
|
|
{
|
|
struct hole_ref *ref;
|
|
int fd, ffd;
|
|
void *data;
|
|
size_t fsize, hdr_len;
|
|
|
|
fd = open(output_path, O_RDWR | O_CREAT | O_TRUNC, 0777);
|
|
if (fd < 0) {
|
|
perror("open");
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* PASS 1):
|
|
* We are to write the header of each file into the
|
|
* top of the blob.
|
|
*/
|
|
TAILQ_FOREACH(ref, &hole_files, link) {
|
|
hdr_len = sizeof(*ref);
|
|
hdr_len -= sizeof(ref->real_path);
|
|
hdr_len -= sizeof(ref->link);
|
|
write(fd, ref, hdr_len);
|
|
}
|
|
|
|
/*
|
|
* PASS 2):
|
|
* Now we can write the contents of each file after
|
|
*/
|
|
TAILQ_FOREACH(ref, &hole_files, link) {
|
|
ffd = open(ref->real_path, O_RDONLY);
|
|
if (ffd < 0) {
|
|
perror("open[ffd]");
|
|
continue;
|
|
}
|
|
|
|
/* Obtain the file size */
|
|
fsize = lseek(ffd, 0, SEEK_END);
|
|
lseek(ffd, 0, SEEK_SET);
|
|
|
|
data = mmap(
|
|
NULL,
|
|
fsize,
|
|
PROT_READ,
|
|
MAP_SHARED,
|
|
ffd,
|
|
0
|
|
);
|
|
|
|
if (data == NULL) {
|
|
close(ffd);
|
|
perror("mmap");
|
|
continue;
|
|
}
|
|
|
|
lseek(fd, SEEK_SET, ref->data_off);
|
|
write(fd, data, fsize);
|
|
munmap(data, fsize);
|
|
close(ffd);
|
|
}
|
|
|
|
close(fd);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Strip a path of leading '.' and '/' characters
|
|
*
|
|
* @path: Path to strip
|
|
*/
|
|
static const char *
|
|
path_strip(const char *path)
|
|
{
|
|
const char *p;
|
|
|
|
if (path == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
p = path;
|
|
while (*p == '.' || *p == '/') {
|
|
++p;
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
/*
|
|
* Push a file to the hole files table
|
|
*/
|
|
static int
|
|
push_file(const char *path)
|
|
{
|
|
struct hole_ref *ref;
|
|
const char *stripped;
|
|
|
|
if (path == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
ref = malloc(sizeof(*ref));
|
|
if (ref == NULL) {
|
|
printf("fatal: could not allocate ref\n");
|
|
hole_files_destroy();
|
|
return -1;
|
|
}
|
|
|
|
ref->real_path = strdup(path);
|
|
stripped = path_strip(path);
|
|
|
|
memcpy(ref->magic, REF_MAGIC, sizeof(REF_MAGIC));
|
|
memcpy(ref->path, stripped, strlen(stripped) + 1);
|
|
ref->data_off = 0;
|
|
|
|
TAILQ_INSERT_TAIL(&hole_files, ref, link);
|
|
++file_count;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Scan a directory to fill the blob
|
|
*/
|
|
static void
|
|
scan_dir(const char *path)
|
|
{
|
|
DIR *dir;
|
|
struct dirent *dirent;
|
|
char pathbuf[300];
|
|
|
|
if (path == NULL) {
|
|
return;
|
|
}
|
|
|
|
dir = opendir(path);
|
|
if (dir == NULL) {
|
|
perror("opendir");
|
|
return;
|
|
}
|
|
|
|
while ((dirent = readdir(dir)) != NULL) {
|
|
if (dirent->d_name[0] == '.') {
|
|
continue;
|
|
}
|
|
|
|
snprintf(pathbuf, sizeof(pathbuf), "%s/%s", path, dirent->d_name);
|
|
switch (dirent->d_type) {
|
|
case DT_REG:
|
|
printf("[f] %s\n", pathbuf);
|
|
if (push_file(pathbuf) < 0) {
|
|
free(fill_dirpath);
|
|
exit(1);
|
|
}
|
|
break;
|
|
case DT_DIR:
|
|
printf("[d] %s\n", pathbuf);
|
|
scan_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, "hf:o:")) != -1) {
|
|
switch (opt) {
|
|
case 'h':
|
|
help();
|
|
return -1;
|
|
case 'f':
|
|
fill_dirpath = strdup(optarg);
|
|
if (fill_dirpath == NULL) {
|
|
printf("fatal: out of memory\n");
|
|
return -1;
|
|
}
|
|
|
|
break;
|
|
case 'o':
|
|
output_override = true;
|
|
output_path = strdup(optarg);
|
|
if (output_path == NULL) {
|
|
printf("fatal: out of memory\n");
|
|
return -1;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (fill_dirpath == NULL) {
|
|
printf("fatal: expected fill directory for the slutty blob...\n");
|
|
help();
|
|
return -1;
|
|
}
|
|
|
|
TAILQ_INIT(&hole_files);
|
|
scan_dir(fill_dirpath);
|
|
|
|
hole_table_fixup();
|
|
blob_write();
|
|
|
|
hole_files_destroy();
|
|
free(fill_dirpath);
|
|
|
|
if (output_override) {
|
|
free(output_path);
|
|
}
|
|
|
|
return 0;
|
|
}
|