diff --git a/src/amduatd.c b/src/amduatd.c index a69de32..cae7b09 100644 --- a/src/amduatd.c +++ b/src/amduatd.c @@ -1,6 +1,13 @@ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif #define _POSIX_C_SOURCE 200809L #include "amduat/asl/artifact_io.h" +#include "amduat/asl/collection.h" +#include "amduat/asl/collection_view.h" +#include "amduat/asl/none.h" +#include "amduat/asl/record.h" #include "amduat/asl/asl_store_fs.h" #include "amduat/asl/asl_store_fs_meta.h" #include "amduat/asl/ref_text.h" @@ -23,6 +30,7 @@ #include "amduat/pel/opreg_kernel.h" #include "amduat/pel/run.h" #include "amduat/util/hex.h" +#include "amduat/util/log.h" #include #include @@ -35,6 +43,7 @@ #include #include #include +#include #include #include @@ -75,54 +84,82 @@ static bool amduatd_strbuf_append_char(amduatd_strbuf_t *b, char c); static const char *const AMDUATD_DEFAULT_ROOT = ".amduat-asl"; static const char *const AMDUATD_DEFAULT_SOCK = "amduatd.sock"; static const char *const AMDUATD_EDGES_FILE = ".amduatd.edges"; +static const char *const AMDUATD_EDGE_COLLECTION = "daemon/edges"; static const uint64_t AMDUATD_FED_TICK_MS = 1000u; -static const uint32_t AMDUATD_TGK_EDGE_TYPE = 0u; +static const uint16_t AMDUATD_EDGE_COLLECTION_KIND = 1u; typedef struct { - amduat_reference_t *refs; + amduat_reference_t record_ref; + amduat_reference_t src_ref; + amduat_reference_t dst_ref; + char *rel; +} amduatd_edge_entry_t; + +typedef struct { + amduatd_edge_entry_t *items; size_t len; size_t cap; -} amduatd_ref_list_t; +} amduatd_edge_list_t; -static void amduatd_ref_list_free(amduatd_ref_list_t *list) { - size_t i; +static void amduatd_edge_entry_free(amduatd_edge_entry_t *entry) { + if (entry == NULL) { + return; + } + amduat_reference_free(&entry->record_ref); + amduat_reference_free(&entry->src_ref); + amduat_reference_free(&entry->dst_ref); + free(entry->rel); + entry->rel = NULL; +} + +static void amduatd_edge_list_clear(amduatd_edge_list_t *list) { if (list == NULL) { return; } - for (i = 0; i < list->len; ++i) { - amduat_reference_free(&list->refs[i]); + for (size_t i = 0; i < list->len; ++i) { + amduatd_edge_entry_free(&list->items[i]); } - free(list->refs); - list->refs = NULL; + free(list->items); + list->items = NULL; list->len = 0; list->cap = 0; } -static bool amduatd_ref_list_push(amduatd_ref_list_t *list, - amduat_reference_t ref) { - amduat_reference_t cloned; - amduat_reference_t *next; +static bool amduatd_edge_list_push(amduatd_edge_list_t *list, + const amduatd_edge_entry_t *entry) { + amduatd_edge_entry_t cloned; + amduatd_edge_entry_t *next; - if (list == NULL) { + if (list == NULL || entry == NULL) { return false; } memset(&cloned, 0, sizeof(cloned)); - if (!amduat_reference_clone(ref, &cloned)) { + if (!amduat_reference_clone(entry->record_ref, &cloned.record_ref) || + !amduat_reference_clone(entry->src_ref, &cloned.src_ref) || + !amduat_reference_clone(entry->dst_ref, &cloned.dst_ref)) { + amduatd_edge_entry_free(&cloned); return false; } + if (entry->rel != NULL) { + cloned.rel = strdup(entry->rel); + if (cloned.rel == NULL) { + amduatd_edge_entry_free(&cloned); + return false; + } + } if (list->len == list->cap) { size_t next_cap = list->cap != 0 ? list->cap * 2u : 64u; - next = (amduat_reference_t *)realloc(list->refs, - next_cap * sizeof(*list->refs)); + next = (amduatd_edge_entry_t *)realloc(list->items, + next_cap * sizeof(*list->items)); if (next == NULL) { - amduat_reference_free(&cloned); + amduatd_edge_entry_free(&cloned); return false; } - list->refs = next; + list->items = next; list->cap = next_cap; } - list->refs[list->len++] = cloned; + list->items[list->len++] = cloned; return true; } @@ -136,7 +173,8 @@ typedef struct { amduat_reference_t rel_within_domain_ref; amduat_reference_t rel_computed_by_ref; amduat_reference_t rel_has_provenance_ref; - amduatd_ref_list_t edge_refs; + amduat_asl_collection_store_t edge_collection; + amduatd_edge_list_t edges; } amduatd_concepts_t; static bool amduatd_concepts_put_name_artifact(amduat_asl_store_t *store, @@ -147,6 +185,7 @@ static bool amduatd_concepts_put_edge(amduat_asl_store_t *store, amduat_reference_t from, amduat_reference_t to, amduat_reference_t relation_concept_ref, + amduat_octets_t actor, amduat_reference_t *out_edge_ref); static bool amduatd_concepts_lookup_alias(amduat_asl_store_t *store, const amduat_asl_store_fs_config_t *cfg, @@ -165,7 +204,657 @@ static void amduatd_concepts_free(amduatd_concepts_t *c) { amduat_reference_free(&c->rel_within_domain_ref); amduat_reference_free(&c->rel_computed_by_ref); amduat_reference_free(&c->rel_has_provenance_ref); - amduatd_ref_list_free(&c->edge_refs); + amduatd_edge_list_clear(&c->edges); +} + +enum { + AMDUATD_EDGE_MAGIC_LEN = 8, + AMDUATD_EDGE_VERSION = 1 +}; + +static const uint8_t k_amduatd_edge_magic[AMDUATD_EDGE_MAGIC_LEN] = { + 'A', 'S', 'L', 'E', 'D', 'G', '1', '\0' +}; + +static const char *const AMDUATD_EDGE_SCHEMA = "tgk/edge"; + +static const char *const AMDUATD_REL_ALIAS = "alias"; +static const char *const AMDUATD_REL_MATERIALIZES = "materializes"; +static const char *const AMDUATD_REL_REPRESENTS = "represents"; +static const char *const AMDUATD_REL_REQUIRES_KEY = "requires_key"; +static const char *const AMDUATD_REL_WITHIN_DOMAIN = "within_domain"; +static const char *const AMDUATD_REL_COMPUTED_BY = "computed_by"; +static const char *const AMDUATD_REL_HAS_PROVENANCE = "has_provenance"; + +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 bool amduatd_read_u32_le(const uint8_t *data, + size_t len, + size_t *offset, + uint32_t *out) { + if (len - *offset < 4u) { + return false; + } + *out = (uint32_t)data[*offset] | + ((uint32_t)data[*offset + 1u] << 8) | + ((uint32_t)data[*offset + 2u] << 16) | + ((uint32_t)data[*offset + 3u] << 24); + *offset += 4u; + return true; +} + +static bool amduatd_add_size(size_t *acc, size_t add) { + if (*acc > SIZE_MAX - add) { + return false; + } + *acc += add; + return true; +} + +static bool amduatd_is_ascii(amduat_octets_t bytes) { + if (bytes.len == 0u || bytes.data == NULL) { + return false; + } + for (size_t i = 0u; i < bytes.len; ++i) { + uint8_t c = bytes.data[i]; + if (c < 0x20u || c > 0x7eu) { + return false; + } + } + return true; +} + +static bool amduatd_edge_payload_encode(amduat_reference_t src, + amduat_reference_t dst, + const char *rel, + amduat_octets_t *out_payload) { + amduat_octets_t src_bytes = amduat_octets(NULL, 0u); + amduat_octets_t dst_bytes = amduat_octets(NULL, 0u); + size_t total_len = 0u; + size_t offset = 0u; + uint8_t *buffer = NULL; + uint32_t rel_len = 0u; + + if (out_payload == NULL || rel == NULL) { + return false; + } + out_payload->data = NULL; + out_payload->len = 0u; + + { + size_t rel_len_full = strlen(rel); + if (rel_len_full > UINT32_MAX) { + return false; + } + rel_len = (uint32_t)rel_len_full; + } + if (rel_len == 0u) { + return false; + } + if (!amduatd_is_ascii(amduat_octets((const uint8_t *)rel, rel_len))) { + return false; + } + if (!amduat_enc_asl1_core_encode_reference_v1(src, &src_bytes) || + !amduat_enc_asl1_core_encode_reference_v1(dst, &dst_bytes)) { + free((void *)src_bytes.data); + free((void *)dst_bytes.data); + return false; + } + if (src_bytes.len > UINT32_MAX || dst_bytes.len > UINT32_MAX) { + free((void *)src_bytes.data); + free((void *)dst_bytes.data); + return false; + } + + if (!amduatd_add_size(&total_len, + AMDUATD_EDGE_MAGIC_LEN + 4u + 4u + src_bytes.len) || + !amduatd_add_size(&total_len, 4u + dst_bytes.len) || + !amduatd_add_size(&total_len, 4u + rel_len) || + !amduatd_add_size(&total_len, 1u)) { + free((void *)src_bytes.data); + free((void *)dst_bytes.data); + return false; + } + + buffer = (uint8_t *)malloc(total_len); + if (buffer == NULL) { + free((void *)src_bytes.data); + free((void *)dst_bytes.data); + return false; + } + + memcpy(buffer + offset, k_amduatd_edge_magic, AMDUATD_EDGE_MAGIC_LEN); + offset += AMDUATD_EDGE_MAGIC_LEN; + amduatd_store_u32_le(buffer + offset, AMDUATD_EDGE_VERSION); + offset += 4u; + + amduatd_store_u32_le(buffer + offset, (uint32_t)src_bytes.len); + offset += 4u; + memcpy(buffer + offset, src_bytes.data, src_bytes.len); + offset += src_bytes.len; + + amduatd_store_u32_le(buffer + offset, (uint32_t)dst_bytes.len); + offset += 4u; + memcpy(buffer + offset, dst_bytes.data, dst_bytes.len); + offset += dst_bytes.len; + + amduatd_store_u32_le(buffer + offset, rel_len); + offset += 4u; + memcpy(buffer + offset, rel, rel_len); + offset += rel_len; + + buffer[offset++] = 0u; + + free((void *)src_bytes.data); + free((void *)dst_bytes.data); + + if (offset != total_len) { + free(buffer); + return false; + } + + out_payload->data = buffer; + out_payload->len = total_len; + return true; +} + +static bool amduatd_edge_payload_decode(amduat_octets_t payload, + amduat_reference_t *out_src, + amduat_reference_t *out_dst, + char **out_rel) { + size_t offset = 0u; + uint32_t version = 0u; + uint32_t src_len = 0u; + uint32_t dst_len = 0u; + uint32_t rel_len = 0u; + uint8_t flags = 0u; + amduat_octets_t src_bytes; + amduat_octets_t dst_bytes; + char *rel = NULL; + + if (out_src == NULL || out_dst == NULL || out_rel == NULL) { + return false; + } + *out_src = amduat_reference(0u, amduat_octets(NULL, 0u)); + *out_dst = amduat_reference(0u, amduat_octets(NULL, 0u)); + *out_rel = NULL; + + if (payload.len < AMDUATD_EDGE_MAGIC_LEN + 4u + 1u || + payload.data == NULL) { + return false; + } + if (memcmp(payload.data, k_amduatd_edge_magic, + AMDUATD_EDGE_MAGIC_LEN) != 0) { + return false; + } + offset += AMDUATD_EDGE_MAGIC_LEN; + if (!amduatd_read_u32_le(payload.data, payload.len, &offset, &version) || + version != AMDUATD_EDGE_VERSION) { + return false; + } + if (!amduatd_read_u32_le(payload.data, payload.len, &offset, &src_len)) { + return false; + } + if (payload.len - offset < src_len) { + return false; + } + src_bytes = amduat_octets(payload.data + offset, src_len); + offset += src_len; + if (!amduatd_read_u32_le(payload.data, payload.len, &offset, &dst_len)) { + return false; + } + if (payload.len - offset < dst_len) { + return false; + } + dst_bytes = amduat_octets(payload.data + offset, dst_len); + offset += dst_len; + if (!amduatd_read_u32_le(payload.data, payload.len, &offset, &rel_len)) { + return false; + } + if (payload.len - offset < rel_len + 1u) { + return false; + } + if (!amduatd_is_ascii(amduat_octets(payload.data + offset, rel_len))) { + return false; + } + rel = (char *)malloc(rel_len + 1u); + if (rel == NULL) { + return false; + } + memcpy(rel, payload.data + offset, rel_len); + rel[rel_len] = '\0'; + offset += rel_len; + flags = payload.data[offset++]; + (void)flags; + + if (offset != payload.len) { + free(rel); + return false; + } + if (!amduat_enc_asl1_core_decode_reference_v1(src_bytes, out_src) || + !amduat_enc_asl1_core_decode_reference_v1(dst_bytes, out_dst)) { + amduat_reference_free(out_src); + amduat_reference_free(out_dst); + free(rel); + return false; + } + + *out_rel = rel; + return true; +} + +static const char *amduatd_relation_name_for_ref( + const amduatd_concepts_t *c, + amduat_reference_t ref) { + if (c == NULL) { + return NULL; + } + if (amduat_reference_eq(ref, c->rel_aliases_ref)) { + return AMDUATD_REL_ALIAS; + } + if (amduat_reference_eq(ref, c->rel_materializes_ref)) { + return AMDUATD_REL_MATERIALIZES; + } + if (amduat_reference_eq(ref, c->rel_represents_ref)) { + return AMDUATD_REL_REPRESENTS; + } + if (amduat_reference_eq(ref, c->rel_requires_key_ref)) { + return AMDUATD_REL_REQUIRES_KEY; + } + if (amduat_reference_eq(ref, c->rel_within_domain_ref)) { + return AMDUATD_REL_WITHIN_DOMAIN; + } + if (amduat_reference_eq(ref, c->rel_computed_by_ref)) { + return AMDUATD_REL_COMPUTED_BY; + } + if (amduat_reference_eq(ref, c->rel_has_provenance_ref)) { + return AMDUATD_REL_HAS_PROVENANCE; + } + return NULL; +} + +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 bool amduatd_get_none_ref(amduat_asl_store_t *store, + amduat_reference_t *out_ref) { + amduat_artifact_t none_artifact; + + if (store == NULL || out_ref == NULL) { + return false; + } + if (!amduat_asl_none_artifact(&none_artifact)) { + return false; + } + if (amduat_asl_store_put(store, none_artifact, out_ref) != + AMDUAT_ASL_STORE_OK) { + amduat_artifact_free(&none_artifact); + return false; + } + amduat_artifact_free(&none_artifact); + return true; +} + +static bool amduatd_put_view_params(amduat_asl_store_t *store, + uint64_t from_offset, + uint32_t limit, + amduat_reference_t *out_ref) { + uint8_t payload[12]; + + if (store == NULL || out_ref == NULL) { + return false; + } + payload[0] = (uint8_t)(from_offset & 0xffu); + payload[1] = (uint8_t)((from_offset >> 8) & 0xffu); + payload[2] = (uint8_t)((from_offset >> 16) & 0xffu); + payload[3] = (uint8_t)((from_offset >> 24) & 0xffu); + payload[4] = (uint8_t)((from_offset >> 32) & 0xffu); + payload[5] = (uint8_t)((from_offset >> 40) & 0xffu); + payload[6] = (uint8_t)((from_offset >> 48) & 0xffu); + payload[7] = (uint8_t)((from_offset >> 56) & 0xffu); + payload[8] = (uint8_t)(limit & 0xffu); + payload[9] = (uint8_t)((limit >> 8) & 0xffu); + payload[10] = (uint8_t)((limit >> 16) & 0xffu); + payload[11] = (uint8_t)((limit >> 24) & 0xffu); + + if (amduat_asl_record_store_put( + store, + amduat_octets("pel/params", strlen("pel/params")), + amduat_octets(payload, sizeof(payload)), + out_ref) != AMDUAT_ASL_STORE_OK) { + return false; + } + return true; +} + +static void amduatd_program_free(amduat_pel_program_t *program) { + if (program == NULL || program->nodes == NULL) { + return; + } + for (size_t i = 0u; i < program->nodes_len; ++i) { + free(program->nodes[i].inputs); + } + free(program->nodes); + free(program->roots); + program->nodes = NULL; + program->roots = NULL; + program->nodes_len = 0u; + program->roots_len = 0u; +} + +static bool amduatd_build_collection_view_program( + amduat_pel_program_t *out_program) { + static const char k_op_snapshot[] = "collection.snapshot_decode_v1"; + static const char k_op_read_range[] = "log.read_range_v1"; + static const char k_op_merge[] = "collection.merge_refs_v1"; + amduat_pel_node_t *nodes; + amduat_pel_root_ref_t *roots; + + if (out_program == NULL) { + return false; + } + memset(out_program, 0, sizeof(*out_program)); + + nodes = (amduat_pel_node_t *)calloc(3u, sizeof(*nodes)); + roots = (amduat_pel_root_ref_t *)calloc(1u, sizeof(*roots)); + if (nodes == NULL || roots == NULL) { + free(nodes); + free(roots); + return false; + } + + nodes[0].id = 1u; + nodes[0].op.name = amduat_octets(k_op_snapshot, strlen(k_op_snapshot)); + nodes[0].op.version = 1u; + nodes[0].inputs_len = 1u; + nodes[0].inputs = + (amduat_pel_dag_input_t *)calloc(1u, sizeof(*nodes[0].inputs)); + if (nodes[0].inputs == NULL) { + free(nodes); + free(roots); + return false; + } + nodes[0].inputs[0].kind = AMDUAT_PEL_DAG_INPUT_EXTERNAL; + nodes[0].inputs[0].value.external.input_index = 0u; + nodes[0].params = amduat_octets(NULL, 0u); + + nodes[1].id = 2u; + nodes[1].op.name = amduat_octets(k_op_read_range, strlen(k_op_read_range)); + nodes[1].op.version = 1u; + nodes[1].inputs_len = 3u; + nodes[1].inputs = + (amduat_pel_dag_input_t *)calloc(3u, sizeof(*nodes[1].inputs)); + if (nodes[1].inputs == NULL) { + free(nodes[0].inputs); + free(nodes); + free(roots); + return false; + } + nodes[1].inputs[0].kind = AMDUAT_PEL_DAG_INPUT_EXTERNAL; + nodes[1].inputs[0].value.external.input_index = 1u; + nodes[1].inputs[1].kind = AMDUAT_PEL_DAG_INPUT_EXTERNAL; + nodes[1].inputs[1].value.external.input_index = 2u; + nodes[1].inputs[2].kind = AMDUAT_PEL_DAG_INPUT_EXTERNAL; + nodes[1].inputs[2].value.external.input_index = 3u; + nodes[1].params = amduat_octets(NULL, 0u); + + nodes[2].id = 3u; + nodes[2].op.name = amduat_octets(k_op_merge, strlen(k_op_merge)); + nodes[2].op.version = 1u; + nodes[2].inputs_len = 4u; + nodes[2].inputs = + (amduat_pel_dag_input_t *)calloc(4u, sizeof(*nodes[2].inputs)); + if (nodes[2].inputs == NULL) { + free(nodes[1].inputs); + free(nodes[0].inputs); + free(nodes); + free(roots); + return false; + } + nodes[2].inputs[0].kind = AMDUAT_PEL_DAG_INPUT_NODE; + nodes[2].inputs[0].value.node.node_id = 1u; + nodes[2].inputs[0].value.node.output_index = 0u; + nodes[2].inputs[1].kind = AMDUAT_PEL_DAG_INPUT_NODE; + nodes[2].inputs[1].value.node.node_id = 2u; + nodes[2].inputs[1].value.node.output_index = 0u; + nodes[2].inputs[2].kind = AMDUAT_PEL_DAG_INPUT_EXTERNAL; + nodes[2].inputs[2].value.external.input_index = 2u; + nodes[2].inputs[3].kind = AMDUAT_PEL_DAG_INPUT_EXTERNAL; + nodes[2].inputs[3].value.external.input_index = 3u; + nodes[2].params = amduat_octets(NULL, 0u); + + roots[0].node_id = 3u; + roots[0].output_index = 0u; + + out_program->nodes = nodes; + out_program->nodes_len = 3u; + out_program->roots = roots; + out_program->roots_len = 1u; + return true; +} + +static bool amduatd_collection_view(amduat_asl_store_t *store, + const char *root_path, + const char *name, + uint64_t from, + uint32_t limit, + amduat_asl_collection_view_t *out_view) { + amduat_asl_pointer_store_t pointer_store; + amduat_reference_t snapshot_ref; + amduat_reference_t log_head_ref; + amduat_reference_t none_ref; + amduat_reference_t params_ref; + amduat_reference_t program_ref; + amduat_reference_t input_refs[4]; + amduat_pel_program_t program; + amduat_octets_t program_bytes = amduat_octets(NULL, 0u); + amduat_pel_run_result_t run_result; + amduat_artifact_t view_artifact; + amduat_asl_collection_view_t view; + amduat_type_tag_t expected_type_tag; + amduat_asl_encoding_profile_id_t expected_profile; + char *snapshot_name = NULL; + char *log_head_name = NULL; + bool snapshot_exists = false; + bool log_head_exists = false; + bool ok = false; + + if (out_view == NULL) { + return false; + } + memset(out_view, 0, sizeof(*out_view)); + memset(&snapshot_ref, 0, sizeof(snapshot_ref)); + memset(&log_head_ref, 0, sizeof(log_head_ref)); + memset(&none_ref, 0, sizeof(none_ref)); + memset(¶ms_ref, 0, sizeof(params_ref)); + memset(&program_ref, 0, sizeof(program_ref)); + memset(&run_result, 0, sizeof(run_result)); + memset(&view, 0, sizeof(view)); + + if (store == NULL || root_path == NULL || name == NULL) { + return false; + } + if (!amduat_asl_pointer_store_init(&pointer_store, root_path)) { + return false; + } + if (!amduatd_get_none_ref(store, &none_ref)) { + return false; + } + if (!amduatd_build_collection_head_name(name, &snapshot_name) || + !amduatd_build_collection_log_head_name(name, &log_head_name)) { + goto view_cleanup; + } + if (amduat_asl_pointer_get(&pointer_store, snapshot_name, + &snapshot_exists, &snapshot_ref) != + AMDUAT_ASL_POINTER_OK) { + goto view_cleanup; + } + if (!snapshot_exists && + !amduat_reference_clone(none_ref, &snapshot_ref)) { + goto view_cleanup; + } + if (amduat_asl_pointer_get(&pointer_store, log_head_name, + &log_head_exists, &log_head_ref) != + AMDUAT_ASL_POINTER_OK) { + goto view_cleanup; + } + if (!log_head_exists && + !amduat_reference_clone(none_ref, &log_head_ref)) { + goto view_cleanup; + } + if (!amduatd_put_view_params(store, from, limit, ¶ms_ref)) { + goto view_cleanup; + } + if (!amduatd_build_collection_view_program(&program)) { + goto view_cleanup; + } + if (!amduat_enc_pel_program_dag_encode_v1(&program, &program_bytes)) { + amduatd_program_free(&program); + goto view_cleanup; + } + amduatd_program_free(&program); + + if (!amduat_pel_program_dag_desc_get_program_binding(&expected_type_tag, + &expected_profile)) { + goto view_cleanup; + } + if (expected_profile != AMDUAT_PEL_ENC_PROGRAM_DAG_V1) { + goto view_cleanup; + } + { + amduat_artifact_t program_artifact = + amduat_artifact_with_type(program_bytes, expected_type_tag); + if (amduat_asl_store_put(store, program_artifact, &program_ref) != + AMDUAT_ASL_STORE_OK) { + goto view_cleanup; + } + } + + input_refs[0] = snapshot_ref; + input_refs[1] = log_head_ref; + input_refs[2] = params_ref; + input_refs[3] = params_ref; + + if (!amduat_pel_surf_run_with_result(store, + amduat_pel_program_dag_scheme_ref(), + program_ref, + input_refs, + 4u, + true, + params_ref, + &run_result)) { + goto view_cleanup; + } + if (!run_result.has_result_value || + run_result.result_value.core_result.status != AMDUAT_PEL_EXEC_STATUS_OK || + run_result.output_refs_len != 1u) { + goto view_cleanup; + } + + memset(&view_artifact, 0, sizeof(view_artifact)); + if (amduat_asl_store_get(store, run_result.output_refs[0], + &view_artifact) != AMDUAT_ASL_STORE_OK) { + goto view_cleanup; + } + if (!view_artifact.has_type_tag || + view_artifact.type_tag.tag_id != AMDUAT_TYPE_TAG_ASL_COLLECTION_VIEW_1 || + !amduat_asl_collection_view_decode_v1(view_artifact.bytes, &view)) { + amduat_artifact_free(&view_artifact); + goto view_cleanup; + } + amduat_artifact_free(&view_artifact); + + *out_view = view; + memset(&view, 0, sizeof(view)); + ok = true; + +view_cleanup: + amduat_asl_collection_view_free(&view); + if (run_result.has_result_value) { + amduat_enc_pel1_result_free(&run_result.result_value); + } + if (run_result.output_refs != NULL) { + amduat_pel_surf_free_refs(run_result.output_refs, + run_result.output_refs_len); + } + amduat_pel_surf_free_ref(&run_result.result_ref); + amduat_reference_free(&snapshot_ref); + amduat_reference_free(&log_head_ref); + amduat_reference_free(&none_ref); + amduat_reference_free(¶ms_ref); + amduat_reference_free(&program_ref); + free((void *)program_bytes.data); + free(snapshot_name); + free(log_head_name); + return ok; } static const char k_amduatd_contract_v1_json[] = @@ -843,6 +1532,7 @@ static bool amduatd_concepts_ensure_alias(amduatd_concepts_t *c, name_ref, concept_ref, c->rel_aliases_ref, + amduat_octets(NULL, 0u), &edge_ref)) { amduat_reference_free(&name_ref); amduat_reference_free(&edge_ref); @@ -853,14 +1543,256 @@ static bool amduatd_concepts_ensure_alias(amduatd_concepts_t *c, return true; } +static bool amduatd_concepts_load_edges(amduatd_concepts_t *c, + amduat_asl_store_t *store) { + amduat_asl_collection_view_t view; + bool ok = false; + + if (c == NULL || store == NULL) { + return false; + } + + amduatd_edge_list_clear(&c->edges); + + memset(&view, 0, sizeof(view)); + if (!amduatd_collection_view(store, + c->root_path, + AMDUATD_EDGE_COLLECTION, + 0u, + UINT32_MAX, + &view)) { + return false; + } + + for (size_t i = 0u; i < view.refs_len; ++i) { + amduat_reference_t record_ref = view.refs[i]; + amduat_asl_record_t record; + amduat_reference_t src_ref; + amduat_reference_t dst_ref; + char *rel = NULL; + amduatd_edge_entry_t entry; + amduat_asl_store_error_t err; + bool schema_ok = false; + + memset(&record, 0, sizeof(record)); + err = amduat_asl_record_store_get(store, record_ref, &record); + if (err != AMDUAT_ASL_STORE_OK) { + continue; + } + if (record.schema.len == strlen(AMDUATD_EDGE_SCHEMA) && + memcmp(record.schema.data, AMDUATD_EDGE_SCHEMA, + record.schema.len) == 0) { + schema_ok = true; + } + if (!schema_ok || + !amduatd_edge_payload_decode(record.payload, + &src_ref, + &dst_ref, + &rel)) { + amduat_asl_record_free(&record); + continue; + } + amduat_asl_record_free(&record); + + memset(&entry, 0, sizeof(entry)); + if (!amduat_reference_clone(record_ref, &entry.record_ref)) { + amduat_reference_free(&src_ref); + amduat_reference_free(&dst_ref); + free(rel); + goto load_cleanup; + } + entry.src_ref = src_ref; + entry.dst_ref = dst_ref; + entry.rel = rel; + if (!amduatd_edge_list_push(&c->edges, &entry)) { + amduatd_edge_entry_free(&entry); + goto load_cleanup; + } + amduatd_edge_entry_free(&entry); + } + + ok = true; + +load_cleanup: + amduat_asl_collection_view_free(&view); + return ok; +} + +static bool amduatd_concepts_append_edge_record(amduatd_concepts_t *c, + amduat_asl_store_t *store, + amduat_reference_t src, + amduat_reference_t dst, + const char *rel, + amduat_octets_t actor, + amduat_reference_t *out_ref) { + amduat_octets_t payload = amduat_octets(NULL, 0u); + amduat_reference_t record_ref; + amduatd_edge_entry_t entry; + uint64_t offset = 0u; + + if (out_ref != NULL) { + *out_ref = amduat_reference(0u, amduat_octets(NULL, 0u)); + } + if (c == NULL || store == NULL || rel == NULL) { + return false; + } + if (!amduatd_edge_payload_encode(src, dst, rel, &payload)) { + return false; + } + if (amduat_asl_record_store_put(store, + amduat_octets(AMDUATD_EDGE_SCHEMA, + strlen(AMDUATD_EDGE_SCHEMA)), + payload, + &record_ref) != AMDUAT_ASL_STORE_OK) { + free((void *)payload.data); + return false; + } + free((void *)payload.data); + if (amduat_asl_collection_append(&c->edge_collection, + AMDUATD_EDGE_COLLECTION, + record_ref, + AMDUATD_EDGE_COLLECTION_KIND, + actor, + &offset) != AMDUAT_ASL_COLLECTION_OK) { + amduat_reference_free(&record_ref); + return false; + } + + memset(&entry, 0, sizeof(entry)); + entry.record_ref = record_ref; + entry.src_ref = src; + entry.dst_ref = dst; + entry.rel = (char *)rel; + if (!amduatd_edge_list_push(&c->edges, &entry)) { + amduat_reference_free(&record_ref); + return false; + } + + if (offset != 0u && (offset % 256u) == 0u) { + amduat_reference_t snapshot_ref; + bool swapped = false; + memset(&snapshot_ref, 0, sizeof(snapshot_ref)); + (void)amduat_asl_collection_snapshot(&c->edge_collection, + AMDUATD_EDGE_COLLECTION, + offset, + &snapshot_ref, + &swapped); + amduat_reference_free(&snapshot_ref); + } + + if (out_ref != NULL) { + *out_ref = record_ref; + } else { + amduat_reference_free(&record_ref); + } + return true; +} + +static bool amduatd_concepts_migrate_edges(amduatd_concepts_t *c, + amduat_asl_store_t *store) { + uint8_t *file_bytes = NULL; + size_t file_len = 0; + bool ok = true; + + if (c == NULL || store == NULL) { + return false; + } + if (!amduatd_read_file(c->edges_path, &file_bytes, &file_len)) { + return true; + } + + { + char *text = (char *)file_bytes; + char *line = text; + char *end = text + file_len; + while (line < end) { + char *nl = memchr(line, '\n', (size_t)(end - line)); + size_t n = nl != NULL ? (size_t)(nl - line) : (size_t)(end - line); + char *tmp; + amduat_reference_t ref; + bool ok_ref; + + while (n != 0 && (line[n - 1] == '\r' || line[n - 1] == ' ' || + line[n - 1] == '\t')) { + n--; + } + while (n != 0 && (*line == ' ' || *line == '\t')) { + line++; + n--; + } + if (n != 0) { + tmp = (char *)malloc(n + 1u); + if (tmp == NULL) { + ok = false; + break; + } + memcpy(tmp, line, n); + tmp[n] = '\0'; + memset(&ref, 0, sizeof(ref)); + ok_ref = amduat_asl_ref_decode_hex(tmp, &ref); + free(tmp); + if (ok_ref) { + amduat_artifact_t artifact; + amduat_tgk_edge_body_t edge; + const char *rel_name = NULL; + memset(&artifact, 0, sizeof(artifact)); + if (amduat_asl_store_get(store, ref, &artifact) == + AMDUAT_ASL_STORE_OK && + artifact.has_type_tag && + artifact.type_tag.tag_id == AMDUAT_TYPE_TAG_TGK1_EDGE_V1) { + memset(&edge, 0, sizeof(edge)); + if (amduat_enc_tgk1_edge_decode_v1(artifact.bytes, &edge)) { + if (edge.from_len == 1 && edge.to_len == 1) { + rel_name = amduatd_relation_name_for_ref(c, edge.payload); + } + if (rel_name != NULL) { + if (!amduatd_concepts_append_edge_record(c, + store, + edge.from[0], + edge.to[0], + rel_name, + amduat_octets(NULL, 0u), + NULL)) { + ok = false; + } + } + amduat_enc_tgk1_edge_free(&edge); + } + amduat_asl_artifact_free(&artifact); + } else if (artifact.bytes.data != NULL) { + amduat_asl_artifact_free(&artifact); + } + amduat_reference_free(&ref); + if (!ok) { + break; + } + } + } + if (nl == NULL) { + break; + } + line = nl + 1; + } + } + + free(file_bytes); + + if (ok) { + char migrated_path[1100]; + (void)snprintf(migrated_path, + sizeof(migrated_path), + "%s.migrated", + c->edges_path); + (void)rename(c->edges_path, migrated_path); + } + + return ok; +} + static bool amduatd_concepts_init(amduatd_concepts_t *c, amduat_asl_store_t *store, const amduat_asl_store_fs_config_t *cfg, const char *root_path) { - uint8_t *file_bytes = NULL; - size_t file_len = 0; - size_t i; - if (c == NULL || store == NULL || cfg == NULL || root_path == NULL) { return false; } @@ -868,6 +1800,10 @@ static bool amduatd_concepts_init(amduatd_concepts_t *c, c->root_path = root_path; (void)snprintf(c->edges_path, sizeof(c->edges_path), "%s/%s", root_path, AMDUATD_EDGES_FILE); + if (!amduat_asl_collection_store_init(&c->edge_collection, root_path, + store)) { + return false; + } if (!amduatd_concepts_seed_relation(store, cfg, "aliases", &c->rel_aliases_ref)) { @@ -925,93 +1861,21 @@ static bool amduatd_concepts_init(amduatd_concepts_t *c, c->rel_has_provenance_ref)) { return false; } - - if (!amduatd_read_file(c->edges_path, &file_bytes, &file_len)) { - return true; + if (!amduatd_concepts_load_edges(c, store)) { + return false; } - - { - char *text = (char *)file_bytes; - char *line = text; - char *end = text + file_len; - while (line < end) { - char *nl = memchr(line, '\n', (size_t)(end - line)); - size_t n = nl != NULL ? (size_t)(nl - line) : (size_t)(end - line); - char *tmp; - amduat_reference_t ref; - bool ok; - - while (n != 0 && (line[n - 1] == '\r' || line[n - 1] == ' ' || - line[n - 1] == '\t')) { - n--; - } - while (n != 0 && (*line == ' ' || *line == '\t')) { - line++; - n--; - } - if (n != 0) { - tmp = (char *)malloc(n + 1u); - if (tmp == NULL) { - free(file_bytes); - return false; - } - memcpy(tmp, line, n); - tmp[n] = '\0'; - memset(&ref, 0, sizeof(ref)); - ok = amduat_asl_ref_decode_hex(tmp, &ref); - free(tmp); - if (ok) { - (void)amduatd_ref_list_push(&c->edge_refs, ref); - amduat_reference_free(&ref); - } - } - if (nl == NULL) { - break; - } - line = nl + 1; + if (c->edges.len == 0u) { + if (!amduatd_concepts_migrate_edges(c, store)) { + return false; } - } - - free(file_bytes); - - for (i = 0; i < c->edge_refs.len; ++i) { - if (c->edge_refs.refs[i].hash_id == 0 || - c->edge_refs.refs[i].digest.data == NULL || - c->edge_refs.refs[i].digest.len == 0) { - continue; + if (!amduatd_concepts_load_edges(c, store)) { + return false; } } return true; } -static bool amduatd_concepts_append_edge_ref(amduatd_concepts_t *c, - amduat_reference_t edge_ref) { - char *hex = NULL; - FILE *f; - bool ok; - - if (c == NULL) { - return false; - } - if (!amduat_asl_ref_encode_hex(edge_ref, &hex)) { - return false; - } - f = fopen(c->edges_path, "ab"); - if (f == NULL) { - free(hex); - return false; - } - ok = fprintf(f, "%s\n", hex) > 0; - fclose(f); - if (!ok) { - free(hex); - return false; - } - free(hex); - return amduatd_ref_list_push(&c->edge_refs, edge_ref); -} - static bool amduatd_concepts_derive_name_ref( const amduat_asl_store_fs_config_t *cfg, const char *name, @@ -1117,12 +1981,9 @@ static bool amduatd_concepts_put_edge(amduat_asl_store_t *store, amduat_reference_t from, amduat_reference_t to, amduat_reference_t relation_concept_ref, + amduat_octets_t actor, amduat_reference_t *out_edge_ref) { - amduat_tgk_edge_body_t edge; - amduat_reference_t from_arr[1]; - amduat_reference_t to_arr[1]; - amduat_octets_t bytes; - amduat_artifact_t artifact; + const char *rel_name = NULL; if (out_edge_ref != NULL) { *out_edge_ref = amduat_reference(0u, amduat_octets(NULL, 0u)); @@ -1130,29 +1991,17 @@ static bool amduatd_concepts_put_edge(amduat_asl_store_t *store, if (store == NULL || c == NULL || out_edge_ref == NULL) { return false; } - - memset(&edge, 0, sizeof(edge)); - edge.type = AMDUATD_TGK_EDGE_TYPE; - from_arr[0] = from; - to_arr[0] = to; - edge.from = from_arr; - edge.from_len = 1; - edge.to = to_arr; - edge.to_len = 1; - edge.payload = relation_concept_ref; - - bytes = amduat_octets(NULL, 0); - if (!amduat_enc_tgk1_edge_encode_v1(&edge, &bytes)) { + rel_name = amduatd_relation_name_for_ref(c, relation_concept_ref); + if (rel_name == NULL) { return false; } - artifact = amduat_artifact_with_type(bytes, - amduat_type_tag(AMDUAT_TYPE_TAG_TGK1_EDGE_V1)); - if (amduat_asl_store_put(store, artifact, out_edge_ref) != AMDUAT_ASL_STORE_OK) { - free((void *)bytes.data); - return false; - } - free((void *)bytes.data); - return amduatd_concepts_append_edge_ref(c, *out_edge_ref); + return amduatd_concepts_append_edge_record(c, + store, + from, + to, + rel_name, + actor, + out_edge_ref); } static bool amduatd_concepts_lookup_alias(amduat_asl_store_t *store, @@ -1174,37 +2023,19 @@ static bool amduatd_concepts_lookup_alias(amduat_asl_store_t *store, return false; } - for (i = c->edge_refs.len; i > 0; --i) { - amduat_artifact_t artifact; - amduat_tgk_edge_body_t edge; - amduat_asl_store_error_t err; - - memset(&artifact, 0, sizeof(artifact)); - err = amduat_asl_store_get(store, c->edge_refs.refs[i - 1u], &artifact); - if (err != AMDUAT_ASL_STORE_OK) { + for (i = c->edges.len; i > 0; --i) { + const amduatd_edge_entry_t *entry = &c->edges.items[i - 1u]; + if (entry->rel == NULL) { continue; } - if (!artifact.has_type_tag || - artifact.type_tag.tag_id != AMDUAT_TYPE_TAG_TGK1_EDGE_V1) { - amduat_asl_artifact_free(&artifact); + if (strcmp(entry->rel, AMDUATD_REL_ALIAS) != 0) { continue; } - memset(&edge, 0, sizeof(edge)); - if (!amduat_enc_tgk1_edge_decode_v1(artifact.bytes, &edge)) { - amduat_asl_artifact_free(&artifact); - continue; - } - if (edge.from_len == 1 && edge.to_len == 1 && - amduat_reference_eq(edge.payload, c->rel_aliases_ref) && - amduat_reference_eq(edge.from[0], name_ref)) { - amduat_reference_clone(edge.to[0], out_concept_ref); - amduat_enc_tgk1_edge_free(&edge); - amduat_asl_artifact_free(&artifact); + if (amduat_reference_eq(entry->src_ref, name_ref)) { + amduat_reference_clone(entry->dst_ref, out_concept_ref); amduat_reference_free(&name_ref); return true; } - amduat_enc_tgk1_edge_free(&edge); - amduat_asl_artifact_free(&artifact); } amduat_reference_free(&name_ref); @@ -1224,36 +2055,18 @@ static bool amduatd_concepts_resolve_latest(amduat_asl_store_t *store, return false; } - for (i = c->edge_refs.len; i > 0; --i) { - amduat_artifact_t artifact; - amduat_tgk_edge_body_t edge; - amduat_asl_store_error_t err; - - memset(&artifact, 0, sizeof(artifact)); - err = amduat_asl_store_get(store, c->edge_refs.refs[i - 1u], &artifact); - if (err != AMDUAT_ASL_STORE_OK) { + for (i = c->edges.len; i > 0; --i) { + const amduatd_edge_entry_t *entry = &c->edges.items[i - 1u]; + if (entry->rel == NULL) { continue; } - if (!artifact.has_type_tag || - artifact.type_tag.tag_id != AMDUAT_TYPE_TAG_TGK1_EDGE_V1) { - amduat_asl_artifact_free(&artifact); + if (strcmp(entry->rel, AMDUATD_REL_MATERIALIZES) != 0) { continue; } - memset(&edge, 0, sizeof(edge)); - if (!amduat_enc_tgk1_edge_decode_v1(artifact.bytes, &edge)) { - amduat_asl_artifact_free(&artifact); - continue; - } - if (edge.from_len == 1 && edge.to_len == 1 && - amduat_reference_eq(edge.payload, c->rel_materializes_ref) && - amduat_reference_eq(edge.from[0], concept_ref)) { - amduat_reference_clone(edge.to[0], out_ref); - amduat_enc_tgk1_edge_free(&edge); - amduat_asl_artifact_free(&artifact); + if (amduat_reference_eq(entry->src_ref, concept_ref)) { + amduat_reference_clone(entry->dst_ref, out_ref); return true; } - amduat_enc_tgk1_edge_free(&edge); - amduat_asl_artifact_free(&artifact); } return false; @@ -1318,49 +2131,28 @@ static bool amduatd_handle_get_concepts(int fd, return amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); } - for (i = 0; i < concepts->edge_refs.len; ++i) { - amduat_reference_t edge_ref = concepts->edge_refs.refs[i]; + for (i = 0; i < concepts->edges.len; ++i) { + const amduatd_edge_entry_t *entry = &concepts->edges.items[i]; amduat_artifact_t artifact; - amduat_tgk_edge_body_t edge; amduat_asl_store_error_t err; char name[128]; char *concept_hex = NULL; - memset(&artifact, 0, sizeof(artifact)); - err = amduat_asl_store_get(store, edge_ref, &artifact); - if (err != AMDUAT_ASL_STORE_OK) { + if (entry->rel == NULL || + strcmp(entry->rel, AMDUATD_REL_ALIAS) != 0) { continue; } - if (!artifact.has_type_tag || - artifact.type_tag.tag_id != AMDUAT_TYPE_TAG_TGK1_EDGE_V1) { - amduat_asl_artifact_free(&artifact); - continue; - } - memset(&edge, 0, sizeof(edge)); - if (!amduat_enc_tgk1_edge_decode_v1(artifact.bytes, &edge)) { - amduat_asl_artifact_free(&artifact); - continue; - } - if (!(edge.from_len == 1 && edge.to_len == 1 && - amduat_reference_eq(edge.payload, concepts->rel_aliases_ref))) { - amduat_enc_tgk1_edge_free(&edge); - amduat_asl_artifact_free(&artifact); - continue; - } - amduat_asl_artifact_free(&artifact); memset(&artifact, 0, sizeof(artifact)); - err = amduat_asl_store_get(store, edge.from[0], &artifact); + err = amduat_asl_store_get(store, entry->src_ref, &artifact); if (err != AMDUAT_ASL_STORE_OK || !amduatd_parse_name_artifact(artifact, name, sizeof(name))) { - amduat_enc_tgk1_edge_free(&edge); amduat_asl_artifact_free(&artifact); continue; } amduat_asl_artifact_free(&artifact); - if (!amduat_asl_ref_encode_hex(edge.to[0], &concept_hex)) { - amduat_enc_tgk1_edge_free(&edge); + if (!amduat_asl_ref_encode_hex(entry->dst_ref, &concept_hex)) { continue; } @@ -1376,7 +2168,6 @@ static bool amduatd_handle_get_concepts(int fd, (void)amduatd_strbuf_append_cstr(&b, "\"}"); free(concept_hex); - amduat_enc_tgk1_edge_free(&edge); } if (!amduatd_strbuf_append_cstr(&b, "]}\n")) { @@ -1524,43 +2315,23 @@ static bool amduatd_handle_get_concept(int fd, } (void)amduatd_strbuf_append_cstr(&b, ",\"versions\":["); - for (i = 0; i < concepts->edge_refs.len; ++i) { - amduat_reference_t edge_ref = concepts->edge_refs.refs[i]; - amduat_artifact_t artifact; - amduat_tgk_edge_body_t edge; - amduat_asl_store_error_t err; + for (i = 0; i < concepts->edges.len; ++i) { + const amduatd_edge_entry_t *entry = &concepts->edges.items[i]; char *edge_hex = NULL; char *ref_hex = NULL; - memset(&artifact, 0, sizeof(artifact)); - err = amduat_asl_store_get(store, edge_ref, &artifact); - if (err != AMDUAT_ASL_STORE_OK) { + if (entry->rel == NULL || + strcmp(entry->rel, AMDUATD_REL_MATERIALIZES) != 0) { continue; } - if (!artifact.has_type_tag || - artifact.type_tag.tag_id != AMDUAT_TYPE_TAG_TGK1_EDGE_V1) { - amduat_asl_artifact_free(&artifact); - continue; - } - memset(&edge, 0, sizeof(edge)); - if (!amduat_enc_tgk1_edge_decode_v1(artifact.bytes, &edge)) { - amduat_asl_artifact_free(&artifact); - continue; - } - amduat_asl_artifact_free(&artifact); - - if (!(edge.from_len == 1 && edge.to_len == 1 && - amduat_reference_eq(edge.payload, concepts->rel_materializes_ref) && - amduat_reference_eq(edge.from[0], concept_ref))) { - amduat_enc_tgk1_edge_free(&edge); + if (!amduat_reference_eq(entry->src_ref, concept_ref)) { continue; } - if (!amduat_asl_ref_encode_hex(edge_ref, &edge_hex) || - !amduat_asl_ref_encode_hex(edge.to[0], &ref_hex)) { + if (!amduat_asl_ref_encode_hex(entry->record_ref, &edge_hex) || + !amduat_asl_ref_encode_hex(entry->dst_ref, &ref_hex)) { free(edge_hex); free(ref_hex); - amduat_enc_tgk1_edge_free(&edge); continue; } @@ -1577,7 +2348,6 @@ static bool amduatd_handle_get_concept(int fd, free(edge_hex); free(ref_hex); - amduat_enc_tgk1_edge_free(&edge); if (version_count >= 64u) { break; @@ -1655,12 +2425,136 @@ typedef struct { char accept[128]; char x_type_tag[64]; size_t content_length; + bool has_actor; + amduat_octets_t actor; + bool has_uid; + uid_t uid; } amduatd_http_req_t; static void amduatd_http_req_init(amduatd_http_req_t *req) { memset(req, 0, sizeof(*req)); } +typedef struct { + uid_t *uids; + size_t len; + size_t cap; +} amduatd_allowlist_t; + +static void amduatd_allowlist_free(amduatd_allowlist_t *list) { + if (list == NULL) { + return; + } + free(list->uids); + list->uids = NULL; + list->len = 0; + list->cap = 0; +} + +static bool amduatd_allowlist_add(amduatd_allowlist_t *list, uid_t uid) { + if (list == NULL) { + return false; + } + for (size_t i = 0u; i < list->len; ++i) { + if (list->uids[i] == uid) { + return true; + } + } + if (list->len == list->cap) { + size_t next_cap = list->cap != 0 ? list->cap * 2u : 8u; + uid_t *next = (uid_t *)realloc(list->uids, next_cap * sizeof(*next)); + if (next == NULL) { + return false; + } + list->uids = next; + list->cap = next_cap; + } + list->uids[list->len++] = uid; + return true; +} + +static bool amduatd_actor_allowed(const amduatd_allowlist_t *list, + bool has_uid, + uid_t uid) { + if (list == NULL || list->len == 0u) { + return true; + } + if (!has_uid) { + return false; + } + for (size_t i = 0u; i < list->len; ++i) { + if (list->uids[i] == uid) { + return true; + } + } + return false; +} + +static bool amduatd_get_peer_actor(int client_fd, + amduat_octets_t *out_actor, + bool *out_has_actor, + bool *out_has_uid, + uid_t *out_uid) { + char *actor = NULL; + if (out_actor == NULL || out_has_actor == NULL || + out_has_uid == NULL || out_uid == NULL) { + return false; + } + *out_actor = amduat_octets(NULL, 0u); + *out_has_actor = false; + *out_has_uid = false; + *out_uid = 0; + +#ifdef SO_PEERCRED + { + struct ucred cred; + socklen_t len = (socklen_t)sizeof(cred); + if (getsockopt(client_fd, + SOL_SOCKET, + SO_PEERCRED, + &cred, + &len) == 0 && + len == sizeof(cred)) { + int needed = snprintf(NULL, 0, + "uid:%u gid:%u pid:%u", + (unsigned int)cred.uid, + (unsigned int)cred.gid, + (unsigned int)cred.pid); + if (needed < 0) { + return false; + } + actor = (char *)malloc((size_t)needed + 1u); + if (actor == NULL) { + return false; + } + snprintf(actor, + (size_t)needed + 1u, + "uid:%u gid:%u pid:%u", + (unsigned int)cred.uid, + (unsigned int)cred.gid, + (unsigned int)cred.pid); + *out_actor = amduat_octets((uint8_t *)actor, (size_t)needed); + *out_has_actor = true; + *out_has_uid = true; + *out_uid = cred.uid; + amduat_log(AMDUAT_LOG_DEBUG, "request actor=%s", actor); + return true; + } + } +#endif + + actor = strdup("unknown"); + if (actor == NULL) { + return false; + } + *out_actor = amduat_octets((uint8_t *)actor, strlen(actor)); + *out_has_actor = true; + *out_has_uid = false; + *out_uid = 0; + amduat_log(AMDUAT_LOG_DEBUG, "request actor=unknown"); + return true; +} + static bool amduatd_http_parse_request(int fd, amduatd_http_req_t *out_req) { char line[2048]; size_t line_len = 0; @@ -2785,6 +3679,7 @@ static bool amduatd_seed_concept_if_missing( name_ref, concept_ref, concepts->rel_aliases_ref, + amduat_octets(NULL, 0u), &edge_ref)) { amduat_reference_free(&name_ref); amduat_reference_free(&concept_ref); @@ -2884,6 +3779,7 @@ static bool amduatd_seed_materializes_if_missing( concept_ref, target_ref, concepts->rel_materializes_ref, + amduat_octets(NULL, 0u), &edge_ref)) { return false; } @@ -3221,6 +4117,7 @@ static bool amduatd_seed_ms_ui_state(amduat_asl_store_t *store, schema_output_ref, receipt_ref, concepts->rel_has_provenance_ref, + amduat_octets(NULL, 0u), &edge_ref)) { goto seed_cleanup; } @@ -3284,6 +4181,7 @@ static bool amduatd_seed_ms_ui_state(amduat_asl_store_t *store, registry_output_ref, receipt_ref, concepts->rel_has_provenance_ref, + amduat_octets(NULL, 0u), &edge_ref)) { goto seed_cleanup; } @@ -5887,6 +6785,7 @@ static bool amduatd_handle_post_concepts(int fd, char *concept_hex = NULL; char json[4096]; bool ok = false; + amduat_octets_t actor = amduat_octets(NULL, 0u); memset(&target_ref, 0, sizeof(target_ref)); memset(&name_ref, 0, sizeof(name_ref)); @@ -5897,6 +6796,9 @@ static bool amduatd_handle_post_concepts(int fd, return amduatd_send_json_error(fd, 500, "Internal Server Error", "internal error"); } + if (req->has_actor) { + actor = req->actor; + } if (req->content_length == 0) { return amduatd_send_json_error(fd, 400, "Bad Request", "missing body"); } @@ -6007,6 +6909,7 @@ static bool amduatd_handle_post_concepts(int fd, name_ref, concept_ref, concepts->rel_aliases_ref, + actor, &edge_ref)) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "store error"); @@ -6019,6 +6922,7 @@ static bool amduatd_handle_post_concepts(int fd, concept_ref, target_ref, concepts->rel_materializes_ref, + actor, &edge_ref)) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "store error"); @@ -6648,6 +7552,7 @@ static bool amduatd_handle_post_concept_publish(int fd, amduat_reference_t edge_ref; bool have_ref = false; bool ok = false; + amduat_octets_t actor = amduat_octets(NULL, 0u); memset(&target_ref, 0, sizeof(target_ref)); memset(&concept_ref, 0, sizeof(concept_ref)); @@ -6658,6 +7563,9 @@ static bool amduatd_handle_post_concept_publish(int fd, return amduatd_send_json_error(fd, 500, "Internal Server Error", "internal error"); } + if (req->has_actor) { + actor = req->actor; + } if (!amduatd_name_valid(name)) { return amduatd_send_json_error(fd, 400, "Bad Request", "invalid name"); } @@ -6742,6 +7650,7 @@ static bool amduatd_handle_post_concept_publish(int fd, concept_ref, target_ref, concepts->rel_materializes_ref, + actor, &edge_ref)) { ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "store error"); @@ -6809,71 +7718,111 @@ static bool amduatd_handle_conn(int fd, amduat_reference_t api_contract_ref, amduat_reference_t ui_ref, amduatd_concepts_t *concepts, - const amduat_fed_coord_t *coord) { + const amduat_fed_coord_t *coord, + const amduatd_allowlist_t *allowlist) { amduatd_http_req_t req; char no_query[1024]; + bool ok = false; + amduat_octets_t actor = amduat_octets(NULL, 0u); + bool has_actor = false; + bool has_uid = false; + uid_t uid = 0; if (!amduatd_http_parse_request(fd, &req)) { return false; } + if (!amduatd_get_peer_actor(fd, &actor, &has_actor, &has_uid, &uid)) { + actor = amduat_octets(NULL, 0u); + has_actor = false; + has_uid = false; + } + req.has_actor = has_actor; + req.actor = actor; + req.has_uid = has_uid; + req.uid = uid; + + if (!amduatd_actor_allowed(allowlist, has_uid, uid)) { + amduat_log(AMDUAT_LOG_DEBUG, + "reject uid=%u (not in allowlist)", + (unsigned int)uid); + ok = amduatd_http_send_text(fd, 403, "Forbidden", "forbidden\n", false); + goto conn_cleanup; + } + amduatd_path_without_query(req.path, no_query, sizeof(no_query)); if (strcmp(req.method, "GET") == 0 && strcmp(no_query, "/v1/ui") == 0) { - return amduatd_handle_get_ui(fd, store, ui_ref); + ok = amduatd_handle_get_ui(fd, store, ui_ref); + goto conn_cleanup; } if (strcmp(req.method, "GET") == 0 && strcmp(no_query, "/v1/meta") == 0) { - return amduatd_handle_meta(fd, cfg, api_contract_ref, false); + ok = amduatd_handle_meta(fd, cfg, api_contract_ref, false); + goto conn_cleanup; } if (strcmp(req.method, "HEAD") == 0 && strcmp(no_query, "/v1/meta") == 0) { - return amduatd_handle_meta(fd, cfg, api_contract_ref, true); + ok = amduatd_handle_meta(fd, cfg, api_contract_ref, true); + goto conn_cleanup; } if (strcmp(req.method, "GET") == 0 && strcmp(no_query, "/v1/contract") == 0) { - return amduatd_handle_get_contract(fd, store, &req, api_contract_ref); + ok = amduatd_handle_get_contract(fd, store, &req, api_contract_ref); + goto conn_cleanup; } if (strcmp(req.method, "GET") == 0 && strcmp(no_query, "/v1/fed/records") == 0) { - return amduatd_handle_get_fed_records(fd, store, &req); + ok = amduatd_handle_get_fed_records(fd, store, &req); + goto conn_cleanup; } if (strcmp(req.method, "GET") == 0 && strcmp(no_query, "/v1/fed/status") == 0) { - return amduatd_handle_get_fed_status(fd, coord, &req); + ok = amduatd_handle_get_fed_status(fd, coord, &req); + goto conn_cleanup; } if (strcmp(req.method, "POST") == 0 && strcmp(no_query, "/v1/artifacts") == 0) { - return amduatd_handle_post_artifacts(fd, store, &req); + ok = amduatd_handle_post_artifacts(fd, store, &req); + goto conn_cleanup; } if (strcmp(req.method, "POST") == 0 && strcmp(no_query, "/v1/pel/run") == 0) { - return amduatd_handle_post_pel_run(fd, store, cfg, concepts, &req); + ok = amduatd_handle_post_pel_run(fd, store, cfg, concepts, &req); + goto conn_cleanup; } if (strcmp(req.method, "POST") == 0 && strcmp(no_query, "/v1/pel/programs") == 0) { - return amduatd_handle_post_pel_programs(fd, store, &req); + ok = amduatd_handle_post_pel_programs(fd, store, &req); + goto conn_cleanup; } if (strcmp(req.method, "POST") == 0 && strcmp(no_query, "/v1/context_frames") == 0) { - return amduatd_handle_post_context_frames(fd, store, cfg, concepts, &req); + ok = amduatd_handle_post_context_frames(fd, store, cfg, concepts, &req); + goto conn_cleanup; } if (strcmp(req.method, "POST") == 0 && strcmp(no_query, "/v1/concepts") == 0) { - return amduatd_handle_post_concepts(fd, store, cfg, concepts, &req); + ok = amduatd_handle_post_concepts(fd, store, cfg, concepts, &req); + goto conn_cleanup; } if (strcmp(req.method, "GET") == 0 && strcmp(no_query, "/v1/concepts") == 0) { - return amduatd_handle_get_concepts(fd, store, concepts); + ok = amduatd_handle_get_concepts(fd, store, concepts); + goto conn_cleanup; } if (strcmp(req.method, "GET") == 0 && strcmp(no_query, "/v1/relations") == 0) { - return amduatd_handle_get_relations(fd, concepts); + ok = amduatd_handle_get_relations(fd, concepts); + goto conn_cleanup; } if (strcmp(req.method, "GET") == 0 && strncmp(no_query, "/v1/fed/artifacts/", 18) == 0) { - return amduatd_handle_get_fed_artifact(fd, store, &req, req.path); + ok = amduatd_handle_get_fed_artifact(fd, store, &req, req.path); + goto conn_cleanup; } if (strcmp(req.method, "GET") == 0 && strncmp(no_query, "/v1/concepts/", 13) == 0) { const char *name = no_query + 13; if (name[0] == '\0') { - return amduatd_send_json_error(fd, 400, "Bad Request", "missing name"); + ok = amduatd_send_json_error(fd, 400, "Bad Request", "missing name"); + goto conn_cleanup; } - return amduatd_handle_get_concept(fd, store, cfg, concepts, name); + ok = amduatd_handle_get_concept(fd, store, cfg, concepts, name); + goto conn_cleanup; } if (strcmp(req.method, "POST") == 0 && strncmp(no_query, "/v1/concepts/", 13) == 0 && @@ -6886,35 +7835,46 @@ static bool amduatd_handle_conn(int fd, } slash = strstr(name, "/publish"); if (slash == NULL || strcmp(slash, "/publish") != 0) { - return amduatd_send_json_error(fd, 400, "Bad Request", "invalid path"); + ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid path"); + goto conn_cleanup; } *slash = '\0'; - return amduatd_handle_post_concept_publish(fd, store, cfg, concepts, name, - &req); + ok = amduatd_handle_post_concept_publish(fd, store, cfg, concepts, name, + &req); + goto conn_cleanup; } if (strcmp(req.method, "GET") == 0 && strncmp(no_query, "/v1/resolve/", 12) == 0) { const char *name = no_query + 12; if (name[0] == '\0') { - return amduatd_send_json_error(fd, 400, "Bad Request", "missing name"); + ok = amduatd_send_json_error(fd, 400, "Bad Request", "missing name"); + goto conn_cleanup; } - return amduatd_handle_get_resolve(fd, store, cfg, concepts, name); + ok = amduatd_handle_get_resolve(fd, store, cfg, concepts, name); + goto conn_cleanup; } if (strcmp(req.method, "GET") == 0 && strncmp(no_query, "/v1/artifacts/", 14) == 0) { - return amduatd_handle_get_artifact(fd, store, &req, req.path, false); + ok = amduatd_handle_get_artifact(fd, store, &req, req.path, false); + goto conn_cleanup; } if (strcmp(req.method, "HEAD") == 0 && strncmp(no_query, "/v1/artifacts/", 14) == 0) { - return amduatd_handle_head_artifact(fd, store, &req, req.path); + ok = amduatd_handle_head_artifact(fd, store, &req, req.path); + goto conn_cleanup; } - return amduatd_http_send_not_found(fd, &req); + ok = amduatd_http_send_not_found(fd, &req); + +conn_cleanup: + free((void *)req.actor.data); + return ok; } static void amduatd_print_usage(FILE *stream) { fprintf(stream, "usage:\n" " amduatd [--root PATH] [--sock PATH]\n" + " [--allow-uid UID] [--allow-user NAME]\n" "\n" "defaults:\n" " --root %s\n" @@ -6935,6 +7895,7 @@ int main(int argc, char **argv) { amduat_fed_transport_stub_t fed_stub; amduat_fed_coord_cfg_t fed_cfg; amduat_fed_coord_t *fed_coord = NULL; + amduatd_allowlist_t allowlist; int i; int sfd = -1; uint64_t last_tick_ms = 0; @@ -6942,6 +7903,7 @@ int main(int argc, char **argv) { memset(&api_contract_ref, 0, sizeof(api_contract_ref)); memset(&ui_ref, 0, sizeof(ui_ref)); memset(&concepts, 0, sizeof(concepts)); + memset(&allowlist, 0, sizeof(allowlist)); for (i = 1; i < argc; ++i) { if (strcmp(argv[i], "--root") == 0) { @@ -6956,6 +7918,37 @@ int main(int argc, char **argv) { return 2; } sock_path = argv[++i]; + } else if (strcmp(argv[i], "--allow-uid") == 0) { + char *endp = NULL; + unsigned long uid_val; + if (i + 1 >= argc) { + fprintf(stderr, "error: --allow-uid requires a value\n"); + return 2; + } + uid_val = strtoul(argv[++i], &endp, 10); + if (endp == argv[i] || *endp != '\0' || uid_val > UINT32_MAX) { + fprintf(stderr, "error: invalid --allow-uid\n"); + return 2; + } + if (!amduatd_allowlist_add(&allowlist, (uid_t)uid_val)) { + fprintf(stderr, "error: failed to add allow-uid\n"); + return 2; + } + } else if (strcmp(argv[i], "--allow-user") == 0) { + struct passwd *pwd; + if (i + 1 >= argc) { + fprintf(stderr, "error: --allow-user requires a value\n"); + return 2; + } + pwd = getpwnam(argv[++i]); + if (pwd == NULL) { + fprintf(stderr, "error: unknown user for --allow-user\n"); + return 2; + } + if (!amduatd_allowlist_add(&allowlist, pwd->pw_uid)) { + fprintf(stderr, "error: failed to add allow-user\n"); + return 2; + } } else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { amduatd_print_usage(stdout); return 0; @@ -7104,7 +8097,8 @@ int main(int argc, char **argv) { api_contract_ref, ui_ref, &concepts, - fed_coord); + fed_coord, + &allowlist); (void)close(cfd); } } @@ -7115,6 +8109,7 @@ int main(int argc, char **argv) { if (fed_coord != NULL) { amduat_fed_coord_close(fed_coord); } + amduatd_allowlist_free(&allowlist); (void)unlink(sock_path); (void)close(sfd); return 0;