Add ASL index/log inspection commands

This commit is contained in:
Carl Niklas Rydberg 2026-01-17 14:26:17 +01:00
parent ac1ce381a2
commit 556c65a54e

View file

@ -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 <dirent.h>
#include <errno.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
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;