From c6f9c6a696aad3f2b9d53b0731a6e678c43f56d7 Mon Sep 17 00:00:00 2001 From: Carl Niklas Rydberg Date: Sat, 20 Dec 2025 07:54:23 +0100 Subject: [PATCH] Add ASL FS metadata and layout helper --- CMakeLists.txt | 2 + include/amduat/asl/asl_store_fs_meta.h | 42 + src/adapters/asl_store_fs/asl_store_fs.c | 201 +---- .../asl_store_fs/asl_store_fs_layout.c | 193 +++++ .../asl_store_fs/asl_store_fs_layout.h | 39 + src/adapters/asl_store_fs/asl_store_fs_meta.c | 723 ++++++++++++++++++ 6 files changed, 1039 insertions(+), 161 deletions(-) create mode 100644 include/amduat/asl/asl_store_fs_meta.h create mode 100644 src/adapters/asl_store_fs/asl_store_fs_layout.c create mode 100644 src/adapters/asl_store_fs/asl_store_fs_layout.h create mode 100644 src/adapters/asl_store_fs/asl_store_fs_meta.c diff --git a/CMakeLists.txt b/CMakeLists.txt index ce484b2..78cbbe2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,6 +96,8 @@ set(AMDUAT_TGK_SRCS set(AMDUAT_ASL_STORE_FS_SRCS src/adapters/asl_store_fs/asl_store_fs.c + src/adapters/asl_store_fs/asl_store_fs_layout.c + src/adapters/asl_store_fs/asl_store_fs_meta.c ) set(AMDUAT_TGK_STORE_MEM_SRCS diff --git a/include/amduat/asl/asl_store_fs_meta.h b/include/amduat/asl/asl_store_fs_meta.h new file mode 100644 index 0000000..318854a --- /dev/null +++ b/include/amduat/asl/asl_store_fs_meta.h @@ -0,0 +1,42 @@ +#ifndef AMDUAT_ASL_STORE_FS_META_H +#define AMDUAT_ASL_STORE_FS_META_H + +#include "amduat/asl/core.h" +#include "amduat/asl/store.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +enum { AMDUAT_ASL_STORE_FS_STORE_ID_MAX = 256 }; + +typedef struct { + amduat_asl_store_config_t config; + char store_id[AMDUAT_ASL_STORE_FS_STORE_ID_MAX + 1]; +} amduat_asl_store_fs_config_t; + +typedef struct { + bool has_snapshot; + amduat_reference_t snapshot; +} amduat_asl_store_fs_head_t; + +bool amduat_asl_store_fs_init_root( + const char *root_path, + const amduat_asl_store_fs_config_t *cfg_in, + amduat_asl_store_fs_config_t *cfg_out); + +bool amduat_asl_store_fs_load_config( + const char *root_path, + amduat_asl_store_fs_config_t *out_cfg); + +bool amduat_asl_store_fs_load_head( + const char *root_path, + amduat_asl_store_fs_head_t *out_head); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* AMDUAT_ASL_STORE_FS_META_H */ diff --git a/src/adapters/asl_store_fs/asl_store_fs.c b/src/adapters/asl_store_fs/asl_store_fs.c index d884010..bf9a61d 100644 --- a/src/adapters/asl_store_fs/asl_store_fs.c +++ b/src/adapters/asl_store_fs/asl_store_fs.c @@ -1,5 +1,6 @@ #include "amduat/asl/asl_store_fs.h" +#include "asl_store_fs_layout.h" #include "amduat/asl/core.h" #include "amduat/enc/asl1_core.h" #include "amduat/enc/asl1_core_codec.h" @@ -36,60 +37,6 @@ typedef enum { AMDUAT_ASL_STORE_FS_READ_ERR = 2 } amduat_asl_store_fs_read_status_t; -static void amduat_asl_store_fs_format_hex(const uint8_t *bytes, - size_t count, - char *out) { - static const char k_hex[] = "0123456789abcdef"; - size_t i; - - for (i = 0; i < count; ++i) { - const uint8_t value = bytes[i]; - out[i * 2u] = k_hex[value >> 4u]; - out[i * 2u + 1u] = k_hex[value & 0x0fu]; - } - out[count * 2u] = '\0'; -} - -static bool amduat_asl_store_fs_join_path(const char *base, - const char *segment, - char **out_path) { - size_t base_len; - size_t seg_len; - bool needs_sep; - size_t total_len; - char *buffer; - size_t offset; - - if (base == NULL || segment == NULL || out_path == NULL) { - return false; - } - if (base[0] == '\0' || segment[0] == '\0') { - return false; - } - - base_len = strlen(base); - seg_len = strlen(segment); - needs_sep = base[base_len - 1u] != '/'; - total_len = base_len + (needs_sep ? 1u : 0u) + seg_len + 1u; - - buffer = (char *)malloc(total_len); - if (buffer == NULL) { - return false; - } - - offset = 0u; - memcpy(buffer + offset, base, base_len); - offset += base_len; - if (needs_sep) { - buffer[offset++] = '/'; - } - memcpy(buffer + offset, segment, seg_len); - offset += seg_len; - buffer[offset] = '\0'; - - *out_path = buffer; - return true; -} static bool amduat_asl_store_fs_require_directory(const char *path) { struct stat st; @@ -313,97 +260,6 @@ static amduat_asl_store_fs_read_status_t amduat_asl_store_fs_read_file( return AMDUAT_ASL_STORE_FS_READ_OK; } -static bool amduat_asl_store_fs_build_paths( - const amduat_asl_store_fs_t *fs, - const uint8_t *digest, - size_t digest_len, - char **out_profile_path, - char **out_hash_path, - char **out_level1_path, - char **out_level2_path, - char **out_object_path) { - char profile_hex[5]; - char hash_hex[5]; - char level1_segment[3]; - char level2_segment[3]; - char *digest_hex; - char *profile_path; - char *hash_path; - char *level1_path; - char *level2_path; - char *object_path; - bool ok; - - if (fs == NULL || digest == NULL || out_profile_path == NULL || - out_hash_path == NULL || out_level1_path == NULL || - out_level2_path == NULL || out_object_path == NULL) { - return false; - } - *out_profile_path = NULL; - *out_hash_path = NULL; - *out_level1_path = NULL; - *out_level2_path = NULL; - *out_object_path = NULL; - - if (digest_len < AMDUAT_ASL_STORE_FS_MIN_DIGEST_BYTES) { - return false; - } - if (digest_len > (SIZE_MAX - 1u) / 2u) { - return false; - } - - snprintf(profile_hex, sizeof(profile_hex), "%04x", - (unsigned int)fs->config.encoding_profile_id); - snprintf(hash_hex, sizeof(hash_hex), "%04x", - (unsigned int)fs->config.hash_id); - - amduat_asl_store_fs_format_hex(digest, 1u, level1_segment); - amduat_asl_store_fs_format_hex(digest + 1u, 1u, level2_segment); - - digest_hex = (char *)malloc(digest_len * 2u + 1u); - if (digest_hex == NULL) { - return false; - } - amduat_asl_store_fs_format_hex(digest, digest_len, digest_hex); - - profile_path = NULL; - hash_path = NULL; - level1_path = NULL; - level2_path = NULL; - object_path = NULL; - - ok = amduat_asl_store_fs_join_path(fs->root_path, profile_hex, &profile_path); - if (ok) { - ok = amduat_asl_store_fs_join_path(profile_path, hash_hex, &hash_path); - } - if (ok) { - ok = amduat_asl_store_fs_join_path(hash_path, level1_segment, &level1_path); - } - if (ok) { - ok = - amduat_asl_store_fs_join_path(level1_path, level2_segment, &level2_path); - } - if (ok) { - ok = amduat_asl_store_fs_join_path(level2_path, digest_hex, &object_path); - } - - free(digest_hex); - if (!ok) { - free(profile_path); - free(hash_path); - free(level1_path); - free(level2_path); - free(object_path); - return false; - } - - *out_profile_path = profile_path; - *out_hash_path = hash_path; - *out_level1_path = level1_path; - *out_level2_path = level2_path; - *out_object_path = object_path; - return true; -} static amduat_asl_store_error_t amduat_asl_store_fs_compare_existing( const char *object_path, @@ -447,6 +303,7 @@ static amduat_asl_store_error_t amduat_asl_store_fs_put_impl( amduat_octets_t artifact_bytes; uint8_t *digest; bool ok; + char *objects_path; char *profile_path; char *hash_path; char *level1_path; @@ -490,19 +347,22 @@ static amduat_asl_store_error_t amduat_asl_store_fs_put_impl( return AMDUAT_ASL_STORE_ERR_INTEGRITY; } + objects_path = NULL; profile_path = NULL; hash_path = NULL; level1_path = NULL; level2_path = NULL; object_path = NULL; - ok = amduat_asl_store_fs_build_paths(fs, - digest, - hash_desc->digest_len, - &profile_path, - &hash_path, - &level1_path, - &level2_path, - &object_path); + ok = amduat_asl_store_fs_layout_build_paths(fs->root_path, + fs->config, + digest, + hash_desc->digest_len, + &objects_path, + &profile_path, + &hash_path, + &level1_path, + &level2_path, + &object_path); if (!ok) { free((void *)artifact_bytes.data); free(digest); @@ -510,12 +370,14 @@ static amduat_asl_store_error_t amduat_asl_store_fs_put_impl( } if (!amduat_asl_store_fs_require_directory(fs->root_path) || + !amduat_asl_store_fs_ensure_directory(objects_path) || !amduat_asl_store_fs_ensure_directory(profile_path) || !amduat_asl_store_fs_ensure_directory(hash_path) || !amduat_asl_store_fs_ensure_directory(level1_path) || !amduat_asl_store_fs_ensure_directory(level2_path)) { free((void *)artifact_bytes.data); free(digest); + free(objects_path); free(profile_path); free(hash_path); free(level1_path); @@ -531,6 +393,7 @@ static amduat_asl_store_error_t amduat_asl_store_fs_put_impl( out_ref->hash_id = fs->config.hash_id; out_ref->digest = amduat_octets(digest, hash_desc->digest_len); free((void *)artifact_bytes.data); + free(objects_path); free(profile_path); free(hash_path); free(level1_path); @@ -541,6 +404,7 @@ static amduat_asl_store_error_t amduat_asl_store_fs_put_impl( if (cmp_err == AMDUAT_ASL_STORE_ERR_INTEGRITY) { free((void *)artifact_bytes.data); free(digest); + free(objects_path); free(profile_path); free(hash_path); free(level1_path); @@ -560,6 +424,7 @@ static amduat_asl_store_error_t amduat_asl_store_fs_put_impl( if (cmp_err != AMDUAT_ASL_STORE_OK) { free((void *)artifact_bytes.data); free(digest); + free(objects_path); free(profile_path); free(hash_path); free(level1_path); @@ -570,6 +435,7 @@ static amduat_asl_store_error_t amduat_asl_store_fs_put_impl( } else if (write_status != AMDUAT_ASL_STORE_FS_WRITE_OK) { free((void *)artifact_bytes.data); free(digest); + free(objects_path); free(profile_path); free(hash_path); free(level1_path); @@ -582,6 +448,7 @@ static amduat_asl_store_error_t amduat_asl_store_fs_put_impl( unlink(object_path); free((void *)artifact_bytes.data); free(digest); + free(objects_path); free(profile_path); free(hash_path); free(level1_path); @@ -593,6 +460,7 @@ static amduat_asl_store_error_t amduat_asl_store_fs_put_impl( unlink(object_path); free((void *)artifact_bytes.data); free(digest); + free(objects_path); free(profile_path); free(hash_path); free(level1_path); @@ -604,6 +472,7 @@ static amduat_asl_store_error_t amduat_asl_store_fs_put_impl( out_ref->hash_id = fs->config.hash_id; out_ref->digest = amduat_octets(digest, hash_desc->digest_len); free((void *)artifact_bytes.data); + free(objects_path); free(profile_path); free(hash_path); free(level1_path); @@ -618,6 +487,7 @@ static amduat_asl_store_error_t amduat_asl_store_fs_get_impl( amduat_artifact_t *out_artifact) { amduat_asl_store_fs_t *fs; const amduat_hash_asl1_desc_t *hash_desc; + char *objects_path; char *profile_path; char *hash_path; char *level1_path; @@ -654,19 +524,22 @@ static amduat_asl_store_error_t amduat_asl_store_fs_get_impl( return AMDUAT_ASL_STORE_ERR_UNSUPPORTED; } + objects_path = NULL; profile_path = NULL; hash_path = NULL; level1_path = NULL; level2_path = NULL; object_path = NULL; - if (!amduat_asl_store_fs_build_paths(fs, - ref.digest.data, - ref.digest.len, - &profile_path, - &hash_path, - &level1_path, - &level2_path, - &object_path)) { + if (!amduat_asl_store_fs_layout_build_paths(fs->root_path, + fs->config, + ref.digest.data, + ref.digest.len, + &objects_path, + &profile_path, + &hash_path, + &level1_path, + &level2_path, + &object_path)) { return AMDUAT_ASL_STORE_ERR_INTEGRITY; } @@ -675,6 +548,7 @@ static amduat_asl_store_error_t amduat_asl_store_fs_get_impl( read_status = amduat_asl_store_fs_read_file(object_path, &stored_bytes, &stored_len); if (read_status == AMDUAT_ASL_STORE_FS_READ_NOT_FOUND) { + free(objects_path); free(profile_path); free(hash_path); free(level1_path); @@ -683,6 +557,7 @@ static amduat_asl_store_error_t amduat_asl_store_fs_get_impl( return AMDUAT_ASL_STORE_ERR_NOT_FOUND; } if (read_status != AMDUAT_ASL_STORE_FS_READ_OK) { + free(objects_path); free(profile_path); free(hash_path); free(level1_path); @@ -694,6 +569,7 @@ static amduat_asl_store_error_t amduat_asl_store_fs_get_impl( computed_digest = (uint8_t *)malloc(hash_desc->digest_len); if (computed_digest == NULL) { free(stored_bytes); + free(objects_path); free(profile_path); free(hash_path); free(level1_path); @@ -709,6 +585,7 @@ static amduat_asl_store_error_t amduat_asl_store_fs_get_impl( hash_desc->digest_len)) { free(computed_digest); free(stored_bytes); + free(objects_path); free(profile_path); free(hash_path); free(level1_path); @@ -720,6 +597,7 @@ static amduat_asl_store_error_t amduat_asl_store_fs_get_impl( if (memcmp(computed_digest, ref.digest.data, hash_desc->digest_len) != 0) { free(computed_digest); free(stored_bytes); + free(objects_path); free(profile_path); free(hash_path); free(level1_path); @@ -733,6 +611,7 @@ static amduat_asl_store_error_t amduat_asl_store_fs_get_impl( amduat_octets(stored_bytes, stored_len), out_artifact); free(stored_bytes); + free(objects_path); free(profile_path); free(hash_path); free(level1_path); diff --git a/src/adapters/asl_store_fs/asl_store_fs_layout.c b/src/adapters/asl_store_fs/asl_store_fs_layout.c new file mode 100644 index 0000000..cd50001 --- /dev/null +++ b/src/adapters/asl_store_fs/asl_store_fs_layout.c @@ -0,0 +1,193 @@ +#include "asl_store_fs_layout.h" + +#include +#include +#include +#include +#include + +enum { AMDUAT_ASL_STORE_FS_LAYOUT_MIN_DIGEST_BYTES = 2 }; + +static void amduat_asl_store_fs_layout_format_hex(const uint8_t *bytes, + size_t count, + char *out) { + static const char k_hex[] = "0123456789abcdef"; + size_t i; + + for (i = 0; i < count; ++i) { + const uint8_t value = bytes[i]; + out[i * 2u] = k_hex[value >> 4u]; + out[i * 2u + 1u] = k_hex[value & 0x0fu]; + } + out[count * 2u] = '\0'; +} + +static bool amduat_asl_store_fs_layout_join_path(const char *base, + const char *segment, + char **out_path) { + size_t base_len; + size_t seg_len; + bool needs_sep; + size_t total_len; + char *buffer; + size_t offset; + + if (base == NULL || segment == NULL || out_path == NULL) { + return false; + } + if (base[0] == '\0' || segment[0] == '\0') { + return false; + } + + base_len = strlen(base); + seg_len = strlen(segment); + needs_sep = base[base_len - 1u] != '/'; + total_len = base_len + (needs_sep ? 1u : 0u) + seg_len + 1u; + + buffer = (char *)malloc(total_len); + if (buffer == NULL) { + return false; + } + + offset = 0u; + memcpy(buffer + offset, base, base_len); + offset += base_len; + if (needs_sep) { + buffer[offset++] = '/'; + } + memcpy(buffer + offset, segment, seg_len); + offset += seg_len; + buffer[offset] = '\0'; + + *out_path = buffer; + return true; +} + +bool amduat_asl_store_fs_layout_build_config_path(const char *root_path, + char **out_path) { + return amduat_asl_store_fs_layout_join_path(root_path, "CONFIG", out_path); +} + +bool amduat_asl_store_fs_layout_build_head_path(const char *root_path, + char **out_path) { + return amduat_asl_store_fs_layout_join_path(root_path, "HEAD", out_path); +} + +bool amduat_asl_store_fs_layout_build_objects_path(const char *root_path, + char **out_path) { + return amduat_asl_store_fs_layout_join_path(root_path, "objects", out_path); +} + +bool amduat_asl_store_fs_layout_build_paths( + const char *root_path, + amduat_asl_store_config_t config, + const uint8_t *digest, + size_t digest_len, + char **out_objects_path, + char **out_profile_path, + char **out_hash_path, + char **out_level1_path, + char **out_level2_path, + char **out_object_path) { + char profile_hex[5]; + char hash_hex[5]; + char level1_segment[3]; + char level2_segment[3]; + char *digest_hex; + char *objects_path; + char *profile_path; + char *hash_path; + char *level1_path; + char *level2_path; + char *object_path; + bool ok; + + if (root_path == NULL || digest == NULL || out_objects_path == NULL || + out_profile_path == NULL || out_hash_path == NULL || + out_level1_path == NULL || out_level2_path == NULL || + out_object_path == NULL) { + return false; + } + *out_objects_path = NULL; + *out_profile_path = NULL; + *out_hash_path = NULL; + *out_level1_path = NULL; + *out_level2_path = NULL; + *out_object_path = NULL; + + if (root_path[0] == '\0') { + return false; + } + if (digest_len < AMDUAT_ASL_STORE_FS_LAYOUT_MIN_DIGEST_BYTES) { + return false; + } + if (digest_len > (SIZE_MAX - 1u) / 2u) { + return false; + } + + snprintf(profile_hex, sizeof(profile_hex), "%04x", + (unsigned int)config.encoding_profile_id); + snprintf(hash_hex, sizeof(hash_hex), "%04x", (unsigned int)config.hash_id); + + amduat_asl_store_fs_layout_format_hex(digest, 1u, level1_segment); + amduat_asl_store_fs_layout_format_hex(digest + 1u, 1u, level2_segment); + + digest_hex = (char *)malloc(digest_len * 2u + 1u); + if (digest_hex == NULL) { + return false; + } + amduat_asl_store_fs_layout_format_hex(digest, digest_len, digest_hex); + + objects_path = NULL; + profile_path = NULL; + hash_path = NULL; + level1_path = NULL; + level2_path = NULL; + object_path = NULL; + + ok = amduat_asl_store_fs_layout_build_objects_path(root_path, &objects_path); + if (ok) { + ok = amduat_asl_store_fs_layout_join_path(objects_path, + profile_hex, + &profile_path); + } + if (ok) { + ok = amduat_asl_store_fs_layout_join_path(profile_path, + hash_hex, + &hash_path); + } + if (ok) { + ok = amduat_asl_store_fs_layout_join_path(hash_path, + level1_segment, + &level1_path); + } + if (ok) { + ok = amduat_asl_store_fs_layout_join_path(level1_path, + level2_segment, + &level2_path); + } + if (ok) { + ok = amduat_asl_store_fs_layout_join_path(level2_path, + digest_hex, + &object_path); + } + + free(digest_hex); + if (!ok) { + free(objects_path); + free(profile_path); + free(hash_path); + free(level1_path); + free(level2_path); + free(object_path); + return false; + } + + *out_objects_path = objects_path; + *out_profile_path = profile_path; + *out_hash_path = hash_path; + *out_level1_path = level1_path; + *out_level2_path = level2_path; + *out_object_path = object_path; + return true; +} diff --git a/src/adapters/asl_store_fs/asl_store_fs_layout.h b/src/adapters/asl_store_fs/asl_store_fs_layout.h new file mode 100644 index 0000000..04c24f8 --- /dev/null +++ b/src/adapters/asl_store_fs/asl_store_fs_layout.h @@ -0,0 +1,39 @@ +#ifndef AMDUAT_ASL_STORE_FS_LAYOUT_H +#define AMDUAT_ASL_STORE_FS_LAYOUT_H + +#include "amduat/asl/store.h" + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +bool amduat_asl_store_fs_layout_build_paths( + const char *root_path, + amduat_asl_store_config_t config, + const uint8_t *digest, + size_t digest_len, + char **out_objects_path, + char **out_profile_path, + char **out_hash_path, + char **out_level1_path, + char **out_level2_path, + char **out_object_path); + +bool amduat_asl_store_fs_layout_build_config_path(const char *root_path, + char **out_path); + +bool amduat_asl_store_fs_layout_build_head_path(const char *root_path, + char **out_path); + +bool amduat_asl_store_fs_layout_build_objects_path(const char *root_path, + char **out_path); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* AMDUAT_ASL_STORE_FS_LAYOUT_H */ diff --git a/src/adapters/asl_store_fs/asl_store_fs_meta.c b/src/adapters/asl_store_fs/asl_store_fs_meta.c new file mode 100644 index 0000000..1f03c05 --- /dev/null +++ b/src/adapters/asl_store_fs/asl_store_fs_meta.c @@ -0,0 +1,723 @@ +#include "amduat/asl/asl_store_fs_meta.h" + +#include "asl_store_fs_layout.h" +#include "amduat/enc/asl1_core.h" +#include "amduat/hash/asl1.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef O_DIRECTORY +#define O_DIRECTORY 0 +#endif + +typedef enum { + AMDUAT_ASL_STORE_FS_META_READ_OK = 0, + AMDUAT_ASL_STORE_FS_META_READ_NOT_FOUND = 1, + AMDUAT_ASL_STORE_FS_META_READ_ERR = 2 +} amduat_asl_store_fs_meta_read_status_t; + +static bool amduat_asl_store_fs_meta_is_store_id_char(char c) { + if (c >= 'a' && c <= 'z') { + return true; + } + if (c >= 'A' && c <= 'Z') { + return true; + } + if (c >= '0' && c <= '9') { + return true; + } + return c == '.' || c == '_' || c == '-'; +} + +static bool amduat_asl_store_fs_meta_validate_store_id(const char *store_id) { + size_t i; + + if (store_id == NULL || store_id[0] == '\0') { + return false; + } + for (i = 0; store_id[i] != '\0'; ++i) { + if (i >= AMDUAT_ASL_STORE_FS_STORE_ID_MAX) { + return false; + } + if (!amduat_asl_store_fs_meta_is_store_id_char(store_id[i])) { + return false; + } + } + return true; +} + +static bool amduat_asl_store_fs_meta_copy_store_id( + char out_id[AMDUAT_ASL_STORE_FS_STORE_ID_MAX + 1], + const char *store_id) { + size_t len; + + if (out_id == NULL || store_id == NULL) { + return false; + } + if (!amduat_asl_store_fs_meta_validate_store_id(store_id)) { + return false; + } + len = strlen(store_id); + if (len > AMDUAT_ASL_STORE_FS_STORE_ID_MAX) { + return false; + } + memcpy(out_id, store_id, len); + out_id[len] = '\0'; + return true; +} + +static bool amduat_asl_store_fs_meta_parse_hex4_lower(const char *value, + uint16_t *out) { + size_t i; + uint16_t accum; + + if (value == NULL || out == NULL) { + return false; + } + if (strlen(value) != 4u) { + return false; + } + accum = 0u; + for (i = 0; i < 4u; ++i) { + char c = value[i]; + uint8_t nibble; + if (c >= '0' && c <= '9') { + nibble = (uint8_t)(c - '0'); + } else if (c >= 'a' && c <= 'f') { + nibble = (uint8_t)(c - 'a' + 10); + } else { + return false; + } + accum = (uint16_t)((accum << 4u) | nibble); + } + *out = accum; + return true; +} + +static bool amduat_asl_store_fs_meta_ensure_directory(const char *path) { + struct stat st; + + if (path == NULL || path[0] == '\0') { + return false; + } + if (stat(path, &st) == 0) { + return S_ISDIR(st.st_mode); + } + if (errno != ENOENT) { + return false; + } + if (mkdir(path, 0755) != 0) { + if (errno == EEXIST) { + return stat(path, &st) == 0 && S_ISDIR(st.st_mode); + } + return false; + } + return true; +} + +static bool amduat_asl_store_fs_meta_fsync_directory(const char *path) { + int fd; + int fsync_errno; + + if (path == NULL || path[0] == '\0') { + return false; + } + + fd = open(path, O_RDONLY | O_DIRECTORY); + if (fd < 0) { + return false; + } + while (fsync(fd) != 0) { + if (errno == EINTR) { + continue; + } + fsync_errno = errno; + close(fd); + errno = fsync_errno; + return false; + } + if (close(fd) != 0) { + return false; + } + return true; +} + +static amduat_asl_store_fs_meta_read_status_t +amduat_asl_store_fs_meta_read_file(const char *path, + char **out_bytes, + size_t *out_size) { + struct stat st; + size_t file_size; + char *buffer; + size_t total_read; + int fd; + + if (path == NULL || out_bytes == NULL || out_size == NULL) { + return AMDUAT_ASL_STORE_FS_META_READ_ERR; + } + *out_bytes = NULL; + *out_size = 0; + + if (stat(path, &st) != 0) { + if (errno == ENOENT || errno == ENOTDIR) { + return AMDUAT_ASL_STORE_FS_META_READ_NOT_FOUND; + } + return AMDUAT_ASL_STORE_FS_META_READ_ERR; + } + if (!S_ISREG(st.st_mode)) { + return AMDUAT_ASL_STORE_FS_META_READ_ERR; + } + if (st.st_size <= 0) { + return AMDUAT_ASL_STORE_FS_META_READ_ERR; + } + if ((uintmax_t)st.st_size > SIZE_MAX) { + return AMDUAT_ASL_STORE_FS_META_READ_ERR; + } + + file_size = (size_t)st.st_size; + buffer = (char *)malloc(file_size + 1u); + if (buffer == NULL) { + return AMDUAT_ASL_STORE_FS_META_READ_ERR; + } + + fd = open(path, O_RDONLY); + if (fd < 0) { + free(buffer); + if (errno == ENOENT || errno == ENOTDIR) { + return AMDUAT_ASL_STORE_FS_META_READ_NOT_FOUND; + } + return AMDUAT_ASL_STORE_FS_META_READ_ERR; + } + + total_read = 0u; + while (total_read < file_size) { + ssize_t rc = read(fd, buffer + total_read, file_size - total_read); + if (rc < 0) { + if (errno == EINTR) { + continue; + } + close(fd); + free(buffer); + return AMDUAT_ASL_STORE_FS_META_READ_ERR; + } + if (rc == 0) { + close(fd); + free(buffer); + return AMDUAT_ASL_STORE_FS_META_READ_ERR; + } + total_read += (size_t)rc; + } + + if (close(fd) != 0) { + free(buffer); + return AMDUAT_ASL_STORE_FS_META_READ_ERR; + } + + buffer[file_size] = '\0'; + *out_bytes = buffer; + *out_size = file_size; + return AMDUAT_ASL_STORE_FS_META_READ_OK; +} + +static bool amduat_asl_store_fs_meta_write_atomic(const char *dir_path, + const char *final_path, + const uint8_t *bytes, + size_t size) { + static const char suffix[] = "tmp.XXXXXX"; + size_t dir_len; + bool need_sep; + size_t template_len; + char *template_path; + int temp_fd; + size_t written; + + if (dir_path == NULL || final_path == NULL) { + return false; + } + if (size != 0u && bytes == NULL) { + return false; + } + + dir_len = strlen(dir_path); + if (dir_len == 0u) { + return false; + } + need_sep = dir_path[dir_len - 1u] != '/'; + template_len = dir_len + (need_sep ? 1u : 0u) + sizeof(suffix); + + template_path = (char *)malloc(template_len); + if (template_path == NULL) { + return false; + } + if (need_sep) { + snprintf(template_path, template_len, "%s/%s", dir_path, suffix); + } else { + snprintf(template_path, template_len, "%s%s", dir_path, suffix); + } + + temp_fd = mkstemp(template_path); + if (temp_fd < 0) { + free(template_path); + return false; + } + + written = 0u; + while (written < size) { + ssize_t rc = write(temp_fd, bytes + written, size - written); + if (rc < 0) { + if (errno == EINTR) { + continue; + } + close(temp_fd); + unlink(template_path); + free(template_path); + return false; + } + written += (size_t)rc; + } + + if (fsync(temp_fd) != 0) { + close(temp_fd); + unlink(template_path); + free(template_path); + return false; + } + + if (close(temp_fd) != 0) { + unlink(template_path); + free(template_path); + return false; + } + + if (rename(template_path, final_path) != 0) { + unlink(template_path); + free(template_path); + return false; + } + + free(template_path); + return amduat_asl_store_fs_meta_fsync_directory(dir_path); +} + +static bool amduat_asl_store_fs_meta_random_bytes(uint8_t *out, size_t len) { + size_t read_total; + int fd; + + if (out == NULL || len == 0u) { + return false; + } + + fd = open("/dev/urandom", O_RDONLY); + if (fd >= 0) { + read_total = 0u; + while (read_total < len) { + ssize_t rc = read(fd, out + read_total, len - read_total); + if (rc < 0) { + if (errno == EINTR) { + continue; + } + close(fd); + fd = -1; + break; + } + if (rc == 0) { + close(fd); + fd = -1; + break; + } + read_total += (size_t)rc; + } + if (fd >= 0) { + close(fd); + return true; + } + } + + { + unsigned int seed; + size_t i; + + seed = (unsigned int)time(NULL); + seed ^= (unsigned int)getpid(); + seed ^= (unsigned int)(uintptr_t)out; + srand(seed); + for (i = 0; i < len; ++i) { + out[i] = (uint8_t)(rand() & 0xff); + } + } + return true; +} + +static char amduat_asl_store_fs_meta_hex_lower(uint8_t nibble) { + static const char k_hex[] = "0123456789abcdef"; + return k_hex[nibble & 0x0fu]; +} + +static bool amduat_asl_store_fs_meta_generate_uuid( + char out_id[AMDUAT_ASL_STORE_FS_STORE_ID_MAX + 1]) { + uint8_t bytes[16]; + char *out; + size_t i; + + if (out_id == NULL) { + return false; + } + if (!amduat_asl_store_fs_meta_random_bytes(bytes, sizeof(bytes))) { + return false; + } + + bytes[6] = (uint8_t)((bytes[6] & 0x0fu) | 0x40u); + bytes[8] = (uint8_t)((bytes[8] & 0x3fu) | 0x80u); + + out = out_id; + for (i = 0; i < sizeof(bytes); ++i) { + if (i == 4u || i == 6u || i == 8u || i == 10u) { + *out++ = '-'; + } + *out++ = amduat_asl_store_fs_meta_hex_lower((uint8_t)(bytes[i] >> 4u)); + *out++ = amduat_asl_store_fs_meta_hex_lower(bytes[i]); + } + *out = '\0'; + + return amduat_asl_store_fs_meta_validate_store_id(out_id); +} + +static bool amduat_asl_store_fs_meta_parse_config_text( + char *text, + amduat_asl_store_fs_config_t *out_cfg) { + char *saveptr; + char *token; + bool got_store_id; + bool got_profile; + bool got_hash; + + if (text == NULL || out_cfg == NULL) { + return false; + } + + memset(out_cfg, 0, sizeof(*out_cfg)); + saveptr = NULL; + token = strtok_r(text, " \t\r\n", &saveptr); + if (token == NULL || strcmp(token, "amduat-asl-config-v1") != 0) { + return false; + } + + got_store_id = false; + got_profile = false; + got_hash = false; + + while ((token = strtok_r(NULL, " \t\r\n", &saveptr)) != NULL) { + if (strncmp(token, "store_id=", 9u) == 0) { + const char *value = token + 9u; + if (!amduat_asl_store_fs_meta_copy_store_id(out_cfg->store_id, value)) { + return false; + } + got_store_id = true; + continue; + } + if (strncmp(token, "profile=", 8u) == 0) { + const char *value = token + 8u; + uint16_t profile_id; + if (!amduat_asl_store_fs_meta_parse_hex4_lower(value, &profile_id)) { + return false; + } + out_cfg->config.encoding_profile_id = profile_id; + got_profile = true; + continue; + } + if (strncmp(token, "hash=", 5u) == 0) { + const char *value = token + 5u; + uint16_t hash_id; + if (!amduat_asl_store_fs_meta_parse_hex4_lower(value, &hash_id)) { + return false; + } + out_cfg->config.hash_id = hash_id; + got_hash = true; + continue; + } + } + + return got_store_id && got_profile && got_hash; +} + +static bool amduat_asl_store_fs_meta_parse_head_text( + char *text, + amduat_asl_store_fs_head_t *out_head) { + char *saveptr; + char *token; + bool got_snapshot; + + if (text == NULL || out_head == NULL) { + return false; + } + + memset(out_head, 0, sizeof(*out_head)); + saveptr = NULL; + token = strtok_r(text, " \t\r\n", &saveptr); + if (token == NULL || strcmp(token, "amduat-asl-head-v1") != 0) { + return false; + } + + got_snapshot = false; + + while ((token = strtok_r(NULL, " \t\r\n", &saveptr)) != NULL) { + if (strncmp(token, "snapshot=", 9u) == 0) { + const char *value = token + 9u; + if (strcmp(value, "none") != 0) { + return false; + } + out_head->has_snapshot = false; + got_snapshot = true; + } + } + + return got_snapshot; +} + +static bool amduat_asl_store_fs_meta_write_config( + const char *root_path, + const amduat_asl_store_fs_config_t *cfg) { + char *config_path; + int needed; + size_t len; + char *buffer; + bool ok; + + if (root_path == NULL || cfg == NULL) { + return false; + } + + config_path = NULL; + if (!amduat_asl_store_fs_layout_build_config_path(root_path, &config_path)) { + return false; + } + + needed = snprintf(NULL, + 0, + "amduat-asl-config-v1\nstore_id=%s\nprofile=%04x\nhash=%04x\n", + cfg->store_id, + (unsigned int)cfg->config.encoding_profile_id, + (unsigned int)cfg->config.hash_id); + if (needed < 0) { + free(config_path); + return false; + } + len = (size_t)needed; + + buffer = (char *)malloc(len + 1u); + if (buffer == NULL) { + free(config_path); + return false; + } + snprintf(buffer, + len + 1u, + "amduat-asl-config-v1\nstore_id=%s\nprofile=%04x\nhash=%04x\n", + cfg->store_id, + (unsigned int)cfg->config.encoding_profile_id, + (unsigned int)cfg->config.hash_id); + + ok = amduat_asl_store_fs_meta_write_atomic(root_path, + config_path, + (const uint8_t *)buffer, + len); + free(buffer); + free(config_path); + return ok; +} + +bool amduat_asl_store_fs_init_root( + const char *root_path, + const amduat_asl_store_fs_config_t *cfg_in, + amduat_asl_store_fs_config_t *cfg_out) { + char *objects_path; + char *config_path; + amduat_asl_store_fs_meta_read_status_t read_status; + char *config_text; + size_t config_size; + amduat_asl_store_fs_config_t loaded_cfg; + amduat_asl_store_fs_config_t new_cfg; + + if (root_path == NULL || root_path[0] == '\0' || cfg_out == NULL) { + return false; + } + + if (!amduat_asl_store_fs_meta_ensure_directory(root_path)) { + return false; + } + + objects_path = NULL; + if (!amduat_asl_store_fs_layout_build_objects_path(root_path, + &objects_path)) { + return false; + } + if (!amduat_asl_store_fs_meta_ensure_directory(objects_path)) { + free(objects_path); + return false; + } + free(objects_path); + + config_path = NULL; + if (!amduat_asl_store_fs_layout_build_config_path(root_path, + &config_path)) { + return false; + } + + config_text = NULL; + config_size = 0u; + read_status = + amduat_asl_store_fs_meta_read_file(config_path, + &config_text, + &config_size); + (void)config_size; + if (read_status == AMDUAT_ASL_STORE_FS_META_READ_OK) { + bool ok = amduat_asl_store_fs_meta_parse_config_text(config_text, + &loaded_cfg); + free(config_text); + free(config_path); + if (!ok) { + return false; + } + if (cfg_in != NULL) { + if (cfg_in->store_id[0] != '\0') { + if (!amduat_asl_store_fs_meta_validate_store_id(cfg_in->store_id)) { + return false; + } + if (strcmp(cfg_in->store_id, loaded_cfg.store_id) != 0) { + return false; + } + } + if (cfg_in->config.encoding_profile_id != 0 && + cfg_in->config.encoding_profile_id != + loaded_cfg.config.encoding_profile_id) { + return false; + } + if (cfg_in->config.hash_id != 0 && + cfg_in->config.hash_id != loaded_cfg.config.hash_id) { + return false; + } + } + *cfg_out = loaded_cfg; + return true; + } + if (read_status != AMDUAT_ASL_STORE_FS_META_READ_NOT_FOUND) { + free(config_path); + return false; + } + free(config_path); + + memset(&new_cfg, 0, sizeof(new_cfg)); + new_cfg.config.encoding_profile_id = AMDUAT_ENC_ASL1_CORE_V1; + new_cfg.config.hash_id = AMDUAT_HASH_ASL1_ID_SHA256; + + if (cfg_in != NULL) { + if (cfg_in->config.encoding_profile_id != 0) { + new_cfg.config.encoding_profile_id = + cfg_in->config.encoding_profile_id; + } + if (cfg_in->config.hash_id != 0) { + new_cfg.config.hash_id = cfg_in->config.hash_id; + } + if (cfg_in->store_id[0] != '\0') { + if (!amduat_asl_store_fs_meta_copy_store_id(new_cfg.store_id, + cfg_in->store_id)) { + return false; + } + } + } + + if (new_cfg.store_id[0] == '\0') { + if (!amduat_asl_store_fs_meta_generate_uuid(new_cfg.store_id)) { + return false; + } + } + + if (!amduat_asl_store_fs_meta_write_config(root_path, &new_cfg)) { + return false; + } + + *cfg_out = new_cfg; + return true; +} + +bool amduat_asl_store_fs_load_config( + const char *root_path, + amduat_asl_store_fs_config_t *out_cfg) { + char *config_path; + amduat_asl_store_fs_meta_read_status_t read_status; + char *config_text; + size_t config_size; + bool ok; + + if (root_path == NULL || root_path[0] == '\0' || out_cfg == NULL) { + return false; + } + + config_path = NULL; + if (!amduat_asl_store_fs_layout_build_config_path(root_path, &config_path)) { + return false; + } + + config_text = NULL; + config_size = 0u; + read_status = + amduat_asl_store_fs_meta_read_file(config_path, + &config_text, + &config_size); + (void)config_size; + free(config_path); + if (read_status != AMDUAT_ASL_STORE_FS_META_READ_OK) { + return false; + } + + ok = amduat_asl_store_fs_meta_parse_config_text(config_text, out_cfg); + free(config_text); + return ok; +} + +bool amduat_asl_store_fs_load_head( + const char *root_path, + amduat_asl_store_fs_head_t *out_head) { + char *head_path; + amduat_asl_store_fs_meta_read_status_t read_status; + char *head_text; + size_t head_size; + bool ok; + + if (root_path == NULL || root_path[0] == '\0' || out_head == NULL) { + return false; + } + + head_path = NULL; + if (!amduat_asl_store_fs_layout_build_head_path(root_path, &head_path)) { + return false; + } + + head_text = NULL; + head_size = 0u; + read_status = + amduat_asl_store_fs_meta_read_file(head_path, &head_text, &head_size); + (void)head_size; + free(head_path); + if (read_status == AMDUAT_ASL_STORE_FS_META_READ_NOT_FOUND) { + memset(out_head, 0, sizeof(*out_head)); + out_head->has_snapshot = false; + return true; + } + if (read_status != AMDUAT_ASL_STORE_FS_META_READ_OK) { + return false; + } + + ok = amduat_asl_store_fs_meta_parse_head_text(head_text, out_head); + free(head_text); + return ok; +}