kit

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

cas.c (15210B)


      1 /* Public content-addressed store API: a thin composition over the internal
      2  * dist content model (src/dist/{blob,tree,cas}.c). See <kit/cas.h>. */
      3 
      4 #include "dist/cas.h"
      5 
      6 #include <kit/cas.h>
      7 #include <stdio.h>
      8 #include <string.h>
      9 
     10 #include "core/diag.h"
     11 #include "dist/blob.h"
     12 #include "dist/dist.h"
     13 #include "dist/tree.h"
     14 
     15 struct KitCas {
     16   /* Own a copy of the context so the handle outlives the caller's (possibly
     17    * stack-local) KitContext; ctx points at the stored copy. The pointed-to
     18    * heap/file_io/diag must still outlive the handle. */
     19   KitContext ctx_storage;
     20   const KitContext* ctx;
     21   KitCasHost host;
     22   DistCas dist;
     23 };
     24 
     25 struct KitCasTreeBuilder {
     26   KitCas* cas;
     27   DistTree tree;
     28   DistTreeEntry* entries;
     29 };
     30 
     31 void kit_blob_info(KitBlobInfo* out, const uint8_t* data, size_t len) {
     32   DistBlobInfo bi;
     33   if (!out) return;
     34   memset(out, 0, sizeof *out);
     35   if (dist_blob_info(&bi, data, len, DIST_BLOB_CHUNK_SIZE_DEFAULT) != DIST_OK)
     36     return;
     37   memcpy(out->id, bi.id, DIST_BLAKE2B_LEN);
     38   memcpy(out->root, bi.root, DIST_BLAKE2B_LEN);
     39   out->size = bi.size;
     40   out->chunks = bi.chunks;
     41 }
     42 
     43 void kit_hex_encode(char* out, const uint8_t* in, size_t n) {
     44   dist_hex_encode(out, in, n);
     45 }
     46 
     47 KitStatus kit_hex_decode(uint8_t* out, const char* in, size_t n) {
     48   return dist_hex_decode(out, in, n) == DIST_OK ? KIT_OK : KIT_MALFORMED;
     49 }
     50 
     51 KitStatus kit_cas_open(const KitContext* ctx, const KitCasHost* host,
     52                        const char* root_path, KitCas** out) {
     53   KitCas* cas;
     54   if (!ctx || !ctx->heap || !host || !host->file_io || !root_path || !out)
     55     return KIT_INVALID;
     56   *out = NULL;
     57   cas = (KitCas*)ctx->heap->alloc(ctx->heap, sizeof *cas, _Alignof(KitCas));
     58   if (!cas) return KIT_NOMEM;
     59   cas->ctx_storage = *ctx;
     60   cas->ctx = &cas->ctx_storage;
     61   cas->host = *host;
     62   cas->dist.host.file_io = host->file_io;
     63   cas->dist.host.mkdir_p = host->mkdir_p;
     64   cas->dist.host.mark_executable = host->mark_executable;
     65   cas->dist.host.user = host->user;
     66   cas->dist.root = root_path;
     67   *out = cas;
     68   return KIT_OK;
     69 }
     70 
     71 void kit_cas_close(KitCas* cas) {
     72   if (!cas) return;
     73   cas->ctx->heap->free(cas->ctx->heap, cas, sizeof *cas);
     74 }
     75 
     76 KitStatus kit_cas_add_blob(KitCas* cas, const uint8_t* data, size_t len,
     77                            KitBlobInfo* out) {
     78   DistBlobInfo bi;
     79   if (!cas || !out) return KIT_INVALID;
     80   if (dist_blob_info(&bi, data, len, DIST_BLOB_CHUNK_SIZE_DEFAULT) != DIST_OK) {
     81     kit_ctx_diagf(cas->ctx, "failed to hash blob");
     82     return KIT_ERR;
     83   }
     84   if (dist_cas_put_blob(&cas->dist, bi.id, data, len) != DIST_OK) {
     85     kit_ctx_diagf(cas->ctx, "failed to store blob");
     86     return KIT_IO;
     87   }
     88   memcpy(out->id, bi.id, DIST_BLAKE2B_LEN);
     89   memcpy(out->root, bi.root, DIST_BLAKE2B_LEN);
     90   out->size = bi.size;
     91   out->chunks = bi.chunks;
     92   return KIT_OK;
     93 }
     94 
     95 KitStatus kit_cas_has_blob(KitCas* cas,
     96                            const uint8_t id[KIT_CAS_HASH_LEN]) {
     97   KitFileData fd;
     98   KitStatus st;
     99   if (!cas || !id) return KIT_INVALID;
    100   fd.data = NULL;
    101   fd.size = 0;
    102   fd.token = NULL;
    103   st = kit_cas_get_blob(cas, id, &fd);
    104   if (st == KIT_OK) kit_cas_release(cas, &fd);
    105   return st;
    106 }
    107 
    108 KitStatus kit_cas_blob_path(KitCas* cas, const uint8_t id[KIT_CAS_HASH_LEN],
    109                             char* out, size_t cap) {
    110   if (!cas || !id || !out || cap == 0u) return KIT_INVALID;
    111   return dist_cas_blob_path(out, cap, cas->dist.root, id) == DIST_OK ? KIT_OK
    112                                                                      : KIT_ERR;
    113 }
    114 
    115 KitStatus kit_cas_get_blob(KitCas* cas, const uint8_t id[KIT_CAS_HASH_LEN],
    116                            KitFileData* out) {
    117   if (!cas || !id || !out) return KIT_INVALID;
    118   out->data = NULL;
    119   out->size = 0;
    120   out->token = NULL;
    121   if (dist_cas_get_blob(&cas->dist, id, out) != DIST_OK) return KIT_NOT_FOUND;
    122   return KIT_OK;
    123 }
    124 
    125 void kit_cas_release(KitCas* cas, KitFileData* fd) {
    126   if (!cas || !fd) return;
    127   if (fd->data && cas->host.file_io && cas->host.file_io->release)
    128     cas->host.file_io->release(cas->host.file_io->user, fd);
    129   fd->data = NULL;
    130   fd->size = 0;
    131   fd->token = NULL;
    132 }
    133 
    134 KitStatus kit_cas_tree_builder_new(KitCas* cas, KitCasTreeBuilder** out) {
    135   KitCasTreeBuilder* b;
    136   KitHeap* h;
    137   if (!cas || !out) return KIT_INVALID;
    138   *out = NULL;
    139   h = cas->ctx->heap;
    140   b = (KitCasTreeBuilder*)h->alloc(h, sizeof *b, _Alignof(KitCasTreeBuilder));
    141   if (!b) return KIT_NOMEM;
    142   b->cas = cas;
    143   b->entries = (DistTreeEntry*)h->alloc(h, DIST_MAX_FILES * sizeof *b->entries,
    144                                         _Alignof(DistTreeEntry));
    145   if (!b->entries) {
    146     h->free(h, b, sizeof *b);
    147     return KIT_NOMEM;
    148   }
    149   b->tree.entries = b->entries;
    150   b->tree.n_entries = 0;
    151   b->tree.cap_entries = DIST_MAX_FILES;
    152   *out = b;
    153   return KIT_OK;
    154 }
    155 
    156 KitStatus kit_cas_tree_builder_add(KitCasTreeBuilder* b, const char* tree_path,
    157                                    KitTreeMode mode, const uint8_t* data,
    158                                    size_t len) {
    159   KitCas* cas;
    160   DistBlobInfo bi;
    161   DistTreeEntry* e;
    162   if (!b || !tree_path) return KIT_INVALID;
    163   cas = b->cas;
    164   if (b->tree.n_entries >= b->tree.cap_entries) {
    165     kit_ctx_diagf(cas->ctx, "too many tree entries");
    166     return KIT_ERR;
    167   }
    168   if (!dist_tree_path_valid(tree_path)) {
    169     kit_ctx_diagf(cas->ctx, "unsafe tree path: %s", tree_path);
    170     return KIT_INVALID;
    171   }
    172   if (!dist_tree_mode_name((uint8_t)mode)) {
    173     kit_ctx_diagf(cas->ctx, "bad tree mode for: %s", tree_path);
    174     return KIT_INVALID;
    175   }
    176   if (dist_blob_info(&bi, data, len, DIST_BLOB_CHUNK_SIZE_DEFAULT) != DIST_OK) {
    177     kit_ctx_diagf(cas->ctx, "failed to hash blob: %s", tree_path);
    178     return KIT_ERR;
    179   }
    180   if (dist_cas_put_blob(&cas->dist, bi.id, data, len) != DIST_OK) {
    181     kit_ctx_diagf(cas->ctx, "failed to store blob: %s", tree_path);
    182     return KIT_IO;
    183   }
    184   e = &b->tree.entries[b->tree.n_entries++];
    185   memset(e, 0, sizeof *e);
    186   snprintf(e->path, sizeof e->path, "%s", tree_path);
    187   e->mode = (uint8_t)mode;
    188   e->size = bi.size;
    189   memcpy(e->blob, bi.id, DIST_BLAKE2B_LEN);
    190   memcpy(e->root, bi.root, DIST_BLAKE2B_LEN);
    191   return KIT_OK;
    192 }
    193 
    194 KitStatus kit_cas_tree_builder_finish(KitCasTreeBuilder* b,
    195                                       uint8_t out_tree_id[KIT_CAS_HASH_LEN]) {
    196   KitCas* cas;
    197   KitWriter* w = NULL;
    198   const uint8_t* bytes;
    199   size_t len;
    200   char err[128];
    201   if (!b || !out_tree_id) return KIT_INVALID;
    202   cas = b->cas;
    203   if (dist_tree_sort_validate(&b->tree, err, sizeof err) != DIST_OK) {
    204     kit_ctx_diagf(cas->ctx, "%s", err);
    205     return KIT_MALFORMED;
    206   }
    207   if (kit_writer_mem(cas->ctx->heap, &w) != KIT_OK) {
    208     kit_ctx_diagf(cas->ctx, "failed to allocate tree writer");
    209     return KIT_NOMEM;
    210   }
    211   if (dist_tree_emit(&b->tree, w) != DIST_OK ||
    212       kit_writer_status(w) != KIT_OK) {
    213     kit_writer_close(w);
    214     kit_ctx_diagf(cas->ctx, "failed to emit tree manifest");
    215     return KIT_ERR;
    216   }
    217   bytes = kit_writer_mem_bytes(w, &len);
    218   dist_tree_id(out_tree_id, bytes, len);
    219   if (dist_cas_put_tree(&cas->dist, out_tree_id, bytes, len) != DIST_OK) {
    220     kit_writer_close(w);
    221     kit_ctx_diagf(cas->ctx, "failed to store tree manifest");
    222     return KIT_IO;
    223   }
    224   kit_writer_close(w);
    225   return KIT_OK;
    226 }
    227 
    228 KitStatus kit_cas_add_tree_manifest(KitCas* cas, const uint8_t* data,
    229                                     size_t len,
    230                                     uint8_t out_tree_id[KIT_CAS_HASH_LEN]) {
    231   DistTree tree;
    232   DistTreeEntry* entries = NULL;
    233   KitWriter* w = NULL;
    234   const uint8_t* canon;
    235   size_t canon_len;
    236   char err[128];
    237   KitStatus st = KIT_OK;
    238 
    239   if (!cas || (!data && len) || !out_tree_id) return KIT_INVALID;
    240   entries = (DistTreeEntry*)cas->ctx->heap->alloc(
    241       cas->ctx->heap, DIST_MAX_FILES * sizeof *entries, _Alignof(DistTreeEntry));
    242   if (!entries) return KIT_NOMEM;
    243   tree.entries = entries;
    244   tree.n_entries = 0;
    245   tree.cap_entries = DIST_MAX_FILES;
    246   if (dist_tree_parse(data, len, &tree, err, sizeof err) != DIST_OK) {
    247     kit_ctx_diagf(cas->ctx, "%s", err);
    248     st = KIT_MALFORMED;
    249     goto out;
    250   }
    251   if (kit_writer_mem(cas->ctx->heap, &w) != KIT_OK) {
    252     kit_ctx_diagf(cas->ctx, "failed to allocate tree writer");
    253     st = KIT_NOMEM;
    254     goto out;
    255   }
    256   if (dist_tree_emit(&tree, w) != DIST_OK || kit_writer_status(w) != KIT_OK) {
    257     kit_ctx_diagf(cas->ctx, "failed to emit tree manifest");
    258     st = KIT_ERR;
    259     goto out;
    260   }
    261   canon = kit_writer_mem_bytes(w, &canon_len);
    262   if (canon_len != len || (len && memcmp(canon, data, len) != 0)) {
    263     kit_ctx_diagf(cas->ctx, "non-canonical tree manifest");
    264     st = KIT_MALFORMED;
    265     goto out;
    266   }
    267   dist_tree_id(out_tree_id, data, len);
    268   if (dist_cas_put_tree(&cas->dist, out_tree_id, data, len) != DIST_OK) {
    269     kit_ctx_diagf(cas->ctx, "failed to store tree manifest");
    270     st = KIT_IO;
    271     goto out;
    272   }
    273 
    274 out:
    275   if (w) kit_writer_close(w);
    276   cas->ctx->heap->free(cas->ctx->heap, entries,
    277                        DIST_MAX_FILES * sizeof *entries);
    278   return st;
    279 }
    280 
    281 void kit_cas_tree_builder_free(KitCasTreeBuilder* b) {
    282   KitHeap* h;
    283   if (!b) return;
    284   h = b->cas->ctx->heap;
    285   h->free(h, b->entries, DIST_MAX_FILES * sizeof *b->entries);
    286   h->free(h, b, sizeof *b);
    287 }
    288 
    289 typedef struct CasDirWalk {
    290   KitCasTreeBuilder* b;
    291   KitStatus status;
    292 } CasDirWalk;
    293 
    294 static int cas_dir_walk_file(void* user, const char* source_path,
    295                              const char* tree_path, int executable) {
    296   CasDirWalk* w = (CasDirWalk*)user;
    297   KitCas* cas = w->b->cas;
    298   const KitFileIO* io = cas->host.file_io;
    299   KitFileData fd;
    300   KitStatus st;
    301   fd.data = NULL;
    302   fd.size = 0;
    303   fd.token = NULL;
    304   if (io->read_all(io->user, source_path, &fd) != KIT_OK) {
    305     kit_ctx_diagf(cas->ctx, "failed to read: %s", source_path);
    306     w->status = KIT_IO;
    307     return 1;
    308   }
    309   st = kit_cas_tree_builder_add(
    310       w->b, tree_path, executable ? KIT_TREE_MODE_EXEC : KIT_TREE_MODE_FILE,
    311       fd.data, fd.size);
    312   if (io->release) io->release(io->user, &fd);
    313   if (st != KIT_OK) {
    314     w->status = st;
    315     return 1;
    316   }
    317   return 0;
    318 }
    319 
    320 KitStatus kit_cas_add_tree_from_dir(KitCas* cas, const char* root,
    321                                     uint8_t out_tree_id[KIT_CAS_HASH_LEN]) {
    322   KitCasTreeBuilder* b;
    323   CasDirWalk w;
    324   KitStatus st;
    325   if (!cas || !root || !out_tree_id) return KIT_INVALID;
    326   if (!cas->host.walk_regular_files) return KIT_UNSUPPORTED;
    327   st = kit_cas_tree_builder_new(cas, &b);
    328   if (st != KIT_OK) return st;
    329   w.b = b;
    330   w.status = KIT_OK;
    331   if (cas->host.walk_regular_files(cas->host.user, root, cas_dir_walk_file,
    332                                    &w) != 0) {
    333     if (w.status == KIT_OK) {
    334       kit_ctx_diagf(cas->ctx, "failed to walk directory: %s", root);
    335       w.status = KIT_IO;
    336     }
    337     kit_cas_tree_builder_free(b);
    338     return w.status;
    339   }
    340   st = kit_cas_tree_builder_finish(b, out_tree_id);
    341   kit_cas_tree_builder_free(b);
    342   return st;
    343 }
    344 
    345 /* Load and parse a stored tree into a heap-allocated entries buffer. On
    346  * success, *raw holds the borrowed manifest bytes (release via file_io) and
    347  * *entries the allocation to free. */
    348 static KitStatus cas_load_tree(KitCas* cas,
    349                                const uint8_t tree_id[KIT_CAS_HASH_LEN],
    350                                DistTree* tree, DistTreeEntry** entries,
    351                                KitFileData* raw) {
    352   KitHeap* h = cas->ctx->heap;
    353   char err[128];
    354   *entries = (DistTreeEntry*)h->alloc(h, DIST_MAX_FILES * sizeof **entries,
    355                                       _Alignof(DistTreeEntry));
    356   if (!*entries) return KIT_NOMEM;
    357   tree->entries = *entries;
    358   tree->n_entries = 0;
    359   tree->cap_entries = DIST_MAX_FILES;
    360   raw->data = NULL;
    361   raw->size = 0;
    362   raw->token = NULL;
    363   if (dist_cas_get_tree(&cas->dist, tree_id, raw) != DIST_OK) {
    364     kit_ctx_diagf(cas->ctx, "failed to load tree");
    365     h->free(h, *entries, DIST_MAX_FILES * sizeof **entries);
    366     *entries = NULL;
    367     return KIT_NOT_FOUND;
    368   }
    369   if (dist_tree_parse(raw->data, raw->size, tree, err, sizeof err) != DIST_OK) {
    370     kit_ctx_diagf(cas->ctx, "%s", err);
    371     if (cas->host.file_io->release)
    372       cas->host.file_io->release(cas->host.file_io->user, raw);
    373     h->free(h, *entries, DIST_MAX_FILES * sizeof **entries);
    374     *entries = NULL;
    375     return KIT_MALFORMED;
    376   }
    377   return KIT_OK;
    378 }
    379 
    380 static void cas_free_tree(KitCas* cas, DistTreeEntry* entries,
    381                           KitFileData* raw) {
    382   KitHeap* h = cas->ctx->heap;
    383   if (cas->host.file_io->release)
    384     cas->host.file_io->release(cas->host.file_io->user, raw);
    385   if (entries) h->free(h, entries, DIST_MAX_FILES * sizeof *entries);
    386 }
    387 
    388 KitStatus kit_cas_inspect_tree(KitCas* cas,
    389                                const uint8_t tree_id[KIT_CAS_HASH_LEN],
    390                                KitWriter* out) {
    391   KitFileData raw;
    392   if (!cas || !tree_id || !out) return KIT_INVALID;
    393   raw.data = NULL;
    394   raw.size = 0;
    395   raw.token = NULL;
    396   if (dist_cas_get_tree(&cas->dist, tree_id, &raw) != DIST_OK) {
    397     kit_ctx_diagf(cas->ctx, "failed to load tree");
    398     return KIT_NOT_FOUND;
    399   }
    400   if (raw.size && kit_writer_write(out, raw.data, raw.size) != KIT_OK) {
    401     if (cas->host.file_io->release)
    402       cas->host.file_io->release(cas->host.file_io->user, &raw);
    403     kit_ctx_diagf(cas->ctx, "failed to write tree manifest");
    404     return KIT_IO;
    405   }
    406   if (cas->host.file_io->release)
    407     cas->host.file_io->release(cas->host.file_io->user, &raw);
    408   return kit_writer_status(out) == KIT_OK ? KIT_OK : KIT_IO;
    409 }
    410 
    411 KitStatus kit_cas_verify_tree(KitCas* cas,
    412                               const uint8_t tree_id[KIT_CAS_HASH_LEN]) {
    413   DistTree tree;
    414   DistTreeEntry* entries;
    415   KitFileData raw;
    416   KitStatus st;
    417   size_t i;
    418   if (!cas || !tree_id) return KIT_INVALID;
    419   st = cas_load_tree(cas, tree_id, &tree, &entries, &raw);
    420   if (st != KIT_OK) return st;
    421   st = KIT_OK;
    422   for (i = 0; i < tree.n_entries; ++i) {
    423     const DistTreeEntry* e = &tree.entries[i];
    424     KitFileData fd;
    425     DistBlobInfo bi;
    426     fd.data = NULL;
    427     fd.size = 0;
    428     fd.token = NULL;
    429     if (dist_cas_get_blob(&cas->dist, e->blob, &fd) != DIST_OK) {
    430       kit_ctx_diagf(cas->ctx, "missing or corrupt blob for: %s", e->path);
    431       st = KIT_NOT_FOUND;
    432       break;
    433     }
    434     if (dist_blob_info(&bi, fd.data, fd.size, DIST_BLOB_CHUNK_SIZE_DEFAULT) !=
    435             DIST_OK ||
    436         bi.size != e->size || memcmp(bi.root, e->root, DIST_BLAKE2B_LEN) != 0) {
    437       if (cas->host.file_io->release)
    438         cas->host.file_io->release(cas->host.file_io->user, &fd);
    439       kit_ctx_diagf(cas->ctx, "blob root mismatch for: %s", e->path);
    440       st = KIT_INVALID;
    441       break;
    442     }
    443     if (cas->host.file_io->release)
    444       cas->host.file_io->release(cas->host.file_io->user, &fd);
    445   }
    446   cas_free_tree(cas, entries, &raw);
    447   return st;
    448 }
    449 
    450 KitStatus kit_cas_materialize_tree(KitCas* cas,
    451                                    const uint8_t tree_id[KIT_CAS_HASH_LEN],
    452                                    const char* dst) {
    453   DistTree tree;
    454   DistTreeEntry* entries;
    455   KitFileData raw;
    456   KitStatus st;
    457   if (!cas || !tree_id || !dst) return KIT_INVALID;
    458   st = cas_load_tree(cas, tree_id, &tree, &entries, &raw);
    459   if (st != KIT_OK) return st;
    460   if (dist_cas_materialize_tree(&cas->dist, &tree, dst) != DIST_OK) {
    461     kit_ctx_diagf(cas->ctx, "failed to materialize tree");
    462     st = KIT_ERR;
    463   }
    464   cas_free_tree(cas, entries, &raw);
    465   return st;
    466 }