From 556c65a54ef805f2ba2f4fb72b79bc7d8e8f424a Mon Sep 17 00:00:00 2001 From: Carl Niklas Rydberg Date: Sat, 17 Jan 2026 14:26:17 +0100 Subject: [PATCH] Add ASL index/log inspection commands --- src/tools/amduat_asl_cli.c | 708 +++++++++++++++++++++++++++++++++++++ 1 file changed, 708 insertions(+) diff --git a/src/tools/amduat_asl_cli.c b/src/tools/amduat_asl_cli.c index 53f6d92..0b575fd 100644 --- a/src/tools/amduat_asl_cli.c +++ b/src/tools/amduat_asl_cli.c @@ -5,15 +5,24 @@ #include "amduat/asl/parse.h" #include "amduat/asl/ref_io.h" #include "amduat/asl/ref_text.h" +#include "amduat/asl/asl_store_index_fs.h" #include "amduat/asl/store.h" +#include "amduat/enc/asl1_core.h" #include "amduat/enc/asl1_core_codec.h" +#include "amduat/enc/asl_core_index.h" +#include "amduat/enc/asl_log.h" #include "amduat/format/parse.h" #include "amduat/format/ref.h" +#include "amduat/hash/asl1.h" +#include +#include +#include #include #include #include #include #include +#include enum { AMDUAT_ASL_CLI_EXIT_OK = 0, @@ -75,6 +84,9 @@ static void amduat_asl_cli_print_usage(FILE *stream) { " [--output-format raw|artifact]\n" " [--expect-type-tag TAG] [--print-type-tag]\n" " [--quiet]\n" + " amduat-asl log inspect [--root PATH]\n" + " amduat-asl index state [--root PATH]\n" + " amduat-asl segment verify [--root PATH] [--segment ID]\n" "\n" "defaults:\n" " --root %s\n" @@ -143,6 +155,365 @@ static int amduat_asl_cli_map_store_error(amduat_asl_store_error_t err) { } } +static bool amduat_asl_cli_join_path(const char *base, + const char *suffix, + char **out_path) { + size_t base_len; + size_t suffix_len; + bool needs_sep; + size_t total_len; + char *buffer; + size_t offset; + + if (base == NULL || suffix == NULL || out_path == NULL) { + return false; + } + if (base[0] == '\0' || suffix[0] == '\0') { + return false; + } + + base_len = strlen(base); + suffix_len = strlen(suffix); + needs_sep = base[base_len - 1u] != '/'; + total_len = base_len + (needs_sep ? 1u : 0u) + suffix_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, suffix, suffix_len); + offset += suffix_len; + buffer[offset] = '\0'; + + *out_path = buffer; + return true; +} + +static bool amduat_asl_cli_build_index_path(const char *root_path, + char **out_path) { + return amduat_asl_cli_join_path(root_path, "index", out_path); +} + +static bool amduat_asl_cli_build_log_path(const char *root_path, + char **out_path) { + char *index_path; + bool ok; + + if (!amduat_asl_cli_build_index_path(root_path, &index_path)) { + return false; + } + ok = amduat_asl_cli_join_path(index_path, "log.asl", out_path); + free(index_path); + return ok; +} + +static bool amduat_asl_cli_build_segments_path(const char *root_path, + char **out_path) { + char *index_path; + bool ok; + + if (!amduat_asl_cli_build_index_path(root_path, &index_path)) { + return false; + } + ok = amduat_asl_cli_join_path(index_path, "segments", out_path); + free(index_path); + return ok; +} + +static bool amduat_asl_cli_build_segment_path(const char *root_path, + uint64_t segment_id, + char **out_path) { + char name[32]; + char *segments_path; + bool ok; + + if (!amduat_asl_cli_build_segments_path(root_path, &segments_path)) { + return false; + } + snprintf(name, sizeof(name), "segment-%016" PRIx64 ".asl", segment_id); + ok = amduat_asl_cli_join_path(segments_path, name, out_path); + free(segments_path); + return ok; +} + +static bool amduat_asl_cli_path_is_dir(const char *path) { + struct stat st; + + if (path == NULL || path[0] == '\0') { + return false; + } + if (stat(path, &st) != 0) { + return false; + } + return S_ISDIR(st.st_mode); +} + +static bool amduat_asl_cli_path_is_file(const char *path) { + struct stat st; + + if (path == NULL || path[0] == '\0') { + return false; + } + if (stat(path, &st) != 0) { + return false; + } + return S_ISREG(st.st_mode); +} + +static bool amduat_asl_cli_is_index_store_root(const char *root_path) { + char *index_path; + bool ok; + + if (!amduat_asl_cli_build_index_path(root_path, &index_path)) { + return false; + } + ok = amduat_asl_cli_path_is_dir(index_path); + free(index_path); + return ok; +} + +static bool amduat_asl_cli_read_u64_le(const uint8_t *bytes, + size_t len, + uint64_t *out_value) { + uint64_t value; + + if (bytes == NULL || out_value == NULL || len < 8u) { + return false; + } + value = 0u; + value |= (uint64_t)bytes[0]; + value |= (uint64_t)bytes[1] << 8; + value |= (uint64_t)bytes[2] << 16; + value |= (uint64_t)bytes[3] << 24; + value |= (uint64_t)bytes[4] << 32; + value |= (uint64_t)bytes[5] << 40; + value |= (uint64_t)bytes[6] << 48; + value |= (uint64_t)bytes[7] << 56; + *out_value = value; + return true; +} + +static bool amduat_asl_cli_parse_segment_id(const char *value, + uint64_t *out_segment_id) { + char *end; + unsigned long long parsed; + + if (value == NULL || out_segment_id == NULL) { + return false; + } + + errno = 0; + parsed = strtoull(value, &end, 0); + if (errno != 0 || end == value || *end != '\0') { + return false; + } + *out_segment_id = (uint64_t)parsed; + return true; +} + +static bool amduat_asl_cli_parse_segment_name(const char *name, + uint64_t *out_segment_id) { + const char *prefix = "segment-"; + const char *suffix = ".asl"; + size_t name_len; + const char *hex_start; + size_t hex_len; + char hex_buf[17]; + char *end; + unsigned long long parsed; + + if (name == NULL || out_segment_id == NULL) { + return false; + } + + name_len = strlen(name); + if (name_len != 28u) { + return false; + } + if (strncmp(name, prefix, 8) != 0) { + return false; + } + if (strcmp(name + name_len - 4u, suffix) != 0) { + return false; + } + + hex_start = name + 8; + hex_len = 16u; + memcpy(hex_buf, hex_start, hex_len); + hex_buf[hex_len] = '\0'; + + errno = 0; + parsed = strtoull(hex_buf, &end, 16); + if (errno != 0 || end == hex_buf || *end != '\0') { + return false; + } + *out_segment_id = (uint64_t)parsed; + return true; +} + +static int amduat_asl_cli_segment_id_cmp(const void *lhs, const void *rhs) { + const uint64_t *a = (const uint64_t *)lhs; + const uint64_t *b = (const uint64_t *)rhs; + + if (*a < *b) { + return -1; + } + if (*a > *b) { + return 1; + } + return 0; +} + +static bool amduat_asl_cli_collect_segment_ids(const char *root_path, + uint64_t **out_ids, + size_t *out_len) { + char *segments_path; + DIR *dir; + struct dirent *entry; + uint64_t *ids; + size_t ids_len; + size_t ids_cap; + + if (out_ids == NULL || out_len == NULL) { + return false; + } + *out_ids = NULL; + *out_len = 0u; + + if (!amduat_asl_cli_build_segments_path(root_path, &segments_path)) { + return false; + } + if (!amduat_asl_cli_path_is_dir(segments_path)) { + free(segments_path); + return false; + } + + dir = opendir(segments_path); + free(segments_path); + if (dir == NULL) { + return false; + } + + ids = NULL; + ids_len = 0u; + ids_cap = 0u; + + while ((entry = readdir(dir)) != NULL) { + uint64_t segment_id; + uint64_t *next; + + if (!amduat_asl_cli_parse_segment_name(entry->d_name, &segment_id)) { + continue; + } + if (ids_len == ids_cap) { + size_t next_cap = ids_cap == 0u ? 8u : ids_cap * 2u; + next = (uint64_t *)realloc(ids, next_cap * sizeof(*next)); + if (next == NULL) { + free(ids); + closedir(dir); + return false; + } + ids = next; + ids_cap = next_cap; + } + ids[ids_len++] = segment_id; + } + + closedir(dir); + if (ids_len != 0u) { + qsort(ids, ids_len, sizeof(*ids), amduat_asl_cli_segment_id_cmp); + } + *out_ids = ids; + *out_len = ids_len; + return true; +} + +static bool amduat_asl_cli_load_log_records( + const char *root_path, + amduat_asl_log_record_t **out_records, + size_t *out_count) { + char *log_path; + uint8_t *log_bytes; + size_t log_len; + bool ok; + + if (out_records == NULL || out_count == NULL) { + return false; + } + *out_records = NULL; + *out_count = 0u; + + if (!amduat_asl_cli_build_log_path(root_path, &log_path)) { + return false; + } + if (!amduat_asl_cli_path_is_file(log_path)) { + free(log_path); + return false; + } + + log_bytes = NULL; + log_len = 0u; + if (!amduat_asl_read_path(log_path, &log_bytes, &log_len)) { + free(log_path); + return false; + } + free(log_path); + + ok = amduat_enc_asl_log_decode_v1(amduat_octets(log_bytes, log_len), + out_records, out_count); + free(log_bytes); + return ok; +} + +static int amduat_asl_cli_log_find_segment_hash( + const amduat_asl_log_record_t *records, + size_t record_count, + uint64_t segment_id, + uint8_t out_hash[32]) { + size_t i; + int status; + + if (out_hash == NULL) { + return -1; + } + memset(out_hash, 0, 32u); + + if (records == NULL || record_count == 0u) { + return 0; + } + + status = 0; + for (i = 0; i < record_count; ++i) { + const amduat_asl_log_record_t *record = &records[i]; + uint64_t payload_segment_id; + if (record->record_type != AMDUAT_ASL_LOG_RECORD_SEGMENT_SEAL) { + continue; + } + if (record->payload.len < 40u) { + return -1; + } + if (!amduat_asl_cli_read_u64_le(record->payload.data, + record->payload.len, + &payload_segment_id)) { + return -1; + } + if (payload_segment_id != segment_id) { + continue; + } + memcpy(out_hash, record->payload.data + 8, 32u); + status = 1; + } + + return status; +} + static int amduat_asl_cli_cmd_init(int argc, char **argv) { amduat_asl_cli_init_opts_t opts; amduat_asl_store_fs_config_t cfg_in; @@ -596,6 +967,334 @@ static int amduat_asl_cli_cmd_get(int argc, char **argv) { return exit_code; } +static int amduat_asl_cli_cmd_log(int argc, char **argv) { + const char *root; + uint8_t *log_bytes; + size_t log_len; + amduat_asl_log_record_t *records; + size_t record_count; + char *log_path; + size_t i; + + if (argc < 1) { + fprintf(stderr, "error: log command requires a subcommand\n"); + return AMDUAT_ASL_CLI_EXIT_USAGE; + } + if (strcmp(argv[0], "inspect") != 0) { + fprintf(stderr, "error: unknown log command: %s\n", argv[0]); + return AMDUAT_ASL_CLI_EXIT_USAGE; + } + + root = AMDUAT_ASL_CLI_DEFAULT_ROOT; + for (i = 1; i < (size_t)argc; ++i) { + if (strcmp(argv[i], "--root") == 0) { + if (i + 1 >= (size_t)argc) { + fprintf(stderr, "error: --root requires a path\n"); + return AMDUAT_ASL_CLI_EXIT_USAGE; + } + root = argv[++i]; + } else if (strcmp(argv[i], "--help") == 0 || + strcmp(argv[i], "-h") == 0) { + amduat_asl_cli_print_usage(stdout); + return AMDUAT_ASL_CLI_EXIT_OK; + } else { + fprintf(stderr, "error: unknown option: %s\n", argv[i]); + return AMDUAT_ASL_CLI_EXIT_USAGE; + } + } + + if (!amduat_asl_cli_is_index_store_root(root)) { + fprintf(stderr, "error: index/log store not found at: %s\n", root); + return AMDUAT_ASL_CLI_EXIT_CONFIG; + } + if (!amduat_asl_cli_build_log_path(root, &log_path)) { + fprintf(stderr, "error: failed to build log path\n"); + return AMDUAT_ASL_CLI_EXIT_IO; + } + if (!amduat_asl_cli_path_is_file(log_path)) { + fprintf(stderr, "error: log file not found: %s\n", log_path); + free(log_path); + return AMDUAT_ASL_CLI_EXIT_NOT_FOUND; + } + + log_bytes = NULL; + log_len = 0u; + if (!amduat_asl_read_path(log_path, &log_bytes, &log_len)) { + fprintf(stderr, "error: failed to read log: %s\n", log_path); + free(log_path); + return AMDUAT_ASL_CLI_EXIT_IO; + } + free(log_path); + + records = NULL; + record_count = 0u; + if (!amduat_enc_asl_log_decode_v1(amduat_octets(log_bytes, log_len), + &records, &record_count)) { + free(log_bytes); + fprintf(stderr, "error: invalid log encoding\n"); + return AMDUAT_ASL_CLI_EXIT_CODEC; + } + free(log_bytes); + + for (i = 0; i < record_count; ++i) { + const amduat_asl_log_record_t *record = &records[i]; + printf("logseq=%" PRIu64 " type=0x%08x\n", + record->logseq, + (unsigned int)record->record_type); + } + + amduat_enc_asl_log_free(records, record_count); + return AMDUAT_ASL_CLI_EXIT_OK; +} + +static int amduat_asl_cli_cmd_index(int argc, char **argv) { + const char *root; + amduat_asl_store_fs_config_t cfg; + amduat_asl_store_index_fs_t index_fs; + amduat_asl_store_t store; + amduat_asl_index_state_t state; + size_t i; + + if (argc < 1) { + fprintf(stderr, "error: index command requires a subcommand\n"); + return AMDUAT_ASL_CLI_EXIT_USAGE; + } + if (strcmp(argv[0], "state") != 0) { + fprintf(stderr, "error: unknown index command: %s\n", argv[0]); + return AMDUAT_ASL_CLI_EXIT_USAGE; + } + + root = AMDUAT_ASL_CLI_DEFAULT_ROOT; + for (i = 1; i < (size_t)argc; ++i) { + if (strcmp(argv[i], "--root") == 0) { + if (i + 1 >= (size_t)argc) { + fprintf(stderr, "error: --root requires a path\n"); + return AMDUAT_ASL_CLI_EXIT_USAGE; + } + root = argv[++i]; + } else if (strcmp(argv[i], "--help") == 0 || + strcmp(argv[i], "-h") == 0) { + amduat_asl_cli_print_usage(stdout); + return AMDUAT_ASL_CLI_EXIT_OK; + } else { + fprintf(stderr, "error: unknown option: %s\n", argv[i]); + return AMDUAT_ASL_CLI_EXIT_USAGE; + } + } + + if (!amduat_asl_cli_is_index_store_root(root)) { + fprintf(stderr, "error: index/log store not found at: %s\n", root); + return AMDUAT_ASL_CLI_EXIT_CONFIG; + } + if (!amduat_asl_store_fs_load_config(root, &cfg)) { + fprintf(stderr, "error: failed to load store config: %s\n", root); + return AMDUAT_ASL_CLI_EXIT_CONFIG; + } + if (!amduat_asl_store_index_fs_init(&index_fs, cfg.config, root)) { + fprintf(stderr, "error: failed to initialize index store\n"); + return AMDUAT_ASL_CLI_EXIT_CONFIG; + } + amduat_asl_store_init(&store, cfg.config, + amduat_asl_store_index_fs_ops(), &index_fs); + + if (!amduat_asl_index_current_state(&store, &state)) { + fprintf(stderr, "error: failed to read index state\n"); + return AMDUAT_ASL_CLI_EXIT_STORE; + } + + printf("snapshot_id=%" PRIu64 "\n", state.snapshot_id); + printf("log_position=%" PRIu64 "\n", state.log_position); + return AMDUAT_ASL_CLI_EXIT_OK; +} + +static int amduat_asl_cli_cmd_segment(int argc, char **argv) { + const char *root; + bool has_segment; + uint64_t segment_id; + uint64_t *segment_ids; + size_t segment_count; + size_t i; + amduat_asl_log_record_t *log_records; + size_t log_count; + bool have_log; + bool all_ok; + + if (argc < 1) { + fprintf(stderr, "error: segment command requires a subcommand\n"); + return AMDUAT_ASL_CLI_EXIT_USAGE; + } + if (strcmp(argv[0], "verify") != 0) { + fprintf(stderr, "error: unknown segment command: %s\n", argv[0]); + return AMDUAT_ASL_CLI_EXIT_USAGE; + } + + root = AMDUAT_ASL_CLI_DEFAULT_ROOT; + has_segment = false; + segment_id = 0u; + for (i = 1; i < (size_t)argc; ++i) { + if (strcmp(argv[i], "--root") == 0) { + if (i + 1 >= (size_t)argc) { + fprintf(stderr, "error: --root requires a path\n"); + return AMDUAT_ASL_CLI_EXIT_USAGE; + } + root = argv[++i]; + } else if (strcmp(argv[i], "--segment") == 0) { + if (i + 1 >= (size_t)argc) { + fprintf(stderr, "error: --segment requires an id\n"); + return AMDUAT_ASL_CLI_EXIT_USAGE; + } + if (!amduat_asl_cli_parse_segment_id(argv[++i], &segment_id)) { + fprintf(stderr, "error: invalid segment id\n"); + return AMDUAT_ASL_CLI_EXIT_USAGE; + } + has_segment = true; + } else if (strcmp(argv[i], "--help") == 0 || + strcmp(argv[i], "-h") == 0) { + amduat_asl_cli_print_usage(stdout); + return AMDUAT_ASL_CLI_EXIT_OK; + } else { + fprintf(stderr, "error: unknown option: %s\n", argv[i]); + return AMDUAT_ASL_CLI_EXIT_USAGE; + } + } + + if (!amduat_asl_cli_is_index_store_root(root)) { + fprintf(stderr, "error: index/log store not found at: %s\n", root); + return AMDUAT_ASL_CLI_EXIT_CONFIG; + } + + log_records = NULL; + log_count = 0u; + have_log = amduat_asl_cli_load_log_records(root, &log_records, &log_count); + if (!have_log) { + char *log_path; + bool log_exists; + + log_path = NULL; + log_exists = false; + if (amduat_asl_cli_build_log_path(root, &log_path)) { + log_exists = amduat_asl_cli_path_is_file(log_path); + free(log_path); + } + if (log_exists) { + fprintf(stderr, "error: failed to load log records\n"); + return AMDUAT_ASL_CLI_EXIT_CODEC; + } + } + + segment_ids = NULL; + segment_count = 0u; + if (has_segment) { + segment_ids = (uint64_t *)malloc(sizeof(*segment_ids)); + if (segment_ids == NULL) { + amduat_enc_asl_log_free(log_records, log_count); + fprintf(stderr, "error: out of memory\n"); + return AMDUAT_ASL_CLI_EXIT_IO; + } + segment_ids[0] = segment_id; + segment_count = 1u; + } else { + if (!amduat_asl_cli_collect_segment_ids(root, &segment_ids, + &segment_count)) { + amduat_enc_asl_log_free(log_records, log_count); + fprintf(stderr, "error: failed to read segments directory\n"); + return AMDUAT_ASL_CLI_EXIT_IO; + } + } + + if (segment_count == 0u) { + amduat_enc_asl_log_free(log_records, log_count); + free(segment_ids); + fprintf(stderr, "error: no segments found\n"); + return AMDUAT_ASL_CLI_EXIT_NOT_FOUND; + } + + all_ok = true; + for (i = 0; i < segment_count; ++i) { + char *segment_path; + uint8_t *segment_bytes; + size_t segment_len; + uint8_t segment_hash[32]; + uint8_t log_hash[32]; + amduat_asl_core_index_segment_t segment; + bool ok; + int hash_status; + bool hash_match; + + if (!amduat_asl_cli_build_segment_path(root, segment_ids[i], + &segment_path)) { + fprintf(stderr, "error: failed to build segment path\n"); + printf("segment=0x%016" PRIx64 " FAIL\n", segment_ids[i]); + all_ok = false; + continue; + } + + segment_bytes = NULL; + segment_len = 0u; + if (!amduat_asl_read_path(segment_path, &segment_bytes, &segment_len)) { + fprintf(stderr, "error: failed to read segment: %s\n", segment_path); + free(segment_path); + printf("segment=0x%016" PRIx64 " FAIL\n", segment_ids[i]); + all_ok = false; + continue; + } + free(segment_path); + + ok = amduat_hash_asl1_digest(AMDUAT_HASH_ASL1_ID_SHA256, + amduat_octets(segment_bytes, segment_len), + segment_hash, + sizeof(segment_hash)); + if (!ok) { + fprintf(stderr, "error: failed to hash segment\n"); + free(segment_bytes); + printf("segment=0x%016" PRIx64 " FAIL\n", segment_ids[i]); + all_ok = false; + continue; + } + + memset(&segment, 0, sizeof(segment)); + ok = amduat_enc_asl_core_index_decode_v1( + amduat_octets(segment_bytes, segment_len), &segment); + free(segment_bytes); + if (!ok) { + fprintf(stderr, "error: invalid segment encoding\n"); + printf("segment=0x%016" PRIx64 " FAIL\n", segment_ids[i]); + all_ok = false; + continue; + } + amduat_enc_asl_core_index_free(&segment); + + hash_match = true; + hash_status = 0; + if (have_log) { + hash_status = amduat_asl_cli_log_find_segment_hash( + log_records, log_count, segment_ids[i], log_hash); + if (hash_status < 0) { + fprintf(stderr, "error: invalid log record payload\n"); + printf("segment=0x%016" PRIx64 " FAIL\n", segment_ids[i]); + all_ok = false; + continue; + } + if (hash_status > 0) { + hash_match = memcmp(segment_hash, log_hash, sizeof(log_hash)) == 0; + } + } + + if (!hash_match) { + fprintf(stderr, "error: segment hash mismatch\n"); + printf("segment=0x%016" PRIx64 " FAIL\n", segment_ids[i]); + all_ok = false; + } else { + printf("segment=0x%016" PRIx64 " OK\n", segment_ids[i]); + } + } + + amduat_enc_asl_log_free(log_records, log_count); + free(segment_ids); + + return all_ok ? AMDUAT_ASL_CLI_EXIT_OK : AMDUAT_ASL_CLI_EXIT_STORE; +} + int main(int argc, char **argv) { if (argc < 2) { amduat_asl_cli_print_usage(stderr); @@ -611,6 +1310,15 @@ int main(int argc, char **argv) { if (strcmp(argv[1], "get") == 0) { return amduat_asl_cli_cmd_get(argc - 2, argv + 2); } + if (strcmp(argv[1], "log") == 0) { + return amduat_asl_cli_cmd_log(argc - 2, argv + 2); + } + if (strcmp(argv[1], "index") == 0) { + return amduat_asl_cli_cmd_index(argc - 2, argv + 2); + } + if (strcmp(argv[1], "segment") == 0) { + return amduat_asl_cli_cmd_segment(argc - 2, argv + 2); + } if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0) { amduat_asl_cli_print_usage(stdout); return AMDUAT_ASL_CLI_EXIT_OK;