kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit c2795974bd602276efb43a46f376c2028ad55366
parent 5ae5d511771ef843bee75c53b3d6bbf27c94d493
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Wed, 17 Jun 2026 21:36:15 -0700

cas: expose by-id blob reads

Diffstat:
Minclude/kit/cas.h | 3++-
Mmk/test.mk | 7++++++-
Mmk/test_unit.mk | 3++-
Msrc/api/cas.c | 32++++++++++++++++++++++++++++++++
Atest/api/cas_test.c | 184+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 226 insertions(+), 3 deletions(-)

diff --git a/include/kit/cas.h b/include/kit/cas.h @@ -76,7 +76,8 @@ KIT_API KitStatus kit_cas_add_blob(KitCas* cas, const uint8_t* data, size_t len, KitBlobInfo* out); /* Report whether a blob is present locally (KIT_OK present, KIT_NOT_FOUND - * absent). A light existence probe — it does not read or re-verify the bytes. + * absent). The current host file boundary has no stat hook, so this verifies + * the stored bytes and releases them immediately. */ KIT_API KitStatus kit_cas_has_blob(KitCas* cas, const uint8_t id[KIT_CAS_HASH_LEN]); diff --git a/mk/test.mk b/mk/test.mk @@ -83,6 +83,7 @@ TEST_TARGETS = \ test-gram \ test-driver-gram \ test-hash \ + test-cas-api \ test-release-index \ test-driver-strip \ test-dwarf \ @@ -248,7 +249,7 @@ test-t1-driver: @$(T1_LOG) driver env KIT=$(abspath $(BIN)) sh test/tier1/system/driver-kitchen.sh test-t1-core-api: - @$(T1_LOG) core-api $(MAKE) --no-print-directory test-hash test-release-index test-abi-classify + @$(T1_LOG) core-api $(MAKE) --no-print-directory test-hash test-cas-api test-release-index test-abi-classify test-t1-pp: bin @$(T1_LOG) pp env KIT=$(abspath $(BIN)) test/tier1/frontend/run-kitchen.sh pp @@ -664,6 +665,7 @@ CG_CONST_TEST_BIN = build/test/cg_const_test STRENGTH_REDUCE_TEST_BIN = build/test/strength_reduce_test TARGET_TEST_BIN = build/test/target_test HASH_TEST_BIN = build/test/hash_test +CAS_TEST_BIN = build/test/cas_test RELEASE_INDEX_TEST_BIN = build/test/release_index_test PANIC_RECOVERY_TEST_BIN = build/test/panic_recovery_test ABI_CLASSIFY_TEST_BIN = build/test/abi_classify_test @@ -687,6 +689,9 @@ test-cg-api: $(TARGET_TEST_BIN) $(CG_API_TEST_BIN) $(CG_SWITCH_TEST_BIN) \ test-hash: $(HASH_TEST_BIN) $(HASH_TEST_BIN) +test-cas-api: $(CAS_TEST_BIN) + $(CAS_TEST_BIN) + test-release-index: $(RELEASE_INDEX_TEST_BIN) $(RELEASE_INDEX_TEST_BIN) diff --git a/mk/test_unit.mk b/mk/test_unit.mk @@ -32,7 +32,7 @@ UNIT_CFLAGS_INTERNAL = $(HOST_CFLAGS) -Iinclude -Isrc -Itest UNIT_TESTS_PUBLIC := \ ar_test target_test arm32_target_features_test cg_api_test cg_switch_test \ cg_fp_cmp_test \ - cg_control_test cg_const_test hash_test release_index_test \ + cg_control_test cg_const_test hash_test cas_test release_index_test \ panic_recovery_test profile_test \ link_script_test \ rv64_jit_test rv32_jit_test aa64_inline_test rv64_inline_test x64_inline_test \ @@ -42,6 +42,7 @@ ar_test_SRC := test/ar/ar_test.c target_test_SRC := test/api/target_test.c arm32_target_features_test_SRC := test/api/arm32_target_features_test.c hash_test_SRC := test/api/hash_test.c +cas_test_SRC := test/api/cas_test.c release_index_test_SRC := test/api/release_index_test.c panic_recovery_test_SRC := test/api/panic_recovery_test.c profile_test_SRC := test/api/profile_test.c diff --git a/src/api/cas.c b/src/api/cas.c @@ -92,6 +92,38 @@ KitStatus kit_cas_add_blob(KitCas* cas, const uint8_t* data, size_t len, return KIT_OK; } +KitStatus kit_cas_has_blob(KitCas* cas, + const uint8_t id[KIT_CAS_HASH_LEN]) { + KitFileData fd; + KitStatus st; + if (!cas || !id) return KIT_INVALID; + fd.data = NULL; + fd.size = 0; + fd.token = NULL; + st = kit_cas_get_blob(cas, id, &fd); + if (st == KIT_OK) kit_cas_release(cas, &fd); + return st; +} + +KitStatus kit_cas_get_blob(KitCas* cas, const uint8_t id[KIT_CAS_HASH_LEN], + KitFileData* out) { + if (!cas || !id || !out) return KIT_INVALID; + out->data = NULL; + out->size = 0; + out->token = NULL; + if (dist_cas_get_blob(&cas->dist, id, out) != DIST_OK) return KIT_NOT_FOUND; + return KIT_OK; +} + +void kit_cas_release(KitCas* cas, KitFileData* fd) { + if (!cas || !fd) return; + if (fd->data && cas->host.file_io && cas->host.file_io->release) + cas->host.file_io->release(cas->host.file_io->user, fd); + fd->data = NULL; + fd->size = 0; + fd->token = NULL; +} + KitStatus kit_cas_tree_builder_new(KitCas* cas, KitCasTreeBuilder** out) { KitCasTreeBuilder* b; KitHeap* h; diff --git a/test/api/cas_test.c b/test/api/cas_test.c @@ -0,0 +1,184 @@ +/* cas_test -- public <kit/cas.h> by-id blob read surface. + * + * Run by: make test-cas-api + */ + +#include <kit/cas.h> +#include <kit/core.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "lib/kit_unit.h" + +static KitUnit g_u; +#define EXPECT(c, ...) CU_EXPECT(&g_u, c, __VA_ARGS__) + +typedef struct CasTestWriter { + KitWriter w; + FILE* f; +} CasTestWriter; + +static KitStatus cas_test_writer_write(KitWriter* w, const void* data, + size_t n) { + CasTestWriter* cw = (CasTestWriter*)w; + return fwrite(data, 1, n, cw->f) == n ? KIT_OK : KIT_IO; +} + +static KitStatus cas_test_writer_seek(KitWriter* w, uint64_t offset) { + CasTestWriter* cw = (CasTestWriter*)w; + return fseek(cw->f, (long)offset, SEEK_SET) == 0 ? KIT_OK : KIT_IO; +} + +static uint64_t cas_test_writer_tell(KitWriter* w) { + CasTestWriter* cw = (CasTestWriter*)w; + long pos = ftell(cw->f); + return pos < 0 ? 0 : (uint64_t)pos; +} + +static KitStatus cas_test_writer_status(KitWriter* w) { + CasTestWriter* cw = (CasTestWriter*)w; + return ferror(cw->f) ? KIT_IO : KIT_OK; +} + +static void cas_test_writer_close(KitWriter* w) { + CasTestWriter* cw = (CasTestWriter*)w; + if (cw->f) fclose(cw->f); + free(cw); +} + +static KitStatus cas_test_read_all(void* user, const char* path, + KitFileData* out) { + FILE* f; + long end; + uint8_t* data; + (void)user; + out->data = NULL; + out->size = 0; + out->token = NULL; + f = fopen(path, "rb"); + if (!f) return KIT_NOT_FOUND; + if (fseek(f, 0, SEEK_END) != 0) { + fclose(f); + return KIT_IO; + } + end = ftell(f); + if (end < 0 || fseek(f, 0, SEEK_SET) != 0) { + fclose(f); + return KIT_IO; + } + data = (uint8_t*)malloc((size_t)end ? (size_t)end : 1u); + if (!data) { + fclose(f); + return KIT_NOMEM; + } + if ((size_t)end && + fread(data, 1, (size_t)end, f) != (size_t)end) { + free(data); + fclose(f); + return KIT_IO; + } + fclose(f); + out->data = data; + out->size = (size_t)end; + out->token = data; + return KIT_OK; +} + +static void cas_test_release(void* user, KitFileData* fd) { + (void)user; + free(fd->token); + fd->data = NULL; + fd->size = 0; + fd->token = NULL; +} + +static KitStatus cas_test_open_writer(void* user, const char* path, + KitWriter** out) { + CasTestWriter* cw; + (void)user; + *out = NULL; + cw = (CasTestWriter*)calloc(1, sizeof *cw); + if (!cw) return KIT_NOMEM; + cw->f = fopen(path, "wb"); + if (!cw->f) { + free(cw); + return KIT_IO; + } + cw->w.write = cas_test_writer_write; + cw->w.seek = cas_test_writer_seek; + cw->w.tell = cas_test_writer_tell; + cw->w.status = cas_test_writer_status; + cw->w.close = cas_test_writer_close; + *out = &cw->w; + return KIT_OK; +} + +static int cas_test_mkdir_p(void* user, const char* path) { + char buf[1024]; + size_t i, n; + (void)user; + n = strlen(path); + if (n == 0 || n >= sizeof buf) return 1; + memcpy(buf, path, n + 1u); + for (i = 1; i <= n; ++i) { + if (buf[i] != '/' && buf[i] != '\0') continue; + { + char save = buf[i]; + buf[i] = '\0'; + if (mkdir(buf, 0777) != 0 && errno != EEXIST) return 1; + buf[i] = save; + } + } + return 0; +} + +int main(void) { + static const uint8_t data[] = "config=debug\n"; + static const char root[] = "build/test/cas_api_test_store"; + KitFileIO fio; + KitCasHost host; + KitCas* cas = NULL; + KitBlobInfo info; + KitFileData fd; + uint8_t missing[KIT_CAS_HASH_LEN]; + + kit_unit_init(&g_u); + memset(&fio, 0, sizeof fio); + fio.read_all = cas_test_read_all; + fio.release = cas_test_release; + fio.open_writer = cas_test_open_writer; + memset(&host, 0, sizeof host); + host.file_io = &fio; + host.mkdir_p = cas_test_mkdir_p; + + EXPECT(kit_cas_open(&g_u.ctx, &host, root, &cas) == KIT_OK, "open CAS"); + EXPECT(kit_cas_add_blob(cas, data, sizeof data - 1u, &info) == KIT_OK, + "add blob"); + EXPECT(kit_cas_has_blob(cas, info.id) == KIT_OK, "has added blob"); + + memset(&fd, 0, sizeof fd); + EXPECT(kit_cas_get_blob(cas, info.id, &fd) == KIT_OK, "get added blob"); + EXPECT(fd.size == sizeof data - 1u, "blob size matches"); + EXPECT(memcmp(fd.data, data, sizeof data - 1u) == 0, "blob bytes match"); + kit_cas_release(cas, &fd); + kit_cas_release(cas, &fd); + EXPECT(fd.data == NULL && fd.size == 0 && fd.token == NULL, + "release is idempotent"); + + memset(missing, 0, sizeof missing); + EXPECT(kit_cas_has_blob(cas, missing) == KIT_NOT_FOUND, + "has missing blob reports not found"); + memset(&fd, 0xff, sizeof fd); + EXPECT(kit_cas_get_blob(cas, missing, &fd) == KIT_NOT_FOUND, + "get missing blob reports not found"); + EXPECT(fd.data == NULL && fd.size == 0 && fd.token == NULL, + "miss leaves output empty"); + + kit_cas_close(cas); + kit_unit_summary(&g_u, "cas_test"); + return kit_unit_status(&g_u); +}