From e2d26e53cd324513de98c842f8b50d4360642f42 Mon Sep 17 00:00:00 2001 From: Carl Niklas Rydberg Date: Fri, 23 Jan 2026 19:04:49 +0100 Subject: [PATCH] Add CAS-native append-only log --- CMakeLists.txt | 15 + include/amduat/asl/asl_pointer_fs.h | 58 ++ include/amduat/asl/log_store.h | 62 ++ src/adapters/asl_pointer_fs/asl_pointer_fs.c | 726 +++++++++++++++ src/core/asl_log_store.c | 905 +++++++++++++++++++ src/tools/amduat_pel_cli.c | 455 ++++++++++ 6 files changed, 2221 insertions(+) create mode 100644 include/amduat/asl/asl_pointer_fs.h create mode 100644 include/amduat/asl/log_store.h create mode 100644 src/adapters/asl_pointer_fs/asl_pointer_fs.c create mode 100644 src/core/asl_log_store.c diff --git a/CMakeLists.txt b/CMakeLists.txt index e862265..e9ae7ab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,6 +75,10 @@ set(AMDUAT_ASL_SRCS src/near_core/asl/registry.c ) +set(AMDUAT_ASL_LOG_STORE_SRCS + src/core/asl_log_store.c +) + set(AMDUAT_HASH_ASL1_SRCS src/near_core/hash/asl1.c src/near_core/hash/sha256.c @@ -150,6 +154,10 @@ set(AMDUAT_ASL_MATERIALIZATION_CACHE_FS_SRCS src/adapters/asl_materialization_cache_fs/asl_materialization_cache_fs.c ) +set(AMDUAT_ASL_POINTER_FS_SRCS + src/adapters/asl_pointer_fs/asl_pointer_fs.c +) + set(AMDUAT_TGK_STORE_MEM_SRCS src/adapters/tgk_store_mem/tgk_store_mem.c ) @@ -201,6 +209,12 @@ amduat_add_lib(asl_materialization_cache_fs SRCS ${AMDUAT_ASL_MATERIALIZATION_CA amduat_link(asl_materialization_cache_fs amduat_asl amduat_enc amduat_hash_asl1 amduat_util) target_compile_definitions(amduat_asl_materialization_cache_fs_obj PRIVATE _POSIX_C_SOURCE=200809L) +amduat_add_lib(asl_pointer_fs SRCS ${AMDUAT_ASL_POINTER_FS_SRCS}) +amduat_link(asl_pointer_fs amduat_asl amduat_enc amduat_hash_asl1 amduat_util) +target_compile_definitions(amduat_asl_pointer_fs_obj PRIVATE _POSIX_C_SOURCE=200809L) + +amduat_add_lib(asl_log_store SRCS ${AMDUAT_ASL_LOG_STORE_SRCS}) +amduat_link(asl_log_store amduat_asl_pointer_fs amduat_asl amduat_enc amduat_util) amduat_add_lib(tgk_store_mem SRCS ${AMDUAT_TGK_STORE_MEM_SRCS}) amduat_link(tgk_store_mem amduat_tgk amduat_asl amduat_enc amduat_hash_asl1 amduat_util) @@ -254,6 +268,7 @@ target_include_directories(amduat_pel_cli ) target_link_libraries(amduat_pel_cli PRIVATE amduat_format amduat_pel amduat_asl_store_fs + amduat_asl_log_store amduat_asl_pointer_fs amduat_asl_derivation_index_fs amduat_asl amduat_enc amduat_hash_asl1 amduat_util ) diff --git a/include/amduat/asl/asl_pointer_fs.h b/include/amduat/asl/asl_pointer_fs.h new file mode 100644 index 0000000..018ab96 --- /dev/null +++ b/include/amduat/asl/asl_pointer_fs.h @@ -0,0 +1,58 @@ +#ifndef AMDUAT_ASL_POINTER_FS_H +#define AMDUAT_ASL_POINTER_FS_H + +#include "amduat/asl/core.h" + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + AMDUAT_ASL_POINTER_FS_ROOT_MAX = 1024, + AMDUAT_ASL_POINTER_NAME_MAX = 512 +}; + +typedef enum { + AMDUAT_ASL_POINTER_OK = 0, + AMDUAT_ASL_POINTER_ERR_NOT_FOUND = 1, + AMDUAT_ASL_POINTER_ERR_IO = 2, + AMDUAT_ASL_POINTER_ERR_INVALID_NAME = 3, + AMDUAT_ASL_POINTER_ERR_INTEGRITY = 4 +} amduat_asl_pointer_error_t; + +typedef struct { + char root_path[AMDUAT_ASL_POINTER_FS_ROOT_MAX]; +} amduat_asl_pointer_store_t; + +/* Pointers are generic control-plane names for artifacts (e.g. dataset heads). + * Example names: + * - space/123/dataset/calendar_types/head + * - space/123/collection/events/head + */ +bool amduat_asl_pointer_store_init(amduat_asl_pointer_store_t *ps, + const char *root_path); + +amduat_asl_pointer_error_t amduat_asl_pointer_get( + const amduat_asl_pointer_store_t *ps, + const char *name, + bool *out_exists, + amduat_reference_t *out_ref); + +amduat_asl_pointer_error_t amduat_asl_pointer_cas( + const amduat_asl_pointer_store_t *ps, + const char *name, + bool expected_exists, + const amduat_reference_t *expected_ref, + const amduat_reference_t *new_ref, + bool *out_swapped); + +bool amduat_asl_pointer_name_is_valid(const char *name); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* AMDUAT_ASL_POINTER_FS_H */ diff --git a/include/amduat/asl/log_store.h b/include/amduat/asl/log_store.h new file mode 100644 index 0000000..5dab443 --- /dev/null +++ b/include/amduat/asl/log_store.h @@ -0,0 +1,62 @@ +#ifndef AMDUAT_ASL_LOG_STORE_H +#define AMDUAT_ASL_LOG_STORE_H + +#include "amduat/asl/asl_pointer_fs.h" +#include "amduat/asl/core.h" +#include "amduat/asl/store.h" + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +enum { TYPE_TAG_ASL_LOG_CHUNK_1 = 0x00000401u }; +enum { AMDUAT_TYPE_TAG_ASL_LOG_CHUNK_1 = TYPE_TAG_ASL_LOG_CHUNK_1 }; + +typedef struct { + uint16_t kind; + bool has_timestamp; + uint64_t timestamp; + amduat_reference_t payload_ref; + bool has_actor; + amduat_octets_t actor; +} amduat_asl_log_entry_t; + +typedef struct { + amduat_asl_store_t *store; + amduat_asl_pointer_store_t pointer_store; +} amduat_asl_log_store_t; + +bool amduat_asl_log_store_init(amduat_asl_log_store_t *log_store, + const char *root_path, + amduat_asl_store_t *store, + const amduat_asl_pointer_store_t *pointer_store); + +amduat_asl_store_error_t amduat_asl_log_append( + amduat_asl_log_store_t *log_store, + const char *log_name, + const amduat_asl_log_entry_t *entries, + size_t entries_len, + uint64_t *out_first_offset); + +amduat_asl_store_error_t amduat_asl_log_read( + amduat_asl_log_store_t *log_store, + const char *log_name, + uint64_t from_offset, + size_t max_entries, + amduat_asl_log_entry_t **out_entries, + size_t *out_len, + uint64_t *out_next_offset, + bool *out_end); + +void amduat_asl_log_entries_free(amduat_asl_log_entry_t *entries, + size_t entries_len); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* AMDUAT_ASL_LOG_STORE_H */ diff --git a/src/adapters/asl_pointer_fs/asl_pointer_fs.c b/src/adapters/asl_pointer_fs/asl_pointer_fs.c new file mode 100644 index 0000000..49666b8 --- /dev/null +++ b/src/adapters/asl_pointer_fs/asl_pointer_fs.c @@ -0,0 +1,726 @@ +#include "amduat/asl/asl_pointer_fs.h" + +#include "amduat/enc/asl1_core_codec.h" +#include "amduat/util/log.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum { + AMDUAT_ASL_POINTER_MAGIC_LEN = 8, + AMDUAT_ASL_POINTER_VERSION = 1 +}; + +static const uint8_t k_amduat_asl_pointer_magic[AMDUAT_ASL_POINTER_MAGIC_LEN] = { + 'A', 'S', 'L', 'P', 'T', 'R', '1', '\0' +}; + +enum { AMDUAT_ASL_POINTER_FLAG_HAS_EXPECTED = 1u << 0, + AMDUAT_ASL_POINTER_FLAG_HAS_PREV = 1u << 1 }; + +typedef struct { + const uint8_t *data; + size_t len; + size_t offset; +} amduat_asl_pointer_cursor_t; + +static void amduat_asl_pointer_store_u32_le(uint8_t *out, uint32_t value) { + out[0] = (uint8_t)(value & 0xffu); + out[1] = (uint8_t)((value >> 8) & 0xffu); + out[2] = (uint8_t)((value >> 16) & 0xffu); + out[3] = (uint8_t)((value >> 24) & 0xffu); +} + +static bool amduat_asl_pointer_read_u32_le(amduat_asl_pointer_cursor_t *cur, + uint32_t *out) { + const uint8_t *data; + + if (cur->len - cur->offset < 4u) { + return false; + } + data = cur->data + cur->offset; + *out = (uint32_t)data[0] | ((uint32_t)data[1] << 8) | + ((uint32_t)data[2] << 16) | ((uint32_t)data[3] << 24); + cur->offset += 4u; + return true; +} + +static bool amduat_asl_pointer_read_u8(amduat_asl_pointer_cursor_t *cur, + uint8_t *out) { + if (cur->len - cur->offset < 1u) { + return false; + } + *out = cur->data[cur->offset++]; + return true; +} + +static bool amduat_asl_pointer_join_path(const char *base, + const char *segment, + char **out_path) { + size_t base_len; + size_t segment_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); + segment_len = strlen(segment); + needs_sep = base[base_len - 1u] != '/'; + total_len = base_len + (needs_sep ? 1u : 0u) + segment_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, segment_len); + offset += segment_len; + buffer[offset] = '\0'; + + *out_path = buffer; + return true; +} + +static bool amduat_asl_pointer_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 char *amduat_asl_pointer_parent_dir(const char *path) { + const char *slash; + size_t len; + char *dir; + + if (path == NULL) { + return NULL; + } + slash = strrchr(path, '/'); + if (slash == NULL || slash == path) { + return NULL; + } + len = (size_t)(slash - path); + dir = (char *)malloc(len + 1u); + if (dir == NULL) { + return NULL; + } + memcpy(dir, path, len); + dir[len] = '\0'; + return dir; +} + +static bool amduat_asl_pointer_fsync_directory(const char *path) { + int fd; + int flags = O_RDONLY; + + if (path == NULL) { + return false; + } +#ifdef O_DIRECTORY + flags |= O_DIRECTORY; +#endif + fd = open(path, flags); + if (fd < 0) { + return false; + } + if (fsync(fd) != 0) { + close(fd); + return false; + } + return close(fd) == 0; +} + +bool amduat_asl_pointer_name_is_valid(const char *name) { + size_t len; + size_t seg_start = 0u; + + if (name == NULL) { + return false; + } + len = strlen(name); + if (len == 0u || len > AMDUAT_ASL_POINTER_NAME_MAX) { + return false; + } + if (name[0] == '/' || name[len - 1u] == '/') { + return false; + } + + for (size_t i = 0u; i < len; ++i) { + char c = name[i]; + bool ok = (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || + c == '.' || c == '_' || c == '-' || c == '/'; + if (!ok) { + return false; + } + if (c == '/') { + size_t seg_len = i - seg_start; + if (seg_len == 0u) { + return false; + } + if (seg_len == 2u && name[seg_start] == '.' && + name[seg_start + 1u] == '.') { + return false; + } + seg_start = i + 1u; + } + } + + { + size_t seg_len = len - seg_start; + if (seg_len == 0u) { + return false; + } + if (seg_len == 2u && name[seg_start] == '.' && + name[seg_start + 1u] == '.') { + return false; + } + } + + return true; +} + +static bool amduat_asl_pointer_build_head_path(const char *root_path, + const char *name, + bool ensure_dirs, + char **out_path) { + char *pointers_path = NULL; + char *cursor_path = NULL; + char *head_path = NULL; + const char *segment_start; + const char *segment_end; + bool ok = false; + + if (root_path == NULL || name == NULL || out_path == NULL) { + return false; + } + *out_path = NULL; + + if (!amduat_asl_pointer_join_path(root_path, "pointers", &pointers_path)) { + return false; + } + if (ensure_dirs && + !amduat_asl_pointer_ensure_directory(pointers_path)) { + free(pointers_path); + return false; + } + + cursor_path = pointers_path; + pointers_path = NULL; + + segment_start = name; + while (*segment_start != '\0') { + segment_end = strchr(segment_start, '/'); + if (segment_end == NULL) { + segment_end = segment_start + strlen(segment_start); + } + { + size_t seg_len = (size_t)(segment_end - segment_start); + char *segment = (char *)malloc(seg_len + 1u); + char *next_path = NULL; + if (segment == NULL) { + goto cleanup; + } + memcpy(segment, segment_start, seg_len); + segment[seg_len] = '\0'; + if (!amduat_asl_pointer_join_path(cursor_path, segment, &next_path)) { + free(segment); + goto cleanup; + } + free(segment); + if (ensure_dirs && + !amduat_asl_pointer_ensure_directory(next_path)) { + free(next_path); + goto cleanup; + } + free(cursor_path); + cursor_path = next_path; + } + if (*segment_end == '\0') { + break; + } + segment_start = segment_end + 1u; + } + + if (!amduat_asl_pointer_join_path(cursor_path, "head", &head_path)) { + goto cleanup; + } + + *out_path = head_path; + head_path = NULL; + ok = true; + +cleanup: + free(pointers_path); + free(cursor_path); + free(head_path); + return ok; +} + +bool amduat_asl_pointer_store_init(amduat_asl_pointer_store_t *ps, + const char *root_path) { + size_t len; + + if (ps == NULL || root_path == NULL) { + return false; + } + len = strlen(root_path); + if (len == 0u || len >= AMDUAT_ASL_POINTER_FS_ROOT_MAX) { + return false; + } + memcpy(ps->root_path, root_path, len); + ps->root_path[len] = '\0'; + return true; +} + +static amduat_asl_pointer_error_t amduat_asl_pointer_read_head( + const char *path, + const char *name, + bool *out_exists, + amduat_reference_t *out_ref, + amduat_reference_t *out_prev_ref, + bool *out_has_prev) { + FILE *fp; + uint8_t *buffer; + long file_size; + amduat_asl_pointer_cursor_t cur; + uint32_t version; + uint32_t name_len; + uint32_t ref_len; + uint32_t prev_len; + uint8_t flags; + amduat_octets_t ref_bytes; + amduat_octets_t prev_bytes; + + if (out_exists == NULL || out_ref == NULL) { + return AMDUAT_ASL_POINTER_ERR_INTEGRITY; + } + *out_exists = false; + memset(out_ref, 0, sizeof(*out_ref)); + if (out_prev_ref != NULL) { + memset(out_prev_ref, 0, sizeof(*out_prev_ref)); + } + if (out_has_prev != NULL) { + *out_has_prev = false; + } + + fp = fopen(path, "rb"); + if (fp == NULL) { + return errno == ENOENT ? AMDUAT_ASL_POINTER_ERR_NOT_FOUND + : AMDUAT_ASL_POINTER_ERR_IO; + } + if (fseek(fp, 0, SEEK_END) != 0) { + fclose(fp); + return AMDUAT_ASL_POINTER_ERR_IO; + } + file_size = ftell(fp); + if (file_size < 0) { + fclose(fp); + return AMDUAT_ASL_POINTER_ERR_IO; + } + if (file_size == 0) { + fclose(fp); + return AMDUAT_ASL_POINTER_ERR_NOT_FOUND; + } + if (fseek(fp, 0, SEEK_SET) != 0) { + fclose(fp); + return AMDUAT_ASL_POINTER_ERR_IO; + } + + buffer = (uint8_t *)malloc((size_t)file_size); + if (buffer == NULL) { + fclose(fp); + return AMDUAT_ASL_POINTER_ERR_IO; + } + if (fread(buffer, 1u, (size_t)file_size, fp) != (size_t)file_size) { + free(buffer); + fclose(fp); + return AMDUAT_ASL_POINTER_ERR_IO; + } + fclose(fp); + + cur.data = buffer; + cur.len = (size_t)file_size; + cur.offset = 0u; + + if (cur.len < AMDUAT_ASL_POINTER_MAGIC_LEN + 4u + 1u) { + free(buffer); + return AMDUAT_ASL_POINTER_ERR_INTEGRITY; + } + if (memcmp(cur.data, k_amduat_asl_pointer_magic, + AMDUAT_ASL_POINTER_MAGIC_LEN) != 0) { + free(buffer); + return AMDUAT_ASL_POINTER_ERR_INTEGRITY; + } + cur.offset += AMDUAT_ASL_POINTER_MAGIC_LEN; + if (!amduat_asl_pointer_read_u32_le(&cur, &version) || + version != AMDUAT_ASL_POINTER_VERSION) { + free(buffer); + return AMDUAT_ASL_POINTER_ERR_INTEGRITY; + } + if (!amduat_asl_pointer_read_u8(&cur, &flags)) { + free(buffer); + return AMDUAT_ASL_POINTER_ERR_INTEGRITY; + } + if (!amduat_asl_pointer_read_u32_le(&cur, &name_len)) { + free(buffer); + return AMDUAT_ASL_POINTER_ERR_INTEGRITY; + } + if (cur.len - cur.offset < name_len) { + free(buffer); + return AMDUAT_ASL_POINTER_ERR_INTEGRITY; + } + if (strlen(name) != name_len || + memcmp(cur.data + cur.offset, name, name_len) != 0) { + free(buffer); + return AMDUAT_ASL_POINTER_ERR_INTEGRITY; + } + cur.offset += name_len; + if (!amduat_asl_pointer_read_u32_le(&cur, &ref_len)) { + free(buffer); + return AMDUAT_ASL_POINTER_ERR_INTEGRITY; + } + if (cur.len - cur.offset < ref_len || ref_len < 2u) { + free(buffer); + return AMDUAT_ASL_POINTER_ERR_INTEGRITY; + } + ref_bytes = amduat_octets(cur.data + cur.offset, ref_len); + if (!amduat_enc_asl1_core_decode_reference_v1(ref_bytes, out_ref)) { + free(buffer); + return AMDUAT_ASL_POINTER_ERR_INTEGRITY; + } + cur.offset += ref_len; + + if (!amduat_asl_pointer_read_u32_le(&cur, &prev_len)) { + free(buffer); + return AMDUAT_ASL_POINTER_ERR_INTEGRITY; + } + if (prev_len != 0u) { + if (!(flags & AMDUAT_ASL_POINTER_FLAG_HAS_PREV)) { + free(buffer); + return AMDUAT_ASL_POINTER_ERR_INTEGRITY; + } + if (cur.len - cur.offset < prev_len || prev_len < 2u) { + free(buffer); + return AMDUAT_ASL_POINTER_ERR_INTEGRITY; + } + prev_bytes = amduat_octets(cur.data + cur.offset, prev_len); + if (out_prev_ref != NULL && + !amduat_enc_asl1_core_decode_reference_v1(prev_bytes, out_prev_ref)) { + free(buffer); + return AMDUAT_ASL_POINTER_ERR_INTEGRITY; + } + cur.offset += prev_len; + if (out_has_prev != NULL) { + *out_has_prev = true; + } + } + + if (cur.offset != cur.len) { + free(buffer); + return AMDUAT_ASL_POINTER_ERR_INTEGRITY; + } + + *out_exists = true; + free(buffer); + return AMDUAT_ASL_POINTER_OK; +} + +static amduat_asl_pointer_error_t amduat_asl_pointer_write_head( + const char *path, + const char *name, + const amduat_reference_t *ref, + const amduat_reference_t *prev_ref, + bool has_prev) { + char *tmp_path; + size_t tmp_len; + FILE *fp; + uint8_t header[AMDUAT_ASL_POINTER_MAGIC_LEN + 4u + 1u]; + amduat_octets_t ref_bytes = amduat_octets(NULL, 0u); + amduat_octets_t prev_bytes = amduat_octets(NULL, 0u); + uint32_t name_len; + uint32_t ref_len; + uint32_t prev_len; + uint8_t flags = 0u; + amduat_asl_pointer_error_t err = AMDUAT_ASL_POINTER_OK; + + if (ref == NULL || name == NULL) { + return AMDUAT_ASL_POINTER_ERR_INTEGRITY; + } + name_len = (uint32_t)strlen(name); + if (!amduat_enc_asl1_core_encode_reference_v1(*ref, &ref_bytes)) { + return AMDUAT_ASL_POINTER_ERR_INTEGRITY; + } + ref_len = (uint32_t)ref_bytes.len; + + if (has_prev) { + if (prev_ref == NULL || + !amduat_enc_asl1_core_encode_reference_v1(*prev_ref, &prev_bytes)) { + free((void *)ref_bytes.data); + return AMDUAT_ASL_POINTER_ERR_INTEGRITY; + } + prev_len = (uint32_t)prev_bytes.len; + flags |= AMDUAT_ASL_POINTER_FLAG_HAS_PREV; + } else { + prev_len = 0u; + } + + tmp_len = strlen(path) + 5u; + tmp_path = (char *)malloc(tmp_len); + if (tmp_path == NULL) { + free((void *)ref_bytes.data); + free((void *)prev_bytes.data); + return AMDUAT_ASL_POINTER_ERR_IO; + } + snprintf(tmp_path, tmp_len, "%s.tmp", path); + + fp = fopen(tmp_path, "wb"); + if (fp == NULL) { + free(tmp_path); + free((void *)ref_bytes.data); + free((void *)prev_bytes.data); + return AMDUAT_ASL_POINTER_ERR_IO; + } + + memcpy(header, k_amduat_asl_pointer_magic, AMDUAT_ASL_POINTER_MAGIC_LEN); + amduat_asl_pointer_store_u32_le( + header + AMDUAT_ASL_POINTER_MAGIC_LEN, AMDUAT_ASL_POINTER_VERSION); + header[AMDUAT_ASL_POINTER_MAGIC_LEN + 4u] = flags; + if (fwrite(header, 1u, sizeof(header), fp) != sizeof(header)) { + err = AMDUAT_ASL_POINTER_ERR_IO; + } + if (err == AMDUAT_ASL_POINTER_OK) { + uint8_t len_buf[4u]; + amduat_asl_pointer_store_u32_le(len_buf, name_len); + if (fwrite(len_buf, 1u, 4u, fp) != 4u || + fwrite(name, 1u, name_len, fp) != name_len) { + err = AMDUAT_ASL_POINTER_ERR_IO; + } + } + if (err == AMDUAT_ASL_POINTER_OK) { + uint8_t len_buf[4u]; + amduat_asl_pointer_store_u32_le(len_buf, ref_len); + if (fwrite(len_buf, 1u, 4u, fp) != 4u || + fwrite(ref_bytes.data, 1u, ref_len, fp) != ref_len) { + err = AMDUAT_ASL_POINTER_ERR_IO; + } + } + if (err == AMDUAT_ASL_POINTER_OK) { + uint8_t len_buf[4u]; + amduat_asl_pointer_store_u32_le(len_buf, prev_len); + if (fwrite(len_buf, 1u, 4u, fp) != 4u) { + err = AMDUAT_ASL_POINTER_ERR_IO; + } + if (err == AMDUAT_ASL_POINTER_OK && prev_len != 0u && + fwrite(prev_bytes.data, 1u, prev_len, fp) != prev_len) { + err = AMDUAT_ASL_POINTER_ERR_IO; + } + } + + if (err == AMDUAT_ASL_POINTER_OK && fflush(fp) != 0) { + err = AMDUAT_ASL_POINTER_ERR_IO; + } + if (err == AMDUAT_ASL_POINTER_OK && fsync(fileno(fp)) != 0) { + err = AMDUAT_ASL_POINTER_ERR_IO; + } + if (fclose(fp) != 0) { + err = AMDUAT_ASL_POINTER_ERR_IO; + } + + if (err == AMDUAT_ASL_POINTER_OK && rename(tmp_path, path) != 0) { + err = AMDUAT_ASL_POINTER_ERR_IO; + } + if (err == AMDUAT_ASL_POINTER_OK) { + char *parent_dir = amduat_asl_pointer_parent_dir(path); + if (parent_dir != NULL) { + if (!amduat_asl_pointer_fsync_directory(parent_dir)) { + amduat_log(AMDUAT_LOG_WARN, + "pointer fsync dir failed for %s", parent_dir); + err = AMDUAT_ASL_POINTER_ERR_IO; + } + free(parent_dir); + } + } + if (err != AMDUAT_ASL_POINTER_OK) { + (void)remove(tmp_path); + } + + free(tmp_path); + free((void *)ref_bytes.data); + free((void *)prev_bytes.data); + return err; +} + +amduat_asl_pointer_error_t amduat_asl_pointer_get( + const amduat_asl_pointer_store_t *ps, + const char *name, + bool *out_exists, + amduat_reference_t *out_ref) { + char *head_path = NULL; + amduat_asl_pointer_error_t err; + + if (ps == NULL || name == NULL) { + return AMDUAT_ASL_POINTER_ERR_INTEGRITY; + } + if (!amduat_asl_pointer_name_is_valid(name)) { + return AMDUAT_ASL_POINTER_ERR_INVALID_NAME; + } + if (!amduat_asl_pointer_build_head_path(ps->root_path, name, false, + &head_path)) { + return AMDUAT_ASL_POINTER_ERR_IO; + } + + err = amduat_asl_pointer_read_head(head_path, name, out_exists, out_ref, + NULL, NULL); + if (err == AMDUAT_ASL_POINTER_ERR_NOT_FOUND) { + *out_exists = false; + err = AMDUAT_ASL_POINTER_OK; + } + free(head_path); + return err; +} + +amduat_asl_pointer_error_t amduat_asl_pointer_cas( + const amduat_asl_pointer_store_t *ps, + const char *name, + bool expected_exists, + const amduat_reference_t *expected_ref, + const amduat_reference_t *new_ref, + bool *out_swapped) { + char *head_path = NULL; + int fd = -1; + struct flock lock; + bool exists = false; + amduat_reference_t current_ref; + amduat_reference_t prev_ref; + bool has_prev = false; + bool created = false; + amduat_asl_pointer_error_t err; + + if (out_swapped == NULL) { + return AMDUAT_ASL_POINTER_ERR_INTEGRITY; + } + *out_swapped = false; + if (ps == NULL || name == NULL || new_ref == NULL) { + return AMDUAT_ASL_POINTER_ERR_INTEGRITY; + } + if (!amduat_asl_pointer_name_is_valid(name)) { + return AMDUAT_ASL_POINTER_ERR_INVALID_NAME; + } + if (expected_exists && expected_ref == NULL) { + return AMDUAT_ASL_POINTER_ERR_INTEGRITY; + } + + if (!amduat_asl_pointer_build_head_path(ps->root_path, name, true, + &head_path)) { + return AMDUAT_ASL_POINTER_ERR_IO; + } + + fd = open(head_path, O_RDWR | O_CREAT, 0644); + if (fd < 0) { + free(head_path); + return AMDUAT_ASL_POINTER_ERR_IO; + } + + memset(&lock, 0, sizeof(lock)); + lock.l_type = F_WRLCK; + lock.l_whence = SEEK_SET; + lock.l_start = 0; + lock.l_len = 0; + if (fcntl(fd, F_SETLKW, &lock) != 0) { + close(fd); + free(head_path); + return AMDUAT_ASL_POINTER_ERR_IO; + } + + { + struct stat st; + if (fstat(fd, &st) == 0 && st.st_size == 0) { + created = true; + } + } + + err = amduat_asl_pointer_read_head(head_path, name, &exists, ¤t_ref, + &prev_ref, &has_prev); + if (err == AMDUAT_ASL_POINTER_ERR_NOT_FOUND) { + exists = false; + err = AMDUAT_ASL_POINTER_OK; + } + if (err != AMDUAT_ASL_POINTER_OK) { + close(fd); + free(head_path); + return err; + } + + if (expected_exists != exists) { + if (created) { + (void)unlink(head_path); + } + close(fd); + free(head_path); + return AMDUAT_ASL_POINTER_OK; + } + if (expected_exists && !amduat_reference_eq(*expected_ref, current_ref)) { + if (created) { + (void)unlink(head_path); + } + close(fd); + free(head_path); + return AMDUAT_ASL_POINTER_OK; + } + + err = amduat_asl_pointer_write_head(head_path, name, new_ref, + expected_exists ? ¤t_ref : NULL, + expected_exists); + if (err == AMDUAT_ASL_POINTER_OK) { + *out_swapped = true; + } + + if (exists) { + amduat_reference_free(¤t_ref); + } + if (has_prev) { + amduat_reference_free(&prev_ref); + } + close(fd); + free(head_path); + return err; +} diff --git a/src/core/asl_log_store.c b/src/core/asl_log_store.c new file mode 100644 index 0000000..6933901 --- /dev/null +++ b/src/core/asl_log_store.c @@ -0,0 +1,905 @@ +#include "amduat/asl/log_store.h" + +#include "amduat/enc/asl1_core_codec.h" +#include "amduat/util/log.h" + +#include +#include +#include +#include + +enum { + AMDUAT_ASL_LOG_MAGIC_LEN = 8, + AMDUAT_ASL_LOG_VERSION = 1, + AMDUAT_ASL_LOG_CHUNK_MAX_ENTRIES = 1024u, + AMDUAT_ASL_LOG_MAX_RETRIES = 8u +}; + +static const uint8_t k_amduat_asl_log_magic[AMDUAT_ASL_LOG_MAGIC_LEN] = { + 'A', 'S', 'L', 'L', 'O', 'G', '1', '\0' +}; + +enum { + AMDUAT_ASL_LOG_FLAG_HAS_PREV = 1u << 0, + AMDUAT_ASL_LOG_FLAG_HAS_TIMESTAMP = 1u << 1, + AMDUAT_ASL_LOG_FLAG_HAS_ACTOR = 1u << 2 +}; + +typedef struct { + bool has_prev; + amduat_reference_t prev_ref; + uint64_t base_offset; + uint32_t entry_count; + bool has_timestamp; + bool has_actor; + amduat_asl_log_entry_t *entries; +} amduat_asl_log_chunk_t; + +typedef struct { + const uint8_t *data; + size_t len; + size_t offset; +} amduat_asl_log_cursor_t; + +static void amduat_asl_log_store_u16_le(uint8_t *out, uint16_t value) { + out[0] = (uint8_t)(value & 0xffu); + out[1] = (uint8_t)((value >> 8) & 0xffu); +} + +static void amduat_asl_log_store_u32_le(uint8_t *out, uint32_t value) { + out[0] = (uint8_t)(value & 0xffu); + out[1] = (uint8_t)((value >> 8) & 0xffu); + out[2] = (uint8_t)((value >> 16) & 0xffu); + out[3] = (uint8_t)((value >> 24) & 0xffu); +} + +static void amduat_asl_log_store_u64_le(uint8_t *out, uint64_t value) { + out[0] = (uint8_t)(value & 0xffu); + out[1] = (uint8_t)((value >> 8) & 0xffu); + out[2] = (uint8_t)((value >> 16) & 0xffu); + out[3] = (uint8_t)((value >> 24) & 0xffu); + out[4] = (uint8_t)((value >> 32) & 0xffu); + out[5] = (uint8_t)((value >> 40) & 0xffu); + out[6] = (uint8_t)((value >> 48) & 0xffu); + out[7] = (uint8_t)((value >> 56) & 0xffu); +} + +static bool amduat_asl_log_read_u16_le(amduat_asl_log_cursor_t *cur, + uint16_t *out) { + const uint8_t *data; + + if (cur->len - cur->offset < 2u) { + return false; + } + data = cur->data + cur->offset; + *out = (uint16_t)data[0] | ((uint16_t)data[1] << 8); + cur->offset += 2u; + return true; +} + +static bool amduat_asl_log_read_u32_le(amduat_asl_log_cursor_t *cur, + uint32_t *out) { + const uint8_t *data; + + if (cur->len - cur->offset < 4u) { + return false; + } + data = cur->data + cur->offset; + *out = (uint32_t)data[0] | ((uint32_t)data[1] << 8) | + ((uint32_t)data[2] << 16) | ((uint32_t)data[3] << 24); + cur->offset += 4u; + return true; +} + +static bool amduat_asl_log_read_u64_le(amduat_asl_log_cursor_t *cur, + uint64_t *out) { + const uint8_t *data; + + if (cur->len - cur->offset < 8u) { + return false; + } + data = cur->data + cur->offset; + *out = (uint64_t)data[0] | ((uint64_t)data[1] << 8) | + ((uint64_t)data[2] << 16) | ((uint64_t)data[3] << 24) | + ((uint64_t)data[4] << 32) | ((uint64_t)data[5] << 40) | + ((uint64_t)data[6] << 48) | ((uint64_t)data[7] << 56); + cur->offset += 8u; + return true; +} + +static bool amduat_asl_log_add_size(size_t *acc, size_t add) { + if (*acc > SIZE_MAX - add) { + return false; + } + *acc += add; + return true; +} + +static void amduat_asl_log_chunk_free(amduat_asl_log_chunk_t *chunk) { + if (chunk == NULL) { + return; + } + if (chunk->has_prev) { + amduat_reference_free(&chunk->prev_ref); + } + if (chunk->entries != NULL) { + for (uint32_t i = 0u; i < chunk->entry_count; ++i) { + amduat_reference_free(&chunk->entries[i].payload_ref); + amduat_octets_free(&chunk->entries[i].actor); + } + free(chunk->entries); + } + memset(chunk, 0, sizeof(*chunk)); +} + +static bool amduat_asl_log_build_pointer_name(const char *log_name, + char **out_name) { + size_t name_len; + size_t total_len; + char *buffer; + size_t offset; + + if (log_name == NULL || out_name == NULL) { + return false; + } + if (!amduat_asl_pointer_name_is_valid(log_name)) { + return false; + } + name_len = strlen(log_name); + total_len = 4u + name_len + 5u + 1u; + buffer = (char *)malloc(total_len); + if (buffer == NULL) { + return false; + } + offset = 0u; + memcpy(buffer + offset, "log/", 4u); + offset += 4u; + memcpy(buffer + offset, log_name, name_len); + offset += name_len; + memcpy(buffer + offset, "/head", 5u); + offset += 5u; + buffer[offset] = '\0'; + *out_name = buffer; + return true; +} + +static bool amduat_asl_log_entries_consistent( + const amduat_asl_log_entry_t *entries, + size_t entries_len, + bool *out_has_timestamp, + bool *out_has_actor) { + bool has_timestamp = false; + bool has_actor = false; + + if (out_has_timestamp == NULL || out_has_actor == NULL) { + return false; + } + *out_has_timestamp = false; + *out_has_actor = false; + + if (entries_len == 0u) { + return false; + } + has_timestamp = entries[0].has_timestamp; + has_actor = entries[0].has_actor; + for (size_t i = 0u; i < entries_len; ++i) { + if (entries[i].has_timestamp != has_timestamp || + entries[i].has_actor != has_actor) { + return false; + } + } + *out_has_timestamp = has_timestamp; + *out_has_actor = has_actor; + return true; +} + +static amduat_asl_store_error_t amduat_asl_log_encode_chunk( + const amduat_asl_log_chunk_t *chunk, + amduat_octets_t *out_bytes) { + uint8_t *buffer; + size_t total_len = 0u; + size_t offset = 0u; + uint8_t flags = 0u; + amduat_octets_t prev_bytes = amduat_octets(NULL, 0u); + + if (chunk == NULL || out_bytes == NULL) { + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + out_bytes->data = NULL; + out_bytes->len = 0u; + + if (!amduat_asl_log_add_size(&total_len, AMDUAT_ASL_LOG_MAGIC_LEN + 4u + 1u) || + !amduat_asl_log_add_size(&total_len, 4u) || + !amduat_asl_log_add_size(&total_len, 8u) || + !amduat_asl_log_add_size(&total_len, 4u)) { + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + + if (chunk->has_prev) { + if (!amduat_enc_asl1_core_encode_reference_v1(chunk->prev_ref, + &prev_bytes)) { + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + if (!amduat_asl_log_add_size(&total_len, 4u + prev_bytes.len)) { + free((void *)prev_bytes.data); + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + flags |= AMDUAT_ASL_LOG_FLAG_HAS_PREV; + } else { + if (!amduat_asl_log_add_size(&total_len, 4u)) { + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + } + + if (chunk->has_timestamp) { + flags |= AMDUAT_ASL_LOG_FLAG_HAS_TIMESTAMP; + } + if (chunk->has_actor) { + flags |= AMDUAT_ASL_LOG_FLAG_HAS_ACTOR; + } + + for (uint32_t i = 0u; i < chunk->entry_count; ++i) { + amduat_octets_t ref_bytes = amduat_octets(NULL, 0u); + if (!amduat_asl_log_add_size(&total_len, 2u)) { + goto cleanup; + } + if (chunk->has_timestamp) { + if (!amduat_asl_log_add_size(&total_len, 8u)) { + goto cleanup; + } + } + if (!amduat_enc_asl1_core_encode_reference_v1( + chunk->entries[i].payload_ref, &ref_bytes)) { + goto cleanup; + } + if (!amduat_asl_log_add_size(&total_len, 4u + ref_bytes.len)) { + free((void *)ref_bytes.data); + goto cleanup; + } + if (chunk->has_actor) { + if (!amduat_asl_log_add_size(&total_len, + 4u + chunk->entries[i].actor.len)) { + free((void *)ref_bytes.data); + goto cleanup; + } + } + free((void *)ref_bytes.data); + } + + buffer = (uint8_t *)malloc(total_len); + if (buffer == NULL) { + free((void *)prev_bytes.data); + return AMDUAT_ASL_STORE_ERR_IO; + } + + memcpy(buffer + offset, k_amduat_asl_log_magic, AMDUAT_ASL_LOG_MAGIC_LEN); + offset += AMDUAT_ASL_LOG_MAGIC_LEN; + amduat_asl_log_store_u32_le(buffer + offset, AMDUAT_ASL_LOG_VERSION); + offset += 4u; + buffer[offset++] = flags; + + if (chunk->has_prev) { + amduat_asl_log_store_u32_le(buffer + offset, + (uint32_t)prev_bytes.len); + offset += 4u; + memcpy(buffer + offset, prev_bytes.data, prev_bytes.len); + offset += prev_bytes.len; + } else { + amduat_asl_log_store_u32_le(buffer + offset, 0u); + offset += 4u; + } + + amduat_asl_log_store_u64_le(buffer + offset, chunk->base_offset); + offset += 8u; + amduat_asl_log_store_u32_le(buffer + offset, chunk->entry_count); + offset += 4u; + + for (uint32_t i = 0u; i < chunk->entry_count; ++i) { + amduat_octets_t ref_bytes = amduat_octets(NULL, 0u); + if (!amduat_enc_asl1_core_encode_reference_v1( + chunk->entries[i].payload_ref, &ref_bytes)) { + free(buffer); + free((void *)prev_bytes.data); + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + amduat_asl_log_store_u16_le(buffer + offset, chunk->entries[i].kind); + offset += 2u; + if (chunk->has_timestamp) { + amduat_asl_log_store_u64_le(buffer + offset, + chunk->entries[i].timestamp); + offset += 8u; + } + amduat_asl_log_store_u32_le(buffer + offset, (uint32_t)ref_bytes.len); + offset += 4u; + memcpy(buffer + offset, ref_bytes.data, ref_bytes.len); + offset += ref_bytes.len; + if (chunk->has_actor) { + amduat_asl_log_store_u32_le(buffer + offset, + (uint32_t)chunk->entries[i].actor.len); + offset += 4u; + if (chunk->entries[i].actor.len != 0u) { + memcpy(buffer + offset, chunk->entries[i].actor.data, + chunk->entries[i].actor.len); + offset += chunk->entries[i].actor.len; + } + } + free((void *)ref_bytes.data); + } + + if (offset != total_len) { + free(buffer); + free((void *)prev_bytes.data); + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + + free((void *)prev_bytes.data); + out_bytes->data = buffer; + out_bytes->len = total_len; + return AMDUAT_ASL_STORE_OK; + +cleanup: + free((void *)prev_bytes.data); + return AMDUAT_ASL_STORE_ERR_INTEGRITY; +} + +static amduat_asl_store_error_t amduat_asl_log_decode_chunk( + amduat_octets_t bytes, + amduat_asl_log_chunk_t *out_chunk) { + amduat_asl_log_cursor_t cur; + uint32_t version; + uint32_t prev_len; + uint8_t flags; + uint32_t entry_count; + + if (out_chunk == NULL) { + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + memset(out_chunk, 0, sizeof(*out_chunk)); + + if (bytes.len < AMDUAT_ASL_LOG_MAGIC_LEN + 4u + 1u) { + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + cur.data = bytes.data; + cur.len = bytes.len; + cur.offset = 0u; + + if (memcmp(cur.data, k_amduat_asl_log_magic, + AMDUAT_ASL_LOG_MAGIC_LEN) != 0) { + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + cur.offset += AMDUAT_ASL_LOG_MAGIC_LEN; + + if (!amduat_asl_log_read_u32_le(&cur, &version) || + version != AMDUAT_ASL_LOG_VERSION) { + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + if (cur.len - cur.offset < 1u) { + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + flags = cur.data[cur.offset++]; + + out_chunk->has_prev = (flags & AMDUAT_ASL_LOG_FLAG_HAS_PREV) != 0u; + out_chunk->has_timestamp = + (flags & AMDUAT_ASL_LOG_FLAG_HAS_TIMESTAMP) != 0u; + out_chunk->has_actor = (flags & AMDUAT_ASL_LOG_FLAG_HAS_ACTOR) != 0u; + + if (!amduat_asl_log_read_u32_le(&cur, &prev_len)) { + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + if (prev_len != 0u) { + amduat_octets_t prev_bytes; + if (!out_chunk->has_prev || + cur.len - cur.offset < prev_len) { + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + prev_bytes = amduat_octets(cur.data + cur.offset, prev_len); + if (!amduat_enc_asl1_core_decode_reference_v1(prev_bytes, + &out_chunk->prev_ref)) { + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + cur.offset += prev_len; + } else if (out_chunk->has_prev) { + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + + if (!amduat_asl_log_read_u64_le(&cur, &out_chunk->base_offset)) { + amduat_asl_log_chunk_free(out_chunk); + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + if (!amduat_asl_log_read_u32_le(&cur, &entry_count)) { + amduat_asl_log_chunk_free(out_chunk); + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + out_chunk->entry_count = entry_count; + + if (entry_count != 0u) { + if (entry_count > SIZE_MAX / sizeof(amduat_asl_log_entry_t)) { + amduat_asl_log_chunk_free(out_chunk); + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + out_chunk->entries = (amduat_asl_log_entry_t *)calloc( + entry_count, sizeof(*out_chunk->entries)); + if (out_chunk->entries == NULL) { + amduat_asl_log_chunk_free(out_chunk); + return AMDUAT_ASL_STORE_ERR_IO; + } + } + + for (uint32_t i = 0u; i < entry_count; ++i) { + amduat_asl_log_entry_t *entry = &out_chunk->entries[i]; + uint16_t kind; + uint32_t ref_len; + amduat_octets_t ref_bytes; + uint32_t actor_len = 0u; + + if (!amduat_asl_log_read_u16_le(&cur, &kind)) { + amduat_asl_log_chunk_free(out_chunk); + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + entry->kind = kind; + if (out_chunk->has_timestamp) { + if (!amduat_asl_log_read_u64_le(&cur, &entry->timestamp)) { + amduat_asl_log_chunk_free(out_chunk); + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + entry->has_timestamp = true; + } + if (!amduat_asl_log_read_u32_le(&cur, &ref_len)) { + amduat_asl_log_chunk_free(out_chunk); + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + if (cur.len - cur.offset < ref_len || ref_len < 2u) { + amduat_asl_log_chunk_free(out_chunk); + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + ref_bytes = amduat_octets(cur.data + cur.offset, ref_len); + if (!amduat_enc_asl1_core_decode_reference_v1(ref_bytes, + &entry->payload_ref)) { + amduat_asl_log_chunk_free(out_chunk); + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + cur.offset += ref_len; + + if (out_chunk->has_actor) { + if (!amduat_asl_log_read_u32_le(&cur, &actor_len)) { + amduat_asl_log_chunk_free(out_chunk); + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + if (cur.len - cur.offset < actor_len) { + amduat_asl_log_chunk_free(out_chunk); + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + if (actor_len != 0u) { + uint8_t *actor = (uint8_t *)malloc(actor_len); + if (actor == NULL) { + amduat_asl_log_chunk_free(out_chunk); + return AMDUAT_ASL_STORE_ERR_IO; + } + memcpy(actor, cur.data + cur.offset, actor_len); + entry->actor = amduat_octets(actor, actor_len); + } else { + entry->actor = amduat_octets(NULL, 0u); + } + entry->has_actor = true; + cur.offset += actor_len; + } + } + + if (cur.offset != cur.len) { + amduat_asl_log_chunk_free(out_chunk); + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + + return AMDUAT_ASL_STORE_OK; +} + +bool amduat_asl_log_store_init(amduat_asl_log_store_t *log_store, + const char *root_path, + amduat_asl_store_t *store, + const amduat_asl_pointer_store_t *pointer_store) { + if (log_store == NULL || root_path == NULL || store == NULL) { + return false; + } + log_store->store = store; + if (pointer_store != NULL) { + log_store->pointer_store = *pointer_store; + return true; + } + return amduat_asl_pointer_store_init(&log_store->pointer_store, root_path); +} + +amduat_asl_store_error_t amduat_asl_log_append( + amduat_asl_log_store_t *log_store, + const char *log_name, + const amduat_asl_log_entry_t *entries, + size_t entries_len, + uint64_t *out_first_offset) { + char *pointer_name = NULL; + amduat_asl_store_error_t store_err; + bool has_timestamp = false; + bool has_actor = false; + uint32_t entry_count; + + if (out_first_offset != NULL) { + *out_first_offset = 0u; + } + if (log_store == NULL || log_store->store == NULL || log_name == NULL || + entries == NULL) { + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + if (!amduat_asl_pointer_name_is_valid(log_name)) { + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + if (entries_len == 0u || entries_len > AMDUAT_ASL_LOG_CHUNK_MAX_ENTRIES) { + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + if (!amduat_asl_log_entries_consistent(entries, entries_len, + &has_timestamp, &has_actor)) { + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + + if (!amduat_asl_log_build_pointer_name(log_name, &pointer_name)) { + return AMDUAT_ASL_STORE_ERR_IO; + } + entry_count = (uint32_t)entries_len; + + for (uint32_t attempt = 0u; attempt < AMDUAT_ASL_LOG_MAX_RETRIES; ++attempt) { + bool head_exists = false; + amduat_reference_t head_ref; + amduat_artifact_t head_artifact; + amduat_asl_log_chunk_t head_chunk; + uint64_t base_offset = 0u; + amduat_asl_pointer_error_t ptr_err; + + memset(&head_chunk, 0, sizeof(head_chunk)); + ptr_err = amduat_asl_pointer_get(&log_store->pointer_store, + pointer_name, &head_exists, &head_ref); + if (ptr_err != AMDUAT_ASL_POINTER_OK) { + free(pointer_name); + return AMDUAT_ASL_STORE_ERR_IO; + } + + if (head_exists) { + store_err = amduat_asl_store_get(log_store->store, head_ref, + &head_artifact); + if (store_err != AMDUAT_ASL_STORE_OK) { + amduat_reference_free(&head_ref); + free(pointer_name); + return store_err; + } + if (!head_artifact.has_type_tag || + head_artifact.type_tag.tag_id != AMDUAT_TYPE_TAG_ASL_LOG_CHUNK_1) { + amduat_reference_free(&head_ref); + amduat_artifact_free(&head_artifact); + free(pointer_name); + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + store_err = amduat_asl_log_decode_chunk(head_artifact.bytes, &head_chunk); + amduat_artifact_free(&head_artifact); + if (store_err != AMDUAT_ASL_STORE_OK) { + amduat_reference_free(&head_ref); + free(pointer_name); + return store_err; + } + if (head_chunk.base_offset > + UINT64_MAX - (uint64_t)head_chunk.entry_count) { + amduat_asl_log_chunk_free(&head_chunk); + amduat_reference_free(&head_ref); + free(pointer_name); + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + base_offset = head_chunk.base_offset + head_chunk.entry_count; + amduat_asl_log_chunk_free(&head_chunk); + } + + { + amduat_asl_log_chunk_t new_chunk; + amduat_octets_t encoded = amduat_octets(NULL, 0u); + amduat_reference_t new_ref; + amduat_artifact_t artifact; + + memset(&new_chunk, 0, sizeof(new_chunk)); + new_chunk.base_offset = base_offset; + new_chunk.entry_count = entry_count; + new_chunk.has_timestamp = has_timestamp; + new_chunk.has_actor = has_actor; + new_chunk.entries = (amduat_asl_log_entry_t *)entries; + if (head_exists) { + new_chunk.has_prev = true; + new_chunk.prev_ref = head_ref; + } + + store_err = amduat_asl_log_encode_chunk(&new_chunk, &encoded); + if (store_err != AMDUAT_ASL_STORE_OK) { + if (head_exists) { + amduat_reference_free(&head_ref); + } + free(pointer_name); + return store_err; + } + artifact = amduat_artifact_with_type( + encoded, amduat_type_tag(AMDUAT_TYPE_TAG_ASL_LOG_CHUNK_1)); + store_err = amduat_asl_store_put(log_store->store, artifact, &new_ref); + free((void *)encoded.data); + if (store_err != AMDUAT_ASL_STORE_OK) { + if (head_exists) { + amduat_reference_free(&head_ref); + } + free(pointer_name); + return store_err; + } + + { + bool swapped = false; + amduat_asl_pointer_error_t cas_err = amduat_asl_pointer_cas( + &log_store->pointer_store, + pointer_name, + head_exists, + head_exists ? &head_ref : NULL, + &new_ref, + &swapped); + if (head_exists) { + amduat_reference_free(&head_ref); + } + if (cas_err != AMDUAT_ASL_POINTER_OK) { + free(pointer_name); + return AMDUAT_ASL_STORE_ERR_IO; + } + if (swapped) { + /* Chunks are rooted by the head pointer; future GC can follow them. */ + if (out_first_offset != NULL) { + *out_first_offset = base_offset; + } + free(pointer_name); + return AMDUAT_ASL_STORE_OK; + } + amduat_log(AMDUAT_LOG_DEBUG, "log append CAS retry"); + } + } + } + + free(pointer_name); + return AMDUAT_ASL_STORE_ERR_IO; +} + +amduat_asl_store_error_t amduat_asl_log_read( + amduat_asl_log_store_t *log_store, + const char *log_name, + uint64_t from_offset, + size_t max_entries, + amduat_asl_log_entry_t **out_entries, + size_t *out_len, + uint64_t *out_next_offset, + bool *out_end) { + char *pointer_name = NULL; + bool head_exists = false; + amduat_reference_t head_ref; + amduat_asl_pointer_error_t ptr_err; + amduat_asl_log_chunk_t *chunks = NULL; + size_t chunks_len = 0u; + uint64_t head_end_offset = 0u; + + if (out_entries == NULL || out_len == NULL || + out_next_offset == NULL || out_end == NULL) { + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + *out_entries = NULL; + *out_len = 0u; + *out_next_offset = from_offset; + *out_end = false; + + if (log_store == NULL || log_store->store == NULL || log_name == NULL) { + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + if (!amduat_asl_pointer_name_is_valid(log_name)) { + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + + if (!amduat_asl_log_build_pointer_name(log_name, &pointer_name)) { + return AMDUAT_ASL_STORE_ERR_IO; + } + + ptr_err = amduat_asl_pointer_get(&log_store->pointer_store, pointer_name, + &head_exists, &head_ref); + if (ptr_err != AMDUAT_ASL_POINTER_OK) { + free(pointer_name); + return AMDUAT_ASL_STORE_ERR_IO; + } + if (!head_exists) { + free(pointer_name); + *out_end = true; + return AMDUAT_ASL_STORE_OK; + } + + { + amduat_reference_t current_ref = head_ref; + bool current_ref_owned = false; + bool has_prev = true; + while (has_prev) { + amduat_artifact_t artifact; + amduat_asl_log_chunk_t chunk; + amduat_asl_store_error_t err = + amduat_asl_store_get(log_store->store, current_ref, &artifact); + if (err != AMDUAT_ASL_STORE_OK) { + free(pointer_name); + if (chunks != NULL) { + for (size_t i = 0u; i < chunks_len; ++i) { + amduat_asl_log_chunk_free(&chunks[i]); + } + free(chunks); + } + if (current_ref_owned) { + amduat_reference_free(¤t_ref); + } + amduat_reference_free(&head_ref); + return err; + } + if (!artifact.has_type_tag || + artifact.type_tag.tag_id != AMDUAT_TYPE_TAG_ASL_LOG_CHUNK_1) { + amduat_artifact_free(&artifact); + if (current_ref_owned) { + amduat_reference_free(¤t_ref); + } + free(pointer_name); + amduat_reference_free(&head_ref); + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + err = amduat_asl_log_decode_chunk(artifact.bytes, &chunk); + amduat_artifact_free(&artifact); + if (err != AMDUAT_ASL_STORE_OK) { + if (current_ref_owned) { + amduat_reference_free(¤t_ref); + } + free(pointer_name); + amduat_reference_free(&head_ref); + return err; + } + if (chunks_len == 0u) { + if (chunk.base_offset > + UINT64_MAX - (uint64_t)chunk.entry_count) { + amduat_asl_log_chunk_free(&chunk); + if (current_ref_owned) { + amduat_reference_free(¤t_ref); + } + free(pointer_name); + amduat_reference_free(&head_ref); + return AMDUAT_ASL_STORE_ERR_INTEGRITY; + } + head_end_offset = chunk.base_offset + chunk.entry_count; + } + { + amduat_asl_log_chunk_t *next = + (amduat_asl_log_chunk_t *)realloc( + chunks, (chunks_len + 1u) * sizeof(*chunks)); + if (next == NULL) { + amduat_asl_log_chunk_free(&chunk); + amduat_reference_free(¤t_ref); + free(pointer_name); + return AMDUAT_ASL_STORE_ERR_IO; + } + chunks = next; + chunks[chunks_len++] = chunk; + } + + if (chunk.base_offset <= from_offset || !chunk.has_prev) { + has_prev = false; + } else { + amduat_reference_t prev_ref = chunk.prev_ref; + amduat_reference_t next_ref; + if (!amduat_octets_clone(prev_ref.digest, &next_ref.digest)) { + amduat_asl_log_chunk_free(&chunk); + if (current_ref_owned) { + amduat_reference_free(¤t_ref); + } + free(pointer_name); + return AMDUAT_ASL_STORE_ERR_IO; + } + next_ref.hash_id = prev_ref.hash_id; + if (current_ref_owned) { + amduat_reference_free(¤t_ref); + } + current_ref = next_ref; + current_ref_owned = true; + } + } + if (current_ref_owned) { + amduat_reference_free(¤t_ref); + } + } + + if (from_offset >= head_end_offset) { + *out_end = true; + amduat_reference_free(&head_ref); + free(pointer_name); + for (size_t i = 0u; i < chunks_len; ++i) { + amduat_asl_log_chunk_free(&chunks[i]); + } + free(chunks); + return AMDUAT_ASL_STORE_OK; + } + + { + size_t total_out = 0u; + amduat_asl_log_entry_t *out = NULL; + + for (size_t i = 0u; i < chunks_len / 2u; ++i) { + amduat_asl_log_chunk_t tmp = chunks[i]; + chunks[i] = chunks[chunks_len - 1u - i]; + chunks[chunks_len - 1u - i] = tmp; + } + + for (size_t i = 0u; i < chunks_len; ++i) { + amduat_asl_log_chunk_t *chunk = &chunks[i]; + for (uint32_t j = 0u; j < chunk->entry_count; ++j) { + uint64_t offset = chunk->base_offset + j; + if (offset < from_offset) { + continue; + } + if (total_out == max_entries) { + *out_entries = out; + *out_len = total_out; + *out_next_offset = offset; + *out_end = false; + goto done; + } + { + amduat_asl_log_entry_t *next = + (amduat_asl_log_entry_t *)realloc( + out, (total_out + 1u) * sizeof(*out)); + if (next == NULL) { + amduat_asl_log_entries_free(out, total_out); + return AMDUAT_ASL_STORE_ERR_IO; + } + out = next; + memset(&out[total_out], 0, sizeof(out[total_out])); + out[total_out].kind = chunk->entries[j].kind; + out[total_out].has_timestamp = chunk->entries[j].has_timestamp; + out[total_out].timestamp = chunk->entries[j].timestamp; + out[total_out].has_actor = chunk->entries[j].has_actor; + out[total_out].payload_ref.hash_id = + chunk->entries[j].payload_ref.hash_id; + if (!amduat_octets_clone(chunk->entries[j].payload_ref.digest, + &out[total_out].payload_ref.digest)) { + amduat_asl_log_entries_free(out, total_out + 1u); + return AMDUAT_ASL_STORE_ERR_IO; + } + if (out[total_out].has_actor) { + if (!amduat_octets_clone(chunk->entries[j].actor, + &out[total_out].actor)) { + amduat_asl_log_entries_free(out, total_out + 1u); + return AMDUAT_ASL_STORE_ERR_IO; + } + } else { + out[total_out].actor = amduat_octets(NULL, 0u); + } + total_out++; + *out_next_offset = offset + 1u; + } + } + } + *out_entries = out; + *out_len = total_out; + *out_end = (*out_next_offset >= head_end_offset); + } + +done: + amduat_reference_free(&head_ref); + free(pointer_name); + for (size_t i = 0u; i < chunks_len; ++i) { + amduat_asl_log_chunk_free(&chunks[i]); + } + free(chunks); + return AMDUAT_ASL_STORE_OK; +} + +void amduat_asl_log_entries_free(amduat_asl_log_entry_t *entries, + size_t entries_len) { + if (entries == NULL) { + return; + } + for (size_t i = 0u; i < entries_len; ++i) { + amduat_reference_free(&entries[i].payload_ref); + amduat_octets_free(&entries[i].actor); + } + free(entries); +} diff --git a/src/tools/amduat_pel_cli.c b/src/tools/amduat_pel_cli.c index e967cbb..d934a49 100644 --- a/src/tools/amduat_pel_cli.c +++ b/src/tools/amduat_pel_cli.c @@ -1,6 +1,8 @@ #include "amduat/asl/artifact_io.h" #include "amduat/asl/asl_derivation_index_fs.h" #include "amduat/asl/asl_materialization_cache_fs.h" +#include "amduat/asl/asl_pointer_fs.h" +#include "amduat/asl/log_store.h" #include "amduat/asl/asl_store_fs.h" #include "amduat/asl/asl_store_fs_meta.h" #include "amduat/asl/io.h" @@ -69,7 +71,9 @@ static void amduat_pel_cli_print_usage(FILE *stream) { " trace Trace DAG tools (decode, from-result).\n" " result Surface result tools (decode).\n" " op Kernel op registry tools.\n" + " log Append-only log tools (append, read).\n" " matcache Materialization cache tools (get, sid).\n" + " ptr Named pointer tools (get, cas).\n" " scheme Scheme refs, type tags, profile IDs.\n" " help Show help for a command.\n" "\n" @@ -135,6 +139,10 @@ static void amduat_pel_cli_print_usage(FILE *stream) { " amduat-pel op params-decode --op NAME[=VERSION]\n" " --input PATH|- [--format text|json]\n" "\n" + "log:\n" + " amduat-pel log append --log NAME --kind N --ref REF [--actor TEXT]\n" + " amduat-pel log read --log NAME --from OFFSET --limit N\n" + "\n" "scheme:\n" " amduat-pel scheme show [--format text|json] [--ref-format hex|bytes]\n" " amduat-pel scheme dag-ref [--format hex|bytes]\n" @@ -144,6 +152,10 @@ static void amduat_pel_cli_print_usage(FILE *stream) { " amduat-pel matcache sid --program-ref REF --input-ref REF...\n" " [--params-ref REF]\n" "\n" + "ptr:\n" + " amduat-pel ptr get --name NAME\n" + " amduat-pel ptr cas --name NAME --expected REF|none --new REF\n" + "\n" "defaults:\n" " --root .amduat-asl\n" " --ref-format hex\n" @@ -3010,6 +3022,443 @@ static int amduat_pel_cli_cmd_matcache( return AMDUAT_PEL_CLI_EXIT_USAGE; } +static int amduat_pel_cli_cmd_ptr_get( + int argc, + char **argv, + const amduat_pel_cli_global_opts_t *global) { + const char *name = NULL; + amduat_asl_pointer_store_t store; + bool exists = false; + amduat_reference_t ref; + int i; + + if (global == NULL) { + return AMDUAT_PEL_CLI_EXIT_USAGE; + } + + for (i = 0; i < argc; ++i) { + if (strcmp(argv[i], "--name") == 0) { + if (i + 1 >= argc) { + fprintf(stderr, "error: --name requires a value\n"); + return AMDUAT_PEL_CLI_EXIT_USAGE; + } + name = argv[++i]; + } else if (strcmp(argv[i], "--help") == 0 || + strcmp(argv[i], "-h") == 0) { + amduat_pel_cli_print_usage(stdout); + return AMDUAT_PEL_CLI_EXIT_OK; + } else { + fprintf(stderr, "error: unknown option: %s\n", argv[i]); + return AMDUAT_PEL_CLI_EXIT_USAGE; + } + } + + if (name == NULL) { + fprintf(stderr, "error: --name is required\n"); + return AMDUAT_PEL_CLI_EXIT_USAGE; + } + if (!amduat_asl_pointer_store_init(&store, global->root)) { + fprintf(stderr, "error: failed to init pointer store\n"); + return AMDUAT_PEL_CLI_EXIT_CONFIG; + } + + { + amduat_asl_pointer_error_t err = + amduat_asl_pointer_get(&store, name, &exists, &ref); + if (err == AMDUAT_ASL_POINTER_ERR_INVALID_NAME) { + fprintf(stderr, "error: invalid pointer name\n"); + return AMDUAT_PEL_CLI_EXIT_USAGE; + } + if (err != AMDUAT_ASL_POINTER_OK) { + fprintf(stderr, "error: pointer get failed\n"); + return AMDUAT_PEL_CLI_EXIT_IO; + } + } + + printf("exists=%s\n", exists ? "true" : "false"); + if (exists) { + fputs("ref=", stdout); + if (!amduat_format_ref_write_text(stdout, ref, global->ref_format)) { + fprintf(stderr, "error: failed to format ref\n"); + amduat_pel_cli_free_reference(&ref); + return AMDUAT_PEL_CLI_EXIT_CODEC; + } + fputc('\n', stdout); + amduat_pel_cli_free_reference(&ref); + } + return AMDUAT_PEL_CLI_EXIT_OK; +} + +static int amduat_pel_cli_cmd_ptr_cas( + int argc, + char **argv, + const amduat_pel_cli_global_opts_t *global) { + const char *name = NULL; + const char *expected_text = NULL; + const char *new_text = NULL; + amduat_asl_pointer_store_t store; + amduat_reference_t expected_ref; + amduat_reference_t new_ref; + bool expected_exists = false; + bool swapped = false; + bool stdin_used = false; + int i; + + if (global == NULL) { + return AMDUAT_PEL_CLI_EXIT_USAGE; + } + + for (i = 0; i < argc; ++i) { + if (strcmp(argv[i], "--name") == 0) { + if (i + 1 >= argc) { + fprintf(stderr, "error: --name requires a value\n"); + return AMDUAT_PEL_CLI_EXIT_USAGE; + } + name = argv[++i]; + } else if (strcmp(argv[i], "--expected") == 0) { + if (i + 1 >= argc) { + fprintf(stderr, "error: --expected requires a value\n"); + return AMDUAT_PEL_CLI_EXIT_USAGE; + } + expected_text = argv[++i]; + } else if (strcmp(argv[i], "--new") == 0) { + if (i + 1 >= argc) { + fprintf(stderr, "error: --new requires a value\n"); + return AMDUAT_PEL_CLI_EXIT_USAGE; + } + new_text = argv[++i]; + } else if (strcmp(argv[i], "--help") == 0 || + strcmp(argv[i], "-h") == 0) { + amduat_pel_cli_print_usage(stdout); + return AMDUAT_PEL_CLI_EXIT_OK; + } else { + fprintf(stderr, "error: unknown option: %s\n", argv[i]); + return AMDUAT_PEL_CLI_EXIT_USAGE; + } + } + + if (name == NULL || expected_text == NULL || new_text == NULL) { + fprintf(stderr, "error: --name, --expected, and --new are required\n"); + return AMDUAT_PEL_CLI_EXIT_USAGE; + } + if (!amduat_asl_pointer_store_init(&store, global->root)) { + fprintf(stderr, "error: failed to init pointer store\n"); + return AMDUAT_PEL_CLI_EXIT_CONFIG; + } + + if (strcmp(expected_text, "none") == 0) { + expected_exists = false; + } else { + expected_exists = true; + if (!amduat_pel_cli_ref_from_text(expected_text, global->ref_format, + &stdin_used, &expected_ref)) { + fprintf(stderr, "error: invalid expected ref\n"); + return AMDUAT_PEL_CLI_EXIT_USAGE; + } + } + if (!amduat_pel_cli_ref_from_text(new_text, global->ref_format, + &stdin_used, &new_ref)) { + fprintf(stderr, "error: invalid new ref\n"); + if (expected_exists) { + amduat_pel_cli_free_reference(&expected_ref); + } + return AMDUAT_PEL_CLI_EXIT_USAGE; + } + + { + amduat_asl_pointer_error_t err = amduat_asl_pointer_cas( + &store, name, expected_exists, + expected_exists ? &expected_ref : NULL, + &new_ref, &swapped); + if (expected_exists) { + amduat_pel_cli_free_reference(&expected_ref); + } + amduat_pel_cli_free_reference(&new_ref); + if (err == AMDUAT_ASL_POINTER_ERR_INVALID_NAME) { + fprintf(stderr, "error: invalid pointer name\n"); + return AMDUAT_PEL_CLI_EXIT_USAGE; + } + if (err != AMDUAT_ASL_POINTER_OK) { + fprintf(stderr, "error: pointer cas failed\n"); + return AMDUAT_PEL_CLI_EXIT_IO; + } + } + + printf("swapped=%s\n", swapped ? "true" : "false"); + if (!swapped) { + return 2; + } + return AMDUAT_PEL_CLI_EXIT_OK; +} + +static int amduat_pel_cli_cmd_ptr( + int argc, + char **argv, + const amduat_pel_cli_global_opts_t *global) { + if (argc < 1) { + fprintf(stderr, "error: ptr requires a subcommand\n"); + return AMDUAT_PEL_CLI_EXIT_USAGE; + } + if (strcmp(argv[0], "get") == 0) { + return amduat_pel_cli_cmd_ptr_get(argc - 1, argv + 1, global); + } + if (strcmp(argv[0], "cas") == 0) { + return amduat_pel_cli_cmd_ptr_cas(argc - 1, argv + 1, global); + } + fprintf(stderr, "error: unknown ptr subcommand: %s\n", argv[0]); + return AMDUAT_PEL_CLI_EXIT_USAGE; +} + +static bool amduat_pel_cli_init_store(const char *root, + amduat_asl_store_t *out_store, + amduat_asl_store_fs_t *out_fs) { + amduat_asl_store_fs_config_t cfg; + + if (root == NULL || out_store == NULL || out_fs == NULL) { + return false; + } + if (!amduat_asl_store_fs_load_config(root, &cfg)) { + return false; + } + if (!amduat_asl_store_fs_init(out_fs, cfg.config, root)) { + return false; + } + amduat_asl_store_init(out_store, cfg.config, amduat_asl_store_fs_ops(), + out_fs); + return true; +} + +static int amduat_pel_cli_cmd_log_append( + int argc, + char **argv, + const amduat_pel_cli_global_opts_t *global) { + const char *log_name = NULL; + const char *ref_text = NULL; + const char *actor_text = NULL; + amduat_asl_store_t store; + amduat_asl_store_fs_t fs; + amduat_asl_log_store_t log_store; + amduat_asl_pointer_store_t pointer_store; + amduat_asl_log_entry_t entry; + amduat_reference_t ref; + uint64_t offset = 0u; + unsigned long kind = 0u; + bool stdin_used = false; + int i; + + if (global == NULL) { + return AMDUAT_PEL_CLI_EXIT_USAGE; + } + + for (i = 0; i < argc; ++i) { + if (strcmp(argv[i], "--log") == 0) { + if (i + 1 >= argc) { + fprintf(stderr, "error: --log requires a value\n"); + return AMDUAT_PEL_CLI_EXIT_USAGE; + } + log_name = argv[++i]; + } else if (strcmp(argv[i], "--kind") == 0) { + if (i + 1 >= argc) { + fprintf(stderr, "error: --kind requires a value\n"); + return AMDUAT_PEL_CLI_EXIT_USAGE; + } + kind = strtoul(argv[++i], NULL, 10); + } else if (strcmp(argv[i], "--ref") == 0) { + if (i + 1 >= argc) { + fprintf(stderr, "error: --ref requires a value\n"); + return AMDUAT_PEL_CLI_EXIT_USAGE; + } + ref_text = argv[++i]; + } else if (strcmp(argv[i], "--actor") == 0) { + if (i + 1 >= argc) { + fprintf(stderr, "error: --actor requires a value\n"); + return AMDUAT_PEL_CLI_EXIT_USAGE; + } + actor_text = argv[++i]; + } else if (strcmp(argv[i], "--help") == 0 || + strcmp(argv[i], "-h") == 0) { + amduat_pel_cli_print_usage(stdout); + return AMDUAT_PEL_CLI_EXIT_OK; + } else { + fprintf(stderr, "error: unknown option: %s\n", argv[i]); + return AMDUAT_PEL_CLI_EXIT_USAGE; + } + } + + if (log_name == NULL || ref_text == NULL) { + fprintf(stderr, "error: --log and --ref are required\n"); + return AMDUAT_PEL_CLI_EXIT_USAGE; + } + if (kind > UINT16_MAX) { + fprintf(stderr, "error: --kind out of range\n"); + return AMDUAT_PEL_CLI_EXIT_USAGE; + } + if (!amduat_pel_cli_ref_from_text(ref_text, global->ref_format, + &stdin_used, &ref)) { + fprintf(stderr, "error: invalid --ref\n"); + return AMDUAT_PEL_CLI_EXIT_USAGE; + } + + if (!amduat_pel_cli_init_store(global->root, &store, &fs)) { + amduat_pel_cli_free_reference(&ref); + fprintf(stderr, "error: failed to initialize store\n"); + return AMDUAT_PEL_CLI_EXIT_CONFIG; + } + if (!amduat_asl_pointer_store_init(&pointer_store, global->root)) { + amduat_pel_cli_free_reference(&ref); + fprintf(stderr, "error: failed to init pointer store\n"); + return AMDUAT_PEL_CLI_EXIT_CONFIG; + } + if (!amduat_asl_log_store_init(&log_store, global->root, &store, + &pointer_store)) { + amduat_pel_cli_free_reference(&ref); + fprintf(stderr, "error: failed to init log store\n"); + return AMDUAT_PEL_CLI_EXIT_CONFIG; + } + + memset(&entry, 0, sizeof(entry)); + entry.kind = (uint16_t)kind; + entry.payload_ref = ref; + entry.has_timestamp = false; + entry.has_actor = actor_text != NULL; + if (entry.has_actor) { + entry.actor = amduat_octets((const uint8_t *)actor_text, + strlen(actor_text)); + } + + { + amduat_asl_store_error_t err = amduat_asl_log_append( + &log_store, log_name, &entry, 1u, &offset); + amduat_pel_cli_free_reference(&ref); + if (err != AMDUAT_ASL_STORE_OK) { + fprintf(stderr, "error: log append failed: %s\n", + amduat_pel_cli_store_error_str(err)); + return amduat_pel_cli_map_store_error(err); + } + } + + printf("offset=%" PRIu64 "\n", offset); + return AMDUAT_PEL_CLI_EXIT_OK; +} + +static int amduat_pel_cli_cmd_log_read( + int argc, + char **argv, + const amduat_pel_cli_global_opts_t *global) { + const char *log_name = NULL; + uint64_t from_offset = 0u; + size_t limit = 0u; + amduat_asl_store_t store; + amduat_asl_store_fs_t fs; + amduat_asl_log_store_t log_store; + amduat_asl_pointer_store_t pointer_store; + amduat_asl_log_entry_t *entries = NULL; + size_t entries_len = 0u; + uint64_t next_offset = 0u; + bool end = false; + int i; + + if (global == NULL) { + return AMDUAT_PEL_CLI_EXIT_USAGE; + } + + for (i = 0; i < argc; ++i) { + if (strcmp(argv[i], "--log") == 0) { + if (i + 1 >= argc) { + fprintf(stderr, "error: --log requires a value\n"); + return AMDUAT_PEL_CLI_EXIT_USAGE; + } + log_name = argv[++i]; + } else if (strcmp(argv[i], "--from") == 0) { + if (i + 1 >= argc) { + fprintf(stderr, "error: --from requires a value\n"); + return AMDUAT_PEL_CLI_EXIT_USAGE; + } + from_offset = (uint64_t)strtoull(argv[++i], NULL, 10); + } else if (strcmp(argv[i], "--limit") == 0) { + if (i + 1 >= argc) { + fprintf(stderr, "error: --limit requires a value\n"); + return AMDUAT_PEL_CLI_EXIT_USAGE; + } + limit = (size_t)strtoull(argv[++i], NULL, 10); + } else if (strcmp(argv[i], "--help") == 0 || + strcmp(argv[i], "-h") == 0) { + amduat_pel_cli_print_usage(stdout); + return AMDUAT_PEL_CLI_EXIT_OK; + } else { + fprintf(stderr, "error: unknown option: %s\n", argv[i]); + return AMDUAT_PEL_CLI_EXIT_USAGE; + } + } + + if (log_name == NULL || limit == 0u) { + fprintf(stderr, "error: --log and --limit are required\n"); + return AMDUAT_PEL_CLI_EXIT_USAGE; + } + + if (!amduat_pel_cli_init_store(global->root, &store, &fs)) { + fprintf(stderr, "error: failed to initialize store\n"); + return AMDUAT_PEL_CLI_EXIT_CONFIG; + } + if (!amduat_asl_pointer_store_init(&pointer_store, global->root)) { + fprintf(stderr, "error: failed to init pointer store\n"); + return AMDUAT_PEL_CLI_EXIT_CONFIG; + } + if (!amduat_asl_log_store_init(&log_store, global->root, &store, + &pointer_store)) { + fprintf(stderr, "error: failed to init log store\n"); + return AMDUAT_PEL_CLI_EXIT_CONFIG; + } + + { + amduat_asl_store_error_t err = amduat_asl_log_read( + &log_store, log_name, from_offset, limit, + &entries, &entries_len, &next_offset, &end); + if (err != AMDUAT_ASL_STORE_OK) { + fprintf(stderr, "error: log read failed: %s\n", + amduat_pel_cli_store_error_str(err)); + return amduat_pel_cli_map_store_error(err); + } + } + + for (size_t idx = 0u; idx < entries_len; ++idx) { + uint64_t offset = from_offset + idx; + printf("offset=%" PRIu64 " kind=%u ref=", offset, entries[idx].kind); + if (!amduat_format_ref_write_text(stdout, entries[idx].payload_ref, + global->ref_format)) { + fprintf(stderr, "error: failed to format ref\n"); + amduat_asl_log_entries_free(entries, entries_len); + return AMDUAT_PEL_CLI_EXIT_CODEC; + } + if (entries[idx].has_actor) { + printf(" actor=%.*s", (int)entries[idx].actor.len, + (const char *)entries[idx].actor.data); + } + fputc('\n', stdout); + } + printf("next=%" PRIu64 " end=%s\n", next_offset, end ? "true" : "false"); + amduat_asl_log_entries_free(entries, entries_len); + return AMDUAT_PEL_CLI_EXIT_OK; +} + +static int amduat_pel_cli_cmd_log( + int argc, + char **argv, + const amduat_pel_cli_global_opts_t *global) { + if (argc < 1) { + fprintf(stderr, "error: log requires a subcommand\n"); + return AMDUAT_PEL_CLI_EXIT_USAGE; + } + if (strcmp(argv[0], "append") == 0) { + return amduat_pel_cli_cmd_log_append(argc - 1, argv + 1, global); + } + if (strcmp(argv[0], "read") == 0) { + return amduat_pel_cli_cmd_log_read(argc - 1, argv + 1, global); + } + fprintf(stderr, "error: unknown log subcommand: %s\n", argv[0]); + return AMDUAT_PEL_CLI_EXIT_USAGE; +} + static int amduat_pel_cli_cmd_help(int argc, char **argv) { (void)argc; (void)argv; @@ -3097,9 +3546,15 @@ int main(int argc, char **argv) { if (strcmp(command, "op") == 0) { return amduat_pel_cli_cmd_op(argc - i, argv + i, &global); } + if (strcmp(command, "log") == 0) { + return amduat_pel_cli_cmd_log(argc - i, argv + i, &global); + } if (strcmp(command, "matcache") == 0) { return amduat_pel_cli_cmd_matcache(argc - i, argv + i, &global); } + if (strcmp(command, "ptr") == 0) { + return amduat_pel_cli_cmd_ptr(argc - i, argv + i, &global); + } if (strcmp(command, "scheme") == 0) { return amduat_pel_cli_cmd_scheme(argc - i, argv + i, &global); }