#ifndef _POSIX_C_SOURCE #define _POSIX_C_SOURCE 200809L #endif #include "amduatd_space_doctor.h" #include "amduat/asl/asl_store_fs_meta.h" #include "amduat/asl/core.h" #include "amduat/asl/none.h" #include "amduat/asl/record.h" #include "amduat/enc/asl1_core_codec.h" #include #include #include static char *amduatd_test_make_temp_dir(void) { char tmpl[] = "/tmp/amduatd-doctor-XXXXXX"; char *dir = mkdtemp(tmpl); size_t len; char *copy; if (dir == NULL) { perror("mkdtemp"); return NULL; } len = strlen(dir); copy = (char *)malloc(len + 1u); if (copy == NULL) { fprintf(stderr, "failed to allocate temp dir copy\n"); return NULL; } memcpy(copy, dir, len + 1u); return copy; } static bool amduatd_check_status(const amduatd_space_doctor_report_t *report, const char *name, amduatd_space_doctor_status_t expected) { if (report == NULL || name == NULL) { return false; } for (size_t i = 0u; i < report->checks_len; ++i) { if (strcmp(report->checks[i].name, name) == 0) { return report->checks[i].status == expected; } } return false; } static bool amduatd_build_collection_head_name(const char *name, char **out_name) { size_t name_len; size_t total_len; char *buffer; size_t offset = 0u; if (name == NULL || out_name == NULL) { return false; } if (!amduat_asl_pointer_name_is_valid(name)) { return false; } name_len = strlen(name); total_len = 11u + name_len + 5u + 1u; buffer = (char *)malloc(total_len); if (buffer == NULL) { return false; } memcpy(buffer + offset, "collection/", 11u); offset += 11u; memcpy(buffer + offset, name, name_len); offset += name_len; memcpy(buffer + offset, "/head", 5u); offset += 5u; buffer[offset] = '\0'; *out_name = buffer; return true; } static bool amduatd_build_collection_log_head_name(const char *name, char **out_name) { size_t name_len; size_t log_name_len; size_t total_len; char *buffer; size_t offset = 0u; if (name == NULL || out_name == NULL) { return false; } if (!amduat_asl_pointer_name_is_valid(name)) { return false; } name_len = strlen(name); log_name_len = 11u + name_len + 4u; total_len = 4u + log_name_len + 5u + 1u; buffer = (char *)malloc(total_len); if (buffer == NULL) { return false; } memcpy(buffer + offset, "log/", 4u); offset += 4u; memcpy(buffer + offset, "collection/", 11u); offset += 11u; memcpy(buffer + offset, name, name_len); offset += name_len; memcpy(buffer + offset, "/log", 4u); offset += 4u; memcpy(buffer + offset, "/head", 5u); offset += 5u; buffer[offset] = '\0'; *out_name = buffer; return true; } static void amduatd_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 amduatd_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 amduatd_build_edge_index_state_payload( amduat_reference_t graph_ref, amduat_octets_t *out_payload) { static const uint8_t k_magic[8] = { 'A', 'S', 'L', 'E', 'I', 'X', '1', '\0' }; amduat_octets_t ref_bytes = amduat_octets(NULL, 0u); uint8_t *payload = NULL; size_t payload_len = 0u; size_t offset = 0u; if (out_payload == NULL) { return false; } *out_payload = amduat_octets(NULL, 0u); if (!amduat_enc_asl1_core_encode_reference_v1(graph_ref, &ref_bytes)) { return false; } if (ref_bytes.len > UINT32_MAX) { free((void *)ref_bytes.data); return false; } payload_len = 8u + 4u + 8u + 4u + ref_bytes.len; payload = (uint8_t *)malloc(payload_len); if (payload == NULL) { free((void *)ref_bytes.data); return false; } memcpy(payload + offset, k_magic, 8u); offset += 8u; amduatd_store_u32_le(payload + offset, 1u); offset += 4u; amduatd_store_u64_le(payload + offset, 0u); offset += 8u; amduatd_store_u32_le(payload + offset, (uint32_t)ref_bytes.len); offset += 4u; memcpy(payload + offset, ref_bytes.data, ref_bytes.len); offset += ref_bytes.len; free((void *)ref_bytes.data); *out_payload = amduat_octets(payload, payload_len); return offset == payload_len; } static int amduatd_test_empty_store(void) { char *root = amduatd_test_make_temp_dir(); amduat_asl_store_fs_config_t cfg; amduatd_store_ctx_t store_ctx; amduat_asl_store_t store; amduat_asl_pointer_store_t pointer_store; amduatd_space_t space; amduatd_cfg_t dcfg; amduatd_fed_cfg_t fed_cfg; amduatd_space_doctor_report_t report; if (root == NULL) { return 1; } memset(&cfg, 0, sizeof(cfg)); if (!amduat_asl_store_fs_init_root(root, NULL, &cfg)) { fprintf(stderr, "failed to init store root\n"); free(root); return 1; } memset(&store_ctx, 0, sizeof(store_ctx)); memset(&store, 0, sizeof(store)); if (!amduatd_store_init(&store, &cfg, &store_ctx, root, AMDUATD_STORE_BACKEND_FS)) { fprintf(stderr, "failed to init fs backend\n"); free(root); return 1; } if (!amduat_asl_pointer_store_init(&pointer_store, root)) { fprintf(stderr, "failed to init pointer store\n"); free(root); return 1; } if (!amduatd_space_init(&space, NULL, false)) { fprintf(stderr, "failed to init space\n"); free(root); return 1; } memset(&dcfg, 0, sizeof(dcfg)); dcfg.space = space; memset(&fed_cfg, 0, sizeof(fed_cfg)); if (!amduatd_space_doctor_run(&store, &pointer_store, &dcfg.space, &dcfg, &fed_cfg, &report)) { fprintf(stderr, "doctor run failed\n"); free(root); return 1; } if (report.backend != AMDUATD_STORE_BACKEND_FS) { fprintf(stderr, "expected fs backend\n"); amduatd_space_doctor_report_free(&report); free(root); return 1; } if (!amduatd_check_status(&report, "pointer_name_validation", AMDUATD_DOCTOR_OK) || !amduatd_check_status(&report, "edges_index_head", AMDUATD_DOCTOR_WARN) || !amduatd_check_status(&report, "edges_collection_snapshot_head", AMDUATD_DOCTOR_WARN) || !amduatd_check_status(&report, "edges_collection_log_head", AMDUATD_DOCTOR_WARN) || !amduatd_check_status(&report, "index_current_state", AMDUATD_DOCTOR_SKIPPED) || !amduatd_check_status(&report, "index_log_scan", AMDUATD_DOCTOR_SKIPPED)) { fprintf(stderr, "unexpected doctor status for empty store\n"); amduatd_space_doctor_report_free(&report); free(root); return 1; } amduatd_space_doctor_report_free(&report); free(root); return 0; } static int amduatd_test_minimal_fixture(void) { char *root = amduatd_test_make_temp_dir(); amduat_asl_store_fs_config_t cfg; amduatd_store_ctx_t store_ctx; amduat_asl_store_t store; amduat_asl_pointer_store_t pointer_store; amduatd_space_t space; amduatd_cfg_t dcfg; amduatd_fed_cfg_t fed_cfg; amduatd_space_doctor_report_t report; amduat_reference_t none_ref; amduat_reference_t graph_ref; amduat_reference_t edge_index_ref; amduat_octets_t collection_name = amduat_octets(NULL, 0u); amduat_octets_t index_head_name = amduat_octets(NULL, 0u); char *collection_head = NULL; char *collection_log_head = NULL; amduat_octets_t payload = amduat_octets(NULL, 0u); bool swapped = false; if (root == NULL) { return 1; } memset(&cfg, 0, sizeof(cfg)); if (!amduat_asl_store_fs_init_root(root, NULL, &cfg)) { fprintf(stderr, "failed to init store root\n"); free(root); return 1; } memset(&store_ctx, 0, sizeof(store_ctx)); memset(&store, 0, sizeof(store)); if (!amduatd_store_init(&store, &cfg, &store_ctx, root, AMDUATD_STORE_BACKEND_FS)) { fprintf(stderr, "failed to init fs backend\n"); free(root); return 1; } if (!amduat_asl_pointer_store_init(&pointer_store, root)) { fprintf(stderr, "failed to init pointer store\n"); free(root); return 1; } if (!amduatd_space_init(&space, NULL, false)) { fprintf(stderr, "failed to init space\n"); free(root); return 1; } memset(&dcfg, 0, sizeof(dcfg)); dcfg.space = space; memset(&fed_cfg, 0, sizeof(fed_cfg)); { amduat_artifact_t none_artifact; if (!amduat_asl_none_artifact(&none_artifact)) { fprintf(stderr, "failed to build none artifact\n"); free(root); return 1; } if (amduat_asl_store_put(&store, none_artifact, &none_ref) != AMDUAT_ASL_STORE_OK) { fprintf(stderr, "failed to store none artifact\n"); amduat_artifact_free(&none_artifact); free(root); return 1; } amduat_artifact_free(&none_artifact); } { amduat_artifact_t graph_artifact = amduat_artifact(amduat_octets("graph", 5u)); if (amduat_asl_store_put(&store, graph_artifact, &graph_ref) != AMDUAT_ASL_STORE_OK) { fprintf(stderr, "failed to store graph artifact\n"); free(root); return 1; } } if (!amduatd_build_edge_index_state_payload(graph_ref, &payload)) { fprintf(stderr, "failed to encode edge index state\n"); free(root); return 1; } if (amduat_asl_record_store_put(&store, amduat_octets("tgk/edge_index_state", 20u), payload, &edge_index_ref) != AMDUAT_ASL_STORE_OK) { fprintf(stderr, "failed to store edge index record\n"); free((void *)payload.data); free(root); return 1; } free((void *)payload.data); if (!amduatd_space_edges_collection_name(&dcfg.space, &collection_name) || !amduatd_space_edges_index_head_name(&dcfg.space, &index_head_name)) { fprintf(stderr, "failed to build scoped names\n"); free(root); return 1; } if (!amduatd_build_collection_head_name((const char *)collection_name.data, &collection_head) || !amduatd_build_collection_log_head_name( (const char *)collection_name.data, &collection_log_head)) { fprintf(stderr, "failed to build collection heads\n"); free(root); return 1; } if (amduat_asl_pointer_cas(&pointer_store, (const char *)index_head_name.data, false, NULL, &edge_index_ref, &swapped) != AMDUAT_ASL_POINTER_OK || !swapped || amduat_asl_pointer_cas(&pointer_store, collection_head, false, NULL, &none_ref, &swapped) != AMDUAT_ASL_POINTER_OK || !swapped || amduat_asl_pointer_cas(&pointer_store, collection_log_head, false, NULL, &none_ref, &swapped) != AMDUAT_ASL_POINTER_OK || !swapped) { fprintf(stderr, "failed to seed pointers\n"); free(root); return 1; } if (!amduatd_space_doctor_run(&store, &pointer_store, &dcfg.space, &dcfg, &fed_cfg, &report)) { fprintf(stderr, "doctor run failed\n"); free(root); return 1; } if (!amduatd_check_status(&report, "edges_index_head", AMDUATD_DOCTOR_OK) || !amduatd_check_status(&report, "edges_collection_snapshot_head", AMDUATD_DOCTOR_OK) || !amduatd_check_status(&report, "edges_collection_log_head", AMDUATD_DOCTOR_OK) || !amduatd_check_status(&report, "edge_index_state_parse", AMDUATD_DOCTOR_OK) || !amduatd_check_status(&report, "edge_index_graph_ref", AMDUATD_DOCTOR_OK) || !amduatd_check_status(&report, "index_current_state", AMDUATD_DOCTOR_SKIPPED)) { fprintf(stderr, "unexpected doctor status for fixture\n"); amduatd_space_doctor_report_free(&report); free(root); return 1; } amduatd_space_doctor_report_free(&report); free((void *)collection_name.data); free((void *)index_head_name.data); free(collection_head); free(collection_log_head); free(root); return 0; } int main(void) { if (amduatd_test_empty_store() != 0) { return 1; } if (amduatd_test_minimal_fixture() != 0) { return 1; } return 0; }