From 265b4ba574df9abcf80b28391a6cd25f0688e6fa Mon Sep 17 00:00:00 2001 From: Max Wash Date: Fri, 4 Jul 2025 15:37:31 +0100 Subject: [PATCH] builder: implement a high-level interface for building images --- src/builder.c | 603 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/builder.h | 52 +++++ 2 files changed, 655 insertions(+) create mode 100644 src/builder.c create mode 100644 src/builder.h diff --git a/src/builder.c b/src/builder.c new file mode 100644 index 0000000..d852103 --- /dev/null +++ b/src/builder.c @@ -0,0 +1,603 @@ +#include "builder.h" + +#include "fs-tree.h" +#include "image.h" +#include "tag.h" +#include "volume.h" + +#include +#include +#include +#include +#include + +struct capture_directory_ctx { + struct ec3_image_builder *ctx_builder; + struct ec3_volume *ctx_volume; + struct fs_tree ctx_tree; +}; + +static enum ec3_status init_string_table(struct ec3_image_builder *builder) +{ + if (builder->b_flags & EC3_IMAGE_BUILDER_STRING_TABLE) { + return EC3_SUCCESS; + } + + uint64_t stab_id; + ec3_identifier_from_string("_VOLSTR0", &stab_id); + + enum ec3_status status = ec3_image_ioctx_create_tag( + builder->b_image, + EC3_TAG_STAB, + stab_id, + EC3_TAG_IO_WRITE | EC3_TAG_IO_SEQUENTIAL, + &builder->b_stab); + if (status != EC3_SUCCESS) { + return status; + } + + string_table_init(&builder->b_strings); + builder->b_flags |= EC3_IMAGE_BUILDER_STRING_TABLE; + + return EC3_SUCCESS; +} + +static enum ec3_status init_chunk_table(struct ec3_image_builder *builder) +{ + if (builder->b_flags & EC3_IMAGE_BUILDER_CHUNK_TABLE) { + return EC3_SUCCESS; + } + + uint64_t ctab_id, cdat_id; + ec3_identifier_from_string("_CHKTAB0", &ctab_id); + ec3_identifier_from_string("_CHKDAT0", &cdat_id); + + enum ec3_status status = ec3_image_ioctx_create_tag( + builder->b_image, + EC3_TAG_CTAB, + ctab_id, + EC3_TAG_IO_READWRITE, + &builder->b_ctab); + if (status != EC3_SUCCESS) { + return status; + } + + status = ec3_image_ioctx_create_tag( + builder->b_image, + EC3_TAG_CDAT, + cdat_id, + EC3_TAG_IO_READWRITE, + &builder->b_cdat); + if (status != EC3_SUCCESS) { + ec3_tag_ioctx_close(builder->b_ctab); + builder->b_ctab = NULL; + return status; + } + + status = chunk_table_init( + builder->b_ctab, + builder->b_cdat, + builder->b_param.p_cluster_size, + &builder->b_chunks); + if (status != EC3_SUCCESS) { + ec3_tag_ioctx_close(builder->b_ctab); + ec3_tag_ioctx_close(builder->b_cdat); + return status; + } + + chunk_table_init_empty_table(&builder->b_chunks); + + builder->b_flags |= EC3_IMAGE_BUILDER_CHUNK_TABLE; + + return EC3_SUCCESS; +} + +static enum ec3_status capture_directory_file( + struct capture_directory_ctx *ctx, + b_directory *dir, + const char *filename, + const b_path *filepath, + ec3_chunk_id out_chunk) +{ + const struct ec3_image_info *image_info + = ec3_image_ioctx_get_info(ctx->ctx_builder->b_image); + size_t key = string_table_get(&ctx->ctx_builder->b_strings, filename); + size_t buf_len = image_info->img_cluster_size; + char *buf = malloc(buf_len); + + if (!buf) { + return EC3_ERR_NO_MEMORY; + } + + b_file *src = NULL; + b_status status = b_file_open( + dir, + filepath, + B_FILE_READ_ONLY | B_FILE_BINARY, + &src); + if (!B_OK(status)) { + free(buf); + return ec3_status_from_b_status(status, EC3_ERR_NO_ENTRY); + } + + enum ec3_status s2 = EC3_SUCCESS; + chunk_table_begin_chunk(&ctx->ctx_builder->b_chunks); + + size_t chunk_size = 0; + + while (1) { + size_t nr_read = 0; + status = b_file_read( + src, + B_OFFSET_CURRENT, + buf_len, + buf, + &nr_read); + + if (!B_OK(status)) { + s2 = ec3_status_from_b_status( + status, + EC3_ERR_IO_FAILURE); + break; + } + + enum ec3_status s2 = chunk_table_put( + &ctx->ctx_builder->b_chunks, + buf, + nr_read); + + if (s2 != EC3_SUCCESS) { + break; + } + + chunk_size += nr_read; + + if (nr_read < buf_len) { + break; + } + } + + s2 = chunk_table_end_chunk(&ctx->ctx_builder->b_chunks, out_chunk); + + if (s2 != EC3_SUCCESS) { + return s2; + } + + char id_str[128]; + ec3_chunk_id_to_string(out_chunk, id_str, sizeof id_str); + + free(buf); + b_file_release(src); + return s2; +} + +struct capture_directory_structure_args { + b_list *args_stack; + struct capture_directory_ctx *args_ctx; +}; + +static int capture_directory_structure_callback( + struct fs_tree *tree, + struct fs_tree_node *node, + unsigned int depth, + int direction, + void *arg) +{ + if (direction != ITERATE_POSTORDER) { + return 0; + } + + struct capture_directory_structure_args *args = arg; + b_list *stack = args->args_stack; + struct capture_directory_ctx *ctx = args->args_ctx; + + if (node->n_type == FS_TREE_FILE) { + b_list_push_back(stack, node); + return 0; + } + + size_t nr_children = fs_tree_node_get_nr_children(node); + struct ec3_directory_entry dent; + enum ec3_status status = EC3_SUCCESS; + + chunk_table_begin_chunk(&ctx->ctx_builder->b_chunks); + for (size_t i = 0; i < nr_children; i++) { + struct fs_tree_node *child = b_list_pop_back(stack); + if (!child) { + return EC3_ERR_INTERNAL_FAILURE; + } + + dent.d_name = b_i32_htob(string_table_get( + &ctx->ctx_builder->b_strings, + child->n_name)); + dent.d_vnode = b_i32_htob(child->n_id); + + status = chunk_table_put( + &ctx->ctx_builder->b_chunks, + &dent, + sizeof dent); + } + + status = chunk_table_end_chunk( + &ctx->ctx_builder->b_chunks, + node->n_chunk); + if (status != EC3_SUCCESS) { + return status; + } + + char id_str[128]; + ec3_chunk_id_to_string(node->n_chunk, id_str, sizeof id_str); +#if 0 + printf("[d %s / %zu] wrote %zu byte chunk %s\n", + node->n_name, + node->n_id, + nr_children * sizeof dent, + id_str); +#endif + + struct ec3_vnode vnode = { + .v_id = node->n_id, + .v_mode = EC3_V_DIR, + }; + + memcpy(vnode.v_data, node->n_chunk, sizeof vnode.v_data); + + ec3_volume_put_vnode(ctx->ctx_volume, &vnode); + + b_list_push_back(stack, node); + + return 0; +} + +static enum ec3_status capture_directory_structure( + struct capture_directory_ctx *ctx) +{ + b_list *stack = b_list_create(); + + struct capture_directory_structure_args args = { + .args_stack = stack, + .args_ctx = ctx, + }; + + fs_tree_iterate( + &ctx->ctx_tree, + capture_directory_structure_callback, + &args); + + return EC3_SUCCESS; +} + +enum ec3_status ec3_image_builder_capture_directory( + struct ec3_image_builder *builder, + uint64_t id, + const char *directory_path) +{ + enum ec3_status status2 = init_string_table(builder); + if (status2 != EC3_SUCCESS) { + return status2; + } + + status2 = init_chunk_table(builder); + if (status2 != EC3_SUCCESS) { + return status2; + } + + b_path *path = b_path_create_from_cstr(directory_path); + b_directory *dir; + b_status status = b_directory_open(NULL, path, &dir); + b_path_release(path); + + if (!B_OK(status)) { + return EC3_ERR_NO_ENTRY; + } + + struct ec3_tag_ioctx *volu; + enum ec3_status s2 = ec3_image_ioctx_create_tag( + builder->b_image, + EC3_TAG_VOLU, + id, + EC3_TAG_IO_READ | EC3_TAG_IO_WRITE, + &volu); + if (s2 != EC3_SUCCESS) { + b_directory_release(dir); + return s2; + } + + struct capture_directory_ctx ctx = {.ctx_builder = builder}; + + s2 = ec3_volume_create(builder->b_image, volu, &ctx.ctx_volume); + + if (s2 != EC3_SUCCESS) { + ec3_tag_ioctx_close(volu); + b_directory_release(dir); + return s2; + } + + fs_tree_init(&ctx.ctx_tree); + + struct ec3_vnode vnode = {0}; + vnode.v_id = ctx.ctx_tree.fs_root->n_id; + ec3_volume_put_vnode(ctx.ctx_volume, &vnode); + + status2 = EC3_SUCCESS; + b_directory_iterator it; + b_directory_iterator_begin(dir, &it, B_DIRECTORY_ITERATE_PARENT_LAST); + while (b_directory_iterator_is_valid(&it)) { + if (!b_directory_path_is_file(dir, it.filepath)) { + printf("dir: %s\n", b_path_ptr(it.filepath)); + b_directory_iterator_next(&it); + continue; + } + + ec3_vnode_from_file_info(&it.info, &vnode); + + printf("file: %s\n", b_path_ptr(it.filepath)); + + status2 = capture_directory_file( + &ctx, + dir, + it.filename, + it.filepath, + vnode.v_data); + + if (status2 != EC3_SUCCESS) { +#if 0 + b_err("failed to capture file '%s'", + b_path_ptr(it.filepath)); + b_i("error code: %s", ec3_status_to_string(status2)); +#endif + break; + } + + fs_tree_put( + &ctx.ctx_tree, + b_path_ptr(it.filepath), + &vnode.v_id, + vnode.v_data); + + ec3_volume_put_vnode(ctx.ctx_volume, &vnode); + + b_directory_iterator_next(&it); + } + + // fs_tree_iterate(&ctx->ctx_tree, print_fs_tree_node, NULL); + + capture_directory_structure(&ctx); + fs_tree_fini(&ctx.ctx_tree); + + ec3_tag_ioctx_close(volu); + + b_directory_release(dir); + return status2; +} + +enum ec3_status ec3_image_builder_create( + const char *path, + const struct ec3_parameters *params, + struct ec3_image_builder **out) +{ + struct ec3_image_builder *builder = malloc(sizeof *builder); + if (!builder) { + return EC3_ERR_NO_MEMORY; + } + + memset(builder, 0x0, sizeof *builder); + + b_path *image_path = b_path_create_from_cstr(path); + if (b_path_exists(image_path)) { + b_path_unlink(image_path); + } + b_path_release(image_path); + + enum ec3_status status = ec3_image_ioctx_open( + path, + params, + EC3_IMAGE_IO_WRITE, + &builder->b_image); + if (status != EC3_SUCCESS) { + ec3_image_builder_destroy(builder); + return status; + } + + memcpy(&builder->b_param, params, sizeof *params); + *out = builder; + + return EC3_SUCCESS; +} + +static enum ec3_status flush_strings(struct ec3_image_builder *builder) +{ + enum ec3_status status = EC3_SUCCESS; + + b_btree_iterator s_it; + b_btree_iterator_begin(&builder->b_strings.s_offset_tree, &s_it); + while (b_btree_iterator_is_valid(&s_it)) { + struct string_table_entry *entry = b_unbox( + struct string_table_entry, + s_it.node, + e_offset_node); + + size_t len = strlen(entry->e_str) + 1; + size_t nr_written = 0; + status = ec3_tag_ioctx_write( + builder->b_stab, + entry->e_str, + len, + &nr_written); + b_btree_iterator_next(&s_it); + + if (status != EC3_SUCCESS) { + break; + } + } + + return status; +} + +enum ec3_status ec3_image_builder_destroy(struct ec3_image_builder *builder) +{ + enum ec3_status status = EC3_SUCCESS; + if (builder->b_flags & EC3_IMAGE_BUILDER_STRING_TABLE) { + status = flush_strings(builder); + string_table_finish(&builder->b_strings); + ec3_tag_ioctx_close(builder->b_stab); + } + + /* TODO propagate status code from flush_strings */ + + if (builder->b_flags & EC3_IMAGE_BUILDER_CHUNK_TABLE) { + chunk_table_finish(&builder->b_chunks); + ec3_tag_ioctx_close(builder->b_ctab); + ec3_tag_ioctx_close(builder->b_cdat); + } + + status = ec3_image_ioctx_close(builder->b_image); + free(builder); + + return status; +} + +enum ec3_status ec3_image_builder_add_blob_from_file( + struct ec3_image_builder *builder, + uint64_t id, + FILE *fp) +{ + size_t cluster_size + = ec3_cluster_size_id_to_bytes(builder->b_param.p_cluster_size); + char *buf = malloc(cluster_size); + if (!buf) { + return EC3_ERR_NO_MEMORY; + } + + struct ec3_tag_ioctx *tag = NULL; + enum ec3_status status = ec3_image_ioctx_create_tag( + builder->b_image, + EC3_TAG_BLOB, + id, + EC3_TAG_IO_WRITE | EC3_TAG_IO_SEQUENTIAL, + &tag); + + if (status != EC3_SUCCESS) { + free(buf); + return status; + } + + size_t i = 0; + + while (1) { + size_t r = fread(buf, 1, cluster_size, fp); + if (r == 0) { + break; + } + + size_t w; + status = ec3_tag_ioctx_write_cluster(tag, i++, buf, r, &w); + + if (r < cluster_size) { + break; + } + } + + free(buf); + ec3_tag_ioctx_close(tag); + + return EC3_SUCCESS; +} + +enum ec3_status ec3_image_builder_add_blob_from_buffer( + struct ec3_image_builder *builder, + uint64_t id, + const void *buf, + size_t len) +{ + size_t cluster_size + = ec3_cluster_size_id_to_bytes(builder->b_param.p_cluster_size); + + struct ec3_tag_ioctx *tag = NULL; + enum ec3_status status = ec3_image_ioctx_create_tag( + builder->b_image, + EC3_TAG_BLOB, + id, + EC3_TAG_IO_WRITE | EC3_TAG_IO_SEQUENTIAL, + &tag); + + if (status != EC3_SUCCESS) { + return status; + } + + size_t i = 0; + const unsigned char *bytes = buf; + size_t remaining = len; + + while (remaining > 0) { + size_t to_copy = remaining; + if (to_copy > cluster_size) { + to_copy = cluster_size; + } + + size_t w; + status = ec3_tag_ioctx_write_cluster( + tag, + i++, + bytes, + to_copy, + &w); + + remaining -= to_copy; + bytes += to_copy; + } + + ec3_tag_ioctx_close(tag); + + return EC3_SUCCESS; +} + +enum ec3_status ec3_image_builder_add_executable_from_file( + struct ec3_image_builder *builder, + uint64_t id, + const struct ec3_tag_executable_info *exe, + FILE *fp) +{ + size_t cluster_size + = ec3_cluster_size_id_to_bytes(builder->b_param.p_cluster_size); + char *buf = malloc(cluster_size); + if (!buf) { + return EC3_ERR_NO_MEMORY; + } + + struct ec3_tag_ioctx *tag = NULL; + enum ec3_status status = ec3_image_ioctx_create_tag( + builder->b_image, + EC3_TAG_EXEC, + id, + EC3_TAG_IO_WRITE | EC3_TAG_IO_SEQUENTIAL, + &tag); + + if (status != EC3_SUCCESS) { + free(buf); + return status; + } + + size_t i = 0; + + while (1) { + size_t r = fread(buf, 1, cluster_size, fp); + if (r == 0) { + break; + } + + size_t w; + status = ec3_tag_ioctx_write_cluster(tag, i++, buf, r, &w); + + if (r < cluster_size) { + break; + } + } + + memcpy(&tag->io_tag_info.tag_exe, exe, sizeof *exe); + + free(buf); + ec3_tag_ioctx_close(tag); + + return EC3_SUCCESS; +} diff --git a/src/builder.h b/src/builder.h new file mode 100644 index 0000000..19f7b60 --- /dev/null +++ b/src/builder.h @@ -0,0 +1,52 @@ +#ifndef BUILDER_H_ +#define BUILDER_H_ + +#include "chunk-table.h" +#include "image.h" +#include "string-table.h" + +enum ec3_image_builder_flags { + EC3_IMAGE_BUILDER_STRING_TABLE = 0x01u, + EC3_IMAGE_BUILDER_CHUNK_TABLE = 0x02u, +}; + +struct ec3_image_builder { + enum ec3_image_builder_flags b_flags; + struct ec3_parameters b_param; + struct ec3_image_ioctx *b_image; + struct string_table b_strings; + struct chunk_table b_chunks; + + struct ec3_tag_ioctx *b_stab; + struct ec3_tag_ioctx *b_ctab, *b_cdat; +}; + +extern enum ec3_status ec3_image_builder_create( + const char *image_path, + const struct ec3_parameters *params, + struct ec3_image_builder **out); +extern enum ec3_status ec3_image_builder_destroy( + struct ec3_image_builder *builder); + +extern enum ec3_status ec3_image_builder_capture_directory( + struct ec3_image_builder *builder, + uint64_t id, + const char *directory_path); + +extern enum ec3_status ec3_image_builder_add_blob_from_file( + struct ec3_image_builder *builder, + uint64_t id, + FILE *fp); +extern enum ec3_status ec3_image_builder_add_blob_from_buffer( + struct ec3_image_builder *builder, + uint64_t id, + const void *buf, + size_t len); + +extern enum ec3_status ec3_image_builder_add_executable_from_file( + struct ec3_image_builder *builder, + uint64_t id, + const struct ec3_tag_executable_info *exe, + FILE *fp); + +#endif