diff --git a/src/create.c b/src/create.c index 655ee60..efc66dd 100644 --- a/src/create.c +++ b/src/create.c @@ -1,250 +1,461 @@ -#if 0 -#include "b-tree.h" -#include "bin.h" +#include "builder.h" #include "commands.h" +#include "exe.h" +#include "image.h" +#include "substitute.h" #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include -#include - -#define INVALID_NODE_PTR 0xFFFF - -#define ORDER 6 -#define MAX (ORDER - 1) -#define MIN (MAX / 2) enum { - ARG_INPATH, + OPT_SPECIFICATION, + OPT_SPECIFICATION_PATH, + OPT_PARAMETER, + OPT_PARAMETER_NAME, + OPT_PARAMETER_VALUE, OPT_OUTPATH, OPT_OUTPATH_PATH, }; -struct entry { - uint32_t key; - uint32_t value; +struct create_ctx { + struct ec3_image_builder *ctx_builder; + struct substitution_list ctx_sub_list; + b_dict *ctx_specification; }; -struct node { - uint16_t nr_entries; - struct entry entries[MAX]; - uint16_t children[ORDER]; -}; - -struct data_tree { - struct b_tree base; - FILE *fp; -}; - -static int node_init(struct node *n) +static enum ec3_status create_ctx_init( + struct create_ctx *ctx, + const b_arglist *args) { - memset(n, 0x0, sizeof *n); + enum ec3_status status = substitution_list_init(&ctx->ctx_sub_list); - for (int i = 0; i < ORDER; i++) { - n->children[i] = INVALID_NODE_PTR; + b_arglist_option_iterator it; + b_arglist_option_foreach_filtered(&it, args, OPT_PARAMETER) + { + b_arglist_value *name, *value; + b_arglist_option_get_value( + it.opt, + OPT_PARAMETER_NAME, + 0, + &name); + b_arglist_option_get_value( + it.opt, + OPT_PARAMETER_VALUE, + 0, + &value); + + status = substitution_list_add_substitution( + &ctx->ctx_sub_list, + name->val_str, + value->val_str); + + if (status != EC3_SUCCESS) { + return status; + } } - return 0; + return EC3_SUCCESS; } -static int tree_get_node(struct b_tree *p, unsigned long id, b_tree_node *n) +static void create_ctx_finish(struct create_ctx *ctx) { - struct data_tree *tree = (struct data_tree *)p; - size_t offset = (id * sizeof(struct node)); - fseek(tree->fp, offset, SEEK_SET); - size_t r = fread(n, sizeof(struct node), 1, tree->fp); - return r == 1 ? 0 : -1; -} - -static int tree_put_node( - struct b_tree *p, - unsigned long id, - const b_tree_node *n) -{ - struct data_tree *tree = (struct data_tree *)p; - size_t offset = (id * sizeof(struct node)); - fseek(tree->fp, offset, SEEK_SET); - size_t r = fwrite(n, sizeof(struct node), 1, tree->fp); - return r == 1 ? 0 : -1; -} - -static long tree_alloc_node(struct b_tree *p) -{ - struct data_tree *tree = (struct data_tree *)p; - size_t pos = ftell(tree->fp); - fseek(tree->fp, 0, SEEK_END); - size_t len = ftell(tree->fp); - - struct node n; - node_init(&n); - - fwrite(&n, sizeof n, 1, tree->fp); - - fseek(tree->fp, pos, SEEK_SET); - - len /= sizeof(struct node); - - return (long)len; -} - -static unsigned long node_get_nr_entries(b_tree_node *n) -{ - struct node *node = (struct node *)n; - return node->nr_entries; -} - -static void node_set_nr_entries(b_tree_node *n, unsigned long val) -{ - struct node *node = (struct node *)n; - node->nr_entries = val; -} - -static b_tree_node_entry *node_get_entry(b_tree_node *n, unsigned long index) -{ - struct node *node = (struct node *)n; - return (b_tree_node_entry *)&node->entries[index]; -} - -static void node_set_entry( - b_tree_node *n, - unsigned long index, - const b_tree_node_entry *entry) -{ - struct node *node = (struct node *)n; - memmove(&node->entries[index], entry, sizeof(struct entry)); -} - -static unsigned long node_get_child(b_tree_node *n, unsigned long index) -{ - struct node *node = (struct node *)n; - uint16_t child = node->children[index]; - return child == INVALID_NODE_PTR ? B_TREE_INVALID_PTR : child; -} - -static void node_set_child( - b_tree_node *n, - unsigned long index, - unsigned long ptr) -{ - struct node *node = (struct node *)n; - uint16_t child - = ptr == B_TREE_INVALID_PTR ? INVALID_NODE_PTR : (uint16_t)ptr; - node->children[index] = child; -} - -static int entry_compare( - const b_tree_node_entry *e0, - const b_tree_node_entry *e1) -{ - struct entry *a = (struct entry *)e0, *b = (struct entry *)e1; - - if (a->key < b->key) { - return -1; + if (ctx->ctx_builder) { + ec3_image_builder_destroy(ctx->ctx_builder); } - if (a->key > b->key) { - return 1; + if (ctx->ctx_specification) { + b_dict_release(ctx->ctx_specification); } - return 0; + substitution_list_finish(&ctx->ctx_sub_list); } -static const struct b_tree_ops ops = { - .tree_get_node = tree_get_node, - .tree_put_node = tree_put_node, - .tree_alloc_node = tree_alloc_node, - - .node_get_nr_entries = node_get_nr_entries, - .node_set_nr_entries = node_set_nr_entries, - .node_get_entry = node_get_entry, - .node_set_entry = node_set_entry, - .node_get_child = node_get_child, - .node_set_child = node_set_child, - - .entry_compare = entry_compare, -}; - -void indent(int depth) +static enum ec3_status configure_image_parameters( + struct create_ctx *ctx, + struct ec3_parameters *param) { - for (int i = 0; i < depth; i++) { - fputs(" ", stdout); - } -} + enum ec3_status status = EC3_SUCCESS; -int node_print(struct b_tree *tree, struct node *n, int depth) -{ - int c_before = (n->nr_entries + 1) / 2; - int c_after = n->nr_entries + 1 - c_before; - int i_before = 0; - int i_after = i_before + c_before; - - int err = 0; - - for (unsigned short i = 0; i < c_before; i++) { - struct node child; - int index = i_before + i; - - if (n->children[index] == INVALID_NODE_PTR) { - // indent(depth + 1); - continue; + b_string *ident = B_STRING(b_dict_at(ctx->ctx_specification, "id")); + if (ident) { + if (B_TYPEID(ident) != B_OBJECT_TYPE_STRING) { + return EC3_ERR_BAD_FORMAT; } - err = tree_get_node( - tree, - n->children[index], - (b_tree_node *)&child); - if (err != 0) { - printf("ERR: failed to read node %u\n", - n->children[index]); - continue; + status = substitution_list_substitute( + &ctx->ctx_sub_list, + ident); + if (status != EC3_SUCCESS) { + return status; } - node_print(tree, &child, depth + 1); + status = ec3_identifier_from_string( + b_string_ptr(ident), + ¶m->p_ident); + + if (status != EC3_SUCCESS) { + return status; + } } - indent(depth); - printf("["); - - for (unsigned short i = 0; i < n->nr_entries; i++) { - if (i > 0) { - printf(" "); - } - - printf("%d", n->entries[i].key); + b_number *cluster_size_p + = B_NUMBER(b_dict_at(ctx->ctx_specification, "clusterSize")); + if (B_TYPEID(cluster_size_p) != B_OBJECT_TYPE_NUMBER) { + return EC3_ERR_BAD_FORMAT; } - printf("]\n"); - - for (unsigned short i = 0; i < c_after; i++) { - struct node child; - int index = i_after + i; - - if (n->children[index] == INVALID_NODE_PTR) { - // indent(depth + 1); - continue; - } - - err = tree_get_node( - tree, - n->children[index], - (b_tree_node *)&child); - if (err != 0) { - printf("ERR: failed to read node %u\n", - n->children[index]); - continue; - } - - node_print(tree, &child, depth + 1); + param->p_cluster_size = ec3_cluster_size_bytes_to_id( + b_number_get_int(cluster_size_p)); + if (param->p_cluster_size == -1) { + return EC3_ERR_NOT_SUPPORTED; } - return 0; + b_string *compression_p + = B_STRING(b_dict_at(ctx->ctx_specification, "compression")); + const char *compression = NULL; + if (compression_p) { + if (B_TYPEID(compression_p) != B_OBJECT_TYPE_STRING) { + return EC3_ERR_BAD_FORMAT; + } + + compression = b_string_ptr(compression_p); + if (!strcmp(compression, "zstd")) { + param->p_compression_func = EC3_COMPRESSION_ZSTD; + } else { + return EC3_ERR_NOT_SUPPORTED; + } + } + + b_string *encryption_p + = B_STRING(b_dict_at(ctx->ctx_specification, "encryption")); + const char *encryption = NULL; + if (encryption_p) { + if (B_TYPEID(encryption_p) != B_OBJECT_TYPE_STRING) { + return EC3_ERR_BAD_FORMAT; + } + + encryption = b_string_ptr(encryption_p); + if (!strcmp(encryption, "aes256")) { + param->p_compression_func = EC3_ENCRYPTION_AES256; + } else { + return EC3_ERR_NOT_SUPPORTED; + } + } + + return EC3_SUCCESS; } -int tree_print(struct b_tree *tree) +static b_status read_specification(const char *path_cstr, b_dict **out) { - struct node root; - tree_get_node(tree, 0, (b_tree_node *)&root); - return node_print(tree, &root, 0); + b_path *path = b_path_create_from_cstr(path_cstr); + b_file *file = NULL; + b_status status = b_file_open(NULL, path, B_FILE_READ_ONLY, &file); + b_path_release(path); + + if (!B_OK(status)) { + return status; + } + + b_stream *src = NULL; + status = b_file_open_stream(file, &src); + b_file_release(file); + + if (!B_OK(status)) { + return status; + } + + b_serial_ctx *ctx = NULL; + b_serial_ctx_create(&ctx); + + b_object *data = NULL; + status = b_serial_ctx_deserialise( + ctx, + B_SERIAL_FORMAT_JSON, + src, + &data, + 0); + if (!B_OK(status)) { + return status; + } + + b_stream_close(src); + + if (!B_OBJECT_IS(data, DICT)) { + b_release(data); + return B_ERR_BAD_FORMAT; + } + + *out = B_DICT(data); + return B_SUCCESS; +} + +#define ASSERT_OBJECT(object, status) \ + if (!object) { \ + return status; \ + } +#define ASSERT_OBJECT_TYPE(object, type, status) \ + if (!B_OBJECT_IS(object, type)) { \ + return status; \ + } + +static enum ec3_status capture_volumes(struct create_ctx *ctx) +{ + enum ec3_status status = EC3_SUCCESS; + + b_array *volumes + = B_ARRAY(b_dict_at(ctx->ctx_specification, "volumes")); + if (!volumes) { + return EC3_SUCCESS; + } + + ASSERT_OBJECT_TYPE(volumes, ARRAY, EC3_ERR_BAD_FORMAT); + + b_array_iterator it; + b_array_foreach(&it, volumes) + { + b_dict *volume = B_DICT(it.value); + ASSERT_OBJECT_TYPE(volume, DICT, EC3_ERR_BAD_FORMAT); + + uint64_t id = 0; + b_object *id_object = b_dict_at(volume, "id"); + ASSERT_OBJECT(id_object, EC3_ERR_BAD_FORMAT); + + if (B_OBJECT_IS(id_object, NUMBER)) { + id = (uint64_t)b_number_get_int64(B_NUMBER(id_object)); + } else if (B_OBJECT_IS(id_object, STRING)) { + substitution_list_substitute( + &ctx->ctx_sub_list, + B_STRING(id_object)); + status = ec3_identifier_from_string( + b_string_ptr(B_STRING(id_object)), + &id); + } + + if (status != EC3_SUCCESS) { + return status; + } + + b_string *src_path = B_STRING(b_dict_at(volume, "sourcePath")); + ASSERT_OBJECT(src_path, EC3_ERR_BAD_FORMAT); + ASSERT_OBJECT_TYPE(src_path, STRING, EC3_ERR_BAD_FORMAT); + substitution_list_substitute(&ctx->ctx_sub_list, src_path); + + char id_str[32]; + ec3_identifier_to_string(id, id_str, sizeof id_str); + + printf("adding volume 0x%" PRIx64 "(%s) -> %s\n", + id, + id_str, + b_string_ptr(src_path)); + + status = ec3_image_builder_capture_directory( + ctx->ctx_builder, + id, + b_string_ptr(src_path)); + if (status != EC3_SUCCESS) { + b_err("cannot capture directory '%s'", + b_string_ptr(src_path)); + b_i("reason: %s", ec3_status_to_string(status)); + return status; + } + } + + return EC3_SUCCESS; +} + +static enum ec3_status capture_blobs(struct create_ctx *ctx) +{ + enum ec3_status status = EC3_SUCCESS; + + b_array *blobs = B_ARRAY(b_dict_at(ctx->ctx_specification, "blobs")); + if (!blobs) { + return EC3_SUCCESS; + } + + ASSERT_OBJECT_TYPE(blobs, ARRAY, EC3_ERR_BAD_FORMAT); + + b_array_iterator it; + b_array_foreach(&it, blobs) + { + b_dict *blob = B_DICT(it.value); + ASSERT_OBJECT_TYPE(blob, DICT, EC3_ERR_BAD_FORMAT); + + uint64_t id = 0; + b_object *id_object = b_dict_at(blob, "id"); + if (!id_object) { + return EC3_ERR_BAD_FORMAT; + } + + if (B_OBJECT_IS(id_object, NUMBER)) { + id = (uint64_t)b_number_get_int64(B_NUMBER(id_object)); + } else if (B_OBJECT_IS(id_object, STRING)) { + substitution_list_substitute( + &ctx->ctx_sub_list, + B_STRING(id_object)); + status = ec3_identifier_from_string( + b_string_ptr(B_STRING(id_object)), + &id); + } + + if (status != EC3_SUCCESS) { + return status; + } + + b_string *src_path = B_STRING(b_dict_at(blob, "sourcePath")); + ASSERT_OBJECT(src_path, EC3_ERR_BAD_FORMAT); + ASSERT_OBJECT_TYPE(src_path, STRING, EC3_ERR_BAD_FORMAT); + substitution_list_substitute(&ctx->ctx_sub_list, src_path); + + char id_str[32]; + ec3_identifier_to_string(id, id_str, sizeof id_str); + + printf("adding blob 0x%" PRIx64 "(%s) -> %s\n", + id, + id_str, + b_string_ptr(src_path)); + + FILE *fp = fopen(b_string_ptr(src_path), "rb"); + if (!fp) { + b_err("cannot open file '%s'", b_string_ptr(src_path)); + b_i("reason: %s", strerror(errno)); + return EC3_ERR_NO_ENTRY; + } + + status = ec3_image_builder_add_blob_from_file( + ctx->ctx_builder, + id, + fp); + + fclose(fp); + + if (status != EC3_SUCCESS) { + b_err("cannot capture blob '%s'", + b_string_ptr(src_path)); + b_i("reason: %s", ec3_status_to_string(status)); + return status; + } + } + + return EC3_SUCCESS; +} + +static enum ec3_executable_format executable_format_string_to_id(const char *s) +{ + if (!strcmp(s, "elf")) { + return EC3_EXEC_ELF; + } + + return EC3_EXEC_OTHER; +} + +static enum ec3_status capture_executables(struct create_ctx *ctx) +{ + enum ec3_status status = EC3_SUCCESS; + + b_array *executables + = B_ARRAY(b_dict_at(ctx->ctx_specification, "executables")); + if (!executables) { + return EC3_SUCCESS; + } + + ASSERT_OBJECT_TYPE(executables, ARRAY, EC3_ERR_BAD_FORMAT); + + b_array_iterator it; + b_array_foreach(&it, executables) + { + b_dict *executable = B_DICT(it.value); + ASSERT_OBJECT_TYPE(executable, DICT, EC3_ERR_BAD_FORMAT); + + uint64_t id = 0; + b_object *id_object = b_dict_at(executable, "id"); + if (!id_object) { + return EC3_ERR_BAD_FORMAT; + } + + if (B_OBJECT_IS(id_object, NUMBER)) { + id = (uint64_t)b_number_get_int64(B_NUMBER(id_object)); + } else if (B_OBJECT_IS(id_object, STRING)) { + substitution_list_substitute( + &ctx->ctx_sub_list, + B_STRING(id_object)); + status = ec3_identifier_from_string( + b_string_ptr(B_STRING(id_object)), + &id); + } + + if (status != EC3_SUCCESS) { + return status; + } + + b_string *src_path + = B_STRING(b_dict_at(executable, "sourcePath")); + ASSERT_OBJECT(src_path, EC3_ERR_BAD_FORMAT); + ASSERT_OBJECT_TYPE(src_path, STRING, EC3_ERR_BAD_FORMAT); + substitution_list_substitute(&ctx->ctx_sub_list, src_path); + + b_string *format = B_STRING(b_dict_at(executable, "format")); + ASSERT_OBJECT(format, EC3_ERR_BAD_FORMAT); + ASSERT_OBJECT_TYPE(format, STRING, EC3_ERR_BAD_FORMAT); + substitution_list_substitute(&ctx->ctx_sub_list, format); + + b_string_tolower(format); + + char id_str[32]; + ec3_identifier_to_string(id, id_str, sizeof id_str); + + printf("adding %s executable 0x%" PRIx64 "(%s) -> %s\n", + b_string_ptr(format), + id, + id_str, + b_string_ptr(src_path)); + + FILE *fp = fopen(b_string_ptr(src_path), "rb"); + if (!fp) { + b_err("cannot open file '%s'", b_string_ptr(src_path)); + b_i("reason: %s", strerror(errno)); + return EC3_ERR_NO_ENTRY; + } + + enum ec3_executable_format exe_format + = executable_format_string_to_id(b_string_ptr(format)); + + struct ec3_tag_executable_info exe_info = {0}; + status = get_executable_info_from_file( + fp, + exe_format, + &exe_info); + if (status != EC3_SUCCESS) { + return status; + } + + fseek(fp, 0, SEEK_SET); + status = ec3_image_builder_add_executable_from_file( + ctx->ctx_builder, + id, + &exe_info, + fp); + + fclose(fp); + + if (status != EC3_SUCCESS) { + b_err("cannot capture executable '%s'", + b_string_ptr(src_path)); + b_i("reason: %s", ec3_status_to_string(status)); + return status; + } + } + + return EC3_SUCCESS; } static int create( @@ -252,48 +463,74 @@ static int create( const b_arglist *opt, const b_array *args) { - printf("%zu\n", sizeof(struct ec3_cluster_group)); - return 0; - const char *in_path = NULL, *out_path = NULL; - - b_arglist_get_string( + const char *out_path_cstr = NULL; + const char *specification_path_cstr = NULL; + b_status status = b_arglist_get_string( opt, - B_COMMAND_INVALID_ID, - ARG_INPATH, + OPT_SPECIFICATION, + OPT_SPECIFICATION_PATH, 0, - &in_path); - b_arglist_get_string(opt, OPT_OUTPATH, OPT_OUTPATH_PATH, 0, &out_path); - - printf("in path: %s\n", in_path); - printf("out path: %s\n", out_path); - - FILE *fp = fopen(out_path, "w+b"); - - struct node root; - node_init(&root); - fwrite(&root, sizeof root, 1, fp); - - struct data_tree tree; - b_tree_init( - &tree.base, - &ops, - sizeof(struct node), - sizeof(struct entry), - ORDER); - tree.fp = fp; - - for (int i = 1; i <= 32; i++) { - int key = rand() % 65535; - // int key = i; - // printf("%d: adding [%d]\n", i, key); - struct entry e = {.key = key, .value = key * 2}; - b_tree_put(&tree.base, (b_tree_node_entry *)&e); + &specification_path_cstr); + if (!B_OK(status)) { + b_err("no specification file specified\n"); + return -1; } - tree_print(&tree.base); + status = b_arglist_get_string( + opt, + OPT_OUTPATH, + OPT_OUTPATH_PATH, + 0, + &out_path_cstr); + if (!B_OK(status)) { + b_err("no output file path specified\n"); + return -1; + } - fclose(fp); + struct create_ctx ctx = {0}; + enum ec3_status status2 = create_ctx_init(&ctx, opt); + status = read_specification( + specification_path_cstr, + &ctx.ctx_specification); + if (!B_OK(status)) { + b_err("cannot read specification file."); + b_i("reason: %s", b_status_to_string(status)); + return -1; + } + + struct ec3_parameters param = {}; + status2 = configure_image_parameters(&ctx, ¶m); + if (status2 != EC3_SUCCESS) { + return -1; + } + + status2 = ec3_image_builder_create( + out_path_cstr, + ¶m, + &ctx.ctx_builder); + + if (status2 != EC3_SUCCESS) { + b_err("cannot initialise EC3 writer"); + return -1; + } + + status2 = capture_volumes(&ctx); + if (status2 != EC3_SUCCESS) { + return -1; + } + + status2 = capture_blobs(&ctx); + if (status2 != EC3_SUCCESS) { + return -1; + } + + status2 = capture_executables(&ctx); + if (status2 != EC3_SUCCESS) { + return -1; + } + + create_ctx_finish(&ctx); return 0; } @@ -320,16 +557,50 @@ B_COMMAND(CMD_CREATE, CMD_ROOT) } } - B_COMMAND_ARG(ARG_INPATH) + B_COMMAND_OPTION(OPT_SPECIFICATION) { - B_ARG_NAME("input file"); - B_ARG_NR_VALUES(1); + B_OPTION_SHORT_NAME('s'); + B_OPTION_LONG_NAME("specification"); + B_OPTION_DESC( + "the path to a specification file describing the " + "layout and parameters of the new file"); + + B_OPTION_ARG(OPT_SPECIFICATION_PATH) + { + B_ARG_NAME("path"); + B_ARG_NR_VALUES(1); + } + } + + B_COMMAND_OPTION(OPT_PARAMETER) + { + B_OPTION_SHORT_NAME('p'); + B_OPTION_LONG_NAME("parameter"); + B_OPTION_DESC("a parameter to use when creating the new file."); + + B_OPTION_ARG(OPT_PARAMETER_NAME) + { + B_ARG_NAME("name"); + B_ARG_NR_VALUES(1); + } + + B_OPTION_ARG(OPT_PARAMETER_VALUE) + { + B_ARG_NAME("value"); + B_ARG_NR_VALUES(1); + } } B_COMMAND_USAGE() { - B_COMMAND_USAGE_ARG(ARG_INPATH); + B_COMMAND_USAGE_OPT(OPT_SPECIFICATION); + B_COMMAND_USAGE_OPT(OPT_OUTPATH); + } + + B_COMMAND_USAGE() + { + B_COMMAND_USAGE_OPT(OPT_SPECIFICATION); + B_COMMAND_USAGE_OPT(OPT_PARAMETER); B_COMMAND_USAGE_OPT(OPT_OUTPATH); } } -#endif