kit

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

coord.c (61479B)


      1 #include "coord.h"
      2 
      3 #include "bundle.h"
      4 #include "dist/tar.h"
      5 
      6 #include <kit/compress.h>
      7 #include <kit/package.h>
      8 
      9 #include <stdarg.h>
     10 #include <stdio.h>
     11 #include <string.h>
     12 #include <stdlib.h>
     13 
     14 struct BuildTargetFuture {
     15   char target[BUILD_TARGET_MAX];
     16   uint8_t config_id[BUILD_HASH_LEN];
     17   uint8_t argv_id[BUILD_HASH_LEN];
     18   int done;
     19   int failed;
     20   BuildResolved result;
     21   struct BuildTargetFuture* next;
     22 };
     23 
     24 struct BuildTargetTable {
     25   BuildTargetFuture* futures;
     26 };
     27 
     28 struct BuildPulledSet {
     29   char target[BUILD_TARGET_MAX];
     30   struct BuildPulledSet* next;
     31 };
     32 
     33 struct BuildSourceMemo {
     34   char root[BUILD_PATH_MAX];
     35   char path[BUILD_PATH_MAX];
     36   uint8_t blob[BUILD_HASH_LEN];
     37   int present;
     38   struct BuildSourceMemo* next;
     39 };
     40 
     41 struct BuildGlobMemo {
     42   char root[BUILD_PATH_MAX];
     43   char pattern[BUILD_PATTERN_MAX];
     44   uint8_t result_hash[BUILD_HASH_LEN];
     45   BuildPathBlob* entries;
     46   size_t n_entries;
     47   struct BuildGlobMemo* next;
     48 };
     49 
     50 typedef struct BuildDeepSetMemo {
     51   BuildLeafSet leaf;
     52   BuildConfigLeaf* configs;
     53   BuildSourceLeaf* sources;
     54   BuildGlobLeaf* globs;
     55   BuildBlobLeaf* blobs;
     56   const BuildLeafSet** children;
     57   int valid_known;
     58   int valid;
     59   struct BuildDeepSetMemo* next;
     60 } BuildDeepSetMemo;
     61 
     62 struct BuildExternalWorkspace {
     63   char name[BUILD_KEY_MAX];
     64   char root[BUILD_PATH_MAX];
     65   char def_name[BUILD_PATH_MAX];
     66   BuildWorkspace workspace;
     67   struct BuildExternalWorkspace* next;
     68 };
     69 
     70 typedef struct BuildGlobExpand {
     71   KitBuildCoordinator* c;
     72   const char* root;
     73   KitSlice pattern;
     74   BuildPathBlob* entries;
     75   size_t n;
     76   size_t cap;
     77   int failed;
     78 } BuildGlobExpand;
     79 
     80 static int build_coord_source_hash_root(KitBuildCoordinator* c,
     81                                         const char* root, KitSlice path,
     82                                         uint8_t out_blob[BUILD_HASH_LEN],
     83                                         int* present);
     84 static int build_coord_glob_root(KitBuildCoordinator* c, const char* root,
     85                                  KitSlice pattern,
     86                                  uint8_t out_result_hash[BUILD_HASH_LEN],
     87                                  BuildCoordGlobFn cb, void* cb_user);
     88 static void release_file_data(KitBuildCoordinator* c, KitFileData* fd);
     89 
     90 static int path_set(char* out, size_t cap, KitSlice s) {
     91   if (!out || cap == 0u || !s.s || s.len + 1u > cap) return BUILD_ERR;
     92   memcpy(out, s.s, s.len);
     93   out[s.len] = '\0';
     94   return BUILD_OK;
     95 }
     96 
     97 static int path_join2(char* out, size_t cap, const char* a, const char* b) {
     98   size_t na, nb;
     99   int need_sep;
    100   if (!out || cap == 0u || !a || !b) return BUILD_ERR;
    101   na = strlen(a);
    102   nb = strlen(b);
    103   need_sep = na > 0u && a[na - 1u] != '/';
    104   if (na + (need_sep ? 1u : 0u) + nb + 1u > cap) return BUILD_ERR;
    105   memcpy(out, a, na);
    106   if (need_sep) out[na++] = '/';
    107   memcpy(out + na, b, nb);
    108   out[na + nb] = '\0';
    109   return BUILD_OK;
    110 }
    111 
    112 static int slice_copy(char* out, size_t cap, KitSlice s) {
    113   if (!out || cap == 0u || !s.s || s.len + 1u > cap) return BUILD_ERR;
    114   memcpy(out, s.s, s.len);
    115   out[s.len] = '\0';
    116   return BUILD_OK;
    117 }
    118 
    119 static int rel_path_safe(KitSlice s) {
    120   size_t i, start = 0;
    121   if (!s.s || s.len == 0u || s.len >= BUILD_PATH_MAX) return 0;
    122   if (s.s[0] == '/') return 0;
    123   for (i = 0; i <= s.len; ++i) {
    124     if (i == s.len || s.s[i] == '/') {
    125       size_t n = i - start;
    126       if (n == 0u) return 0;
    127       if (n == 1u && s.s[start] == '.') return 0;
    128       if (n == 2u && s.s[start] == '.' && s.s[start + 1u] == '.') return 0;
    129       start = i + 1u;
    130     } else if (s.s[i] == '\0' || s.s[i] == '\\' || s.s[i] == ':') {
    131       return 0;
    132     }
    133   }
    134   return 1;
    135 }
    136 
    137 static int glob_pattern_safe(KitSlice s) {
    138   size_t i, start = 0;
    139   if (!s.s || s.len == 0u || s.len >= BUILD_PATTERN_MAX) return 0;
    140   if (s.s[0] == '/') return 0;
    141   for (i = 0; i <= s.len; ++i) {
    142     if (i == s.len || s.s[i] == '/') {
    143       size_t n = i - start;
    144       if (n == 0u) return 0;
    145       if (n == 1u && s.s[start] == '.') return 0;
    146       if (n == 2u && s.s[start] == '.' && s.s[start + 1u] == '.') return 0;
    147       start = i + 1u;
    148     } else if (s.s[i] == '\0' || s.s[i] == '\\' || s.s[i] == ':' ||
    149                s.s[i] == '[' || s.s[i] == ']') {
    150       return 0;
    151     }
    152   }
    153   return 1;
    154 }
    155 
    156 static int glob_match_range(const char* pat, size_t pn, const char* text,
    157                             size_t tn) {
    158   size_t pi = 0, ti = 0;
    159   size_t star = (size_t)-1, mark = 0;
    160   while (ti < tn) {
    161     if (pi < pn && pat[pi] == '*') {
    162       star = pi++;
    163       mark = ti;
    164     } else if (pi < pn && (pat[pi] == '?' || pat[pi] == text[ti])) {
    165       ++pi;
    166       ++ti;
    167     } else if (star != (size_t)-1) {
    168       pi = star + 1u;
    169       ti = ++mark;
    170     } else {
    171       return 0;
    172     }
    173   }
    174   while (pi < pn && pat[pi] == '*') ++pi;
    175   return pi == pn;
    176 }
    177 
    178 static int glob_segment_double_star(const char* pat, size_t start,
    179                                     size_t end) {
    180   return end == start + 2u && pat[start] == '*' && pat[start + 1u] == '*';
    181 }
    182 
    183 static int glob_match_segments(const char* pat, size_t pn, size_t pp,
    184                                const char* text, size_t tn, size_t tp) {
    185   size_t pe = pp, te = tp;
    186   if (pp == pn) return tp == tn;
    187   while (pe < pn && pat[pe] != '/') ++pe;
    188   if (glob_segment_double_star(pat, pp, pe)) {
    189     size_t next_pp = pe < pn ? pe + 1u : pn;
    190     if (glob_match_segments(pat, pn, next_pp, text, tn, tp)) return 1;
    191     if (tp == tn) return 0;
    192     while (te < tn && text[te] != '/') ++te;
    193     return glob_match_segments(pat, pn, pp, text, tn,
    194                                te < tn ? te + 1u : tn);
    195   }
    196   if (tp == tn) return 0;
    197   while (te < tn && text[te] != '/') ++te;
    198   if (!glob_match_range(pat + pp, pe - pp, text + tp, te - tp)) return 0;
    199   if (pe == pn || te == tn) return pe == pn && te == tn;
    200   return glob_match_segments(pat, pn, pe + 1u, text, tn, te + 1u);
    201 }
    202 
    203 static int glob_match_path(KitSlice pattern, const char* path) {
    204   if (!pattern.s || !path) return 0;
    205   return glob_match_segments(pattern.s, pattern.len, 0, path, strlen(path), 0);
    206 }
    207 
    208 static int path_blob_cmp_qsort(const void* a, const void* b) {
    209   const BuildPathBlob* pa = (const BuildPathBlob*)a;
    210   const BuildPathBlob* pb = (const BuildPathBlob*)b;
    211   return strcmp(pa->path, pb->path);
    212 }
    213 
    214 static int glob_entries_push(BuildGlobExpand* g, const char* rel,
    215                              const uint8_t blob[BUILD_HASH_LEN]) {
    216   BuildPathBlob* next;
    217   size_t new_cap;
    218   if (!g || !rel || strlen(rel) >= BUILD_PATH_MAX) return BUILD_ERR;
    219   if (g->n == g->cap) {
    220     new_cap = g->cap ? g->cap * 2u : 16u;
    221     next = (BuildPathBlob*)g->c->ctx->heap->realloc(
    222         g->c->ctx->heap, g->entries, g->cap * sizeof *g->entries,
    223         new_cap * sizeof *g->entries, _Alignof(BuildPathBlob));
    224     if (!next) return BUILD_ERR;
    225     g->entries = next;
    226     g->cap = new_cap;
    227   }
    228   snprintf(g->entries[g->n].path, sizeof g->entries[g->n].path, "%s", rel);
    229   memcpy(g->entries[g->n].blob, blob, BUILD_HASH_LEN);
    230   ++g->n;
    231   return BUILD_OK;
    232 }
    233 
    234 static int glob_walk_cb(void* user, const char* source_path,
    235                         const char* tree_path, int executable) {
    236   BuildGlobExpand* g = (BuildGlobExpand*)user;
    237   uint8_t blob[BUILD_HASH_LEN];
    238   int present = 0;
    239   (void)source_path;
    240   (void)executable;
    241   if (!g || !tree_path) return 1;
    242   if (!glob_match_path(g->pattern, tree_path)) return 0;
    243   if (build_coord_source_hash_root(g->c, g->root, kit_slice_cstr(tree_path),
    244                                    blob, &present) != BUILD_OK ||
    245       !present ||
    246       glob_entries_push(g, tree_path, blob) != BUILD_OK) {
    247     g->failed = 1;
    248     return 1;
    249   }
    250   return 0;
    251 }
    252 
    253 static int read_workspace_manifest(KitBuildCoordinator* c, const char* root,
    254                                    BuildWorkspace* out, int* present) {
    255   char path[BUILD_PATH_MAX];
    256   KitFileData fd;
    257   char err[160];
    258   if (!c || !root || !out || !present) return BUILD_ERR;
    259   *present = 0;
    260   build_workspace_init(out);
    261   if (path_join2(path, sizeof path, root, "WORKSPACE.kit") != BUILD_OK)
    262     return BUILD_ERR;
    263   memset(&fd, 0, sizeof fd);
    264   if (c->host.cas_host->file_io->read_all(c->host.cas_host->file_io->user, path,
    265                                           &fd) != KIT_OK)
    266     return BUILD_OK;
    267   *present = 1;
    268   if (build_workspace_parse(fd.data, fd.size, out, err, sizeof err) !=
    269       BUILD_OK) {
    270     build_diagf(c->ctx, "build: %s: %s", path, err);
    271     release_file_data(c, &fd);
    272     return BUILD_ERR;
    273   }
    274   release_file_data(c, &fd);
    275   return BUILD_OK;
    276 }
    277 
    278 static int external_key(const BuildWorkspaceExternal* ext, char out[BUILD_HEX_LEN]) {
    279   KitBlobInfo bi;
    280   char buf[BUILD_PATH_MAX + 160];
    281   char archive[BUILD_HEX_LEN], package[BUILD_HEX_LEN];
    282   const char* fmt = "unknown";
    283   if (!ext || !out) return BUILD_ERR;
    284   if (ext->format == BUILD_WS_EXT_TREE) fmt = "tree";
    285   if (ext->format == BUILD_WS_EXT_KPKG) fmt = "kpkg";
    286   if (ext->format == BUILD_WS_EXT_TARGZ) fmt = "tar.gz";
    287   kit_hex_encode(archive, ext->archive, BUILD_HASH_LEN);
    288   if (ext->has_package)
    289     kit_hex_encode(package, ext->package, BUILD_HASH_LEN);
    290   else
    291     package[0] = '\0';
    292   snprintf(buf, sizeof buf, "%s %s %s %s", fmt, archive, package,
    293            ext->strip_prefix);
    294   kit_blob_info(&bi, (const uint8_t*)buf, strlen(buf));
    295   kit_hex_encode(out, bi.id, BUILD_HASH_LEN);
    296   return BUILD_OK;
    297 }
    298 
    299 static int path_parent_dir(const char* path, char* out, size_t cap) {
    300   size_t n, i;
    301   if (!path || !out || cap == 0u) return BUILD_ERR;
    302   n = strlen(path);
    303   while (n && (path[n - 1u] == '/' || path[n - 1u] == '\\')) --n;
    304   for (i = n; i > 0u; --i) {
    305     if (path[i - 1u] == '/' || path[i - 1u] == '\\') {
    306       if (i > cap) return BUILD_ERR;
    307       memcpy(out, path, i - 1u);
    308       out[i - 1u] = '\0';
    309       return BUILD_OK;
    310     }
    311   }
    312   out[0] = '\0';
    313   return BUILD_OK;
    314 }
    315 
    316 static int external_write_file(KitBuildCoordinator* c, const char* root,
    317                                const char* rel, const uint8_t* data,
    318                                size_t len, int executable) {
    319   char path[BUILD_PATH_MAX];
    320   char parent[BUILD_PATH_MAX];
    321   KitWriter* w = NULL;
    322   KitStatus st;
    323   if (!c || !root || !rel || (!data && len)) return BUILD_ERR;
    324   if (path_join2(path, sizeof path, root, rel) != BUILD_OK ||
    325       path_parent_dir(path, parent, sizeof parent) != BUILD_OK)
    326     return BUILD_ERR;
    327   if (parent[0] && c->host.cas_host->mkdir_p &&
    328       c->host.cas_host->mkdir_p(c->host.cas_host->user, parent) != 0)
    329     return BUILD_ERR;
    330   if (!c->host.cas_host->file_io ||
    331       c->host.cas_host->file_io->open_writer(c->host.cas_host->file_io->user,
    332                                              path, &w) != KIT_OK ||
    333       !w)
    334     return BUILD_ERR;
    335   st = len ? kit_writer_write(w, data, len) : KIT_OK;
    336   if (st == KIT_OK) st = kit_writer_status(w);
    337   kit_writer_close(w);
    338   if (st != KIT_OK) return BUILD_ERR;
    339   if (executable && c->host.cas_host->mark_executable &&
    340       c->host.cas_host->mark_executable(c->host.cas_host->user, path) != 0)
    341     return BUILD_ERR;
    342   return BUILD_OK;
    343 }
    344 
    345 static int external_tar_relpath(const BuildWorkspaceExternal* ext,
    346                                 const DistTarEntry* e,
    347                                 char out[BUILD_PATH_MAX], int* skip) {
    348   const char* name;
    349   const char* rel;
    350   size_t prefix_len;
    351   if (!ext || !e || !out || !skip) return BUILD_ERR;
    352   *skip = 0;
    353   name = e->name;
    354   if (!name[0]) return BUILD_ERR;
    355   if (ext->strip_prefix[0]) {
    356     prefix_len = strlen(ext->strip_prefix);
    357     if (strncmp(name, ext->strip_prefix, prefix_len) != 0)
    358       return BUILD_ERR;
    359     if (name[prefix_len] == '\0') {
    360       *skip = 1;
    361       return BUILD_OK;
    362     }
    363     if (name[prefix_len] != '/') return BUILD_ERR;
    364     rel = name + prefix_len + 1u;
    365   } else {
    366     rel = name;
    367   }
    368   if (!rel[0]) {
    369     *skip = 1;
    370     return BUILD_OK;
    371   }
    372   if (strlen(rel) >= BUILD_PATH_MAX || !rel_path_safe(kit_slice_cstr(rel)))
    373     return BUILD_ERR;
    374   snprintf(out, BUILD_PATH_MAX, "%s", rel);
    375   return BUILD_OK;
    376 }
    377 
    378 static int materialize_targz_external(KitBuildCoordinator* c,
    379                                       const BuildWorkspaceExternal* ext,
    380                                       const char* root) {
    381   KitSlice urls[8];
    382   KitFileData fd;
    383   KitWriter* tar_w = NULL;
    384   const uint8_t* tar_bytes;
    385   size_t tar_len;
    386   DistTarEntry entries[DIST_MAX_FILES];
    387   size_t nentries = 0;
    388   char blob_path[BUILD_PATH_MAX];
    389   size_t i;
    390   int ok = BUILD_ERR;
    391   if (!c || !ext || !root) return BUILD_ERR;
    392   for (i = 0; i < ext->n_urls; ++i) urls[i] = kit_slice_cstr(ext->urls[i]);
    393   if (build_coord_fetch_blob(c, ext->archive, urls, ext->n_urls, blob_path,
    394                              sizeof blob_path) != BUILD_OK)
    395     return BUILD_ERR;
    396   memset(&fd, 0, sizeof fd);
    397   if (kit_cas_get_blob(c->cas, ext->archive, &fd) != KIT_OK) return BUILD_ERR;
    398   if (kit_writer_mem(c->ctx->heap, &tar_w) != KIT_OK || !tar_w)
    399     goto out;
    400   if (kit_decompress(c->ctx, KIT_COMPRESS_GZIP, fd.data, fd.size, tar_w) !=
    401           KIT_OK ||
    402       kit_writer_status(tar_w) != KIT_OK)
    403     goto out;
    404   tar_bytes = kit_writer_mem_bytes(tar_w, &tar_len);
    405   if (dist_tar_iter(tar_bytes, tar_len, entries,
    406                     sizeof entries / sizeof entries[0], &nentries) != DIST_OK)
    407     goto out;
    408   for (i = 0; i < nentries; ++i) {
    409     char rel[BUILD_PATH_MAX];
    410     int skip = 0;
    411     if (entries[i].type == '5') continue;
    412     if (entries[i].type != '\0' && entries[i].type != '0') goto out;
    413     if (external_tar_relpath(ext, &entries[i], rel, &skip) != BUILD_OK)
    414       goto out;
    415     if (skip) continue;
    416     if (external_write_file(c, root, rel, entries[i].data, entries[i].size,
    417                             (entries[i].mode & 0111u) != 0u) != BUILD_OK)
    418       goto out;
    419   }
    420   ok = BUILD_OK;
    421 out:
    422   if (tar_w) kit_writer_close(tar_w);
    423   kit_cas_release(c->cas, &fd);
    424   return ok;
    425 }
    426 
    427 static int trusted_pkgid(const char* trusted, uint8_t out[BUILD_HASH_LEN],
    428                          int* present) {
    429   const char* p;
    430   char hex[BUILD_HEX_LEN];
    431   if (!trusted || !out || !present) return BUILD_ERR;
    432   *present = 0;
    433   p = strstr(trusted, "pkgid=");
    434   if (!p) return BUILD_OK;
    435   p += 6u;
    436   if (strlen(p) < BUILD_HEX_LEN - 1u) return BUILD_ERR;
    437   memcpy(hex, p, BUILD_HEX_LEN - 1u);
    438   hex[BUILD_HEX_LEN - 1u] = '\0';
    439   if (kit_hex_decode(out, hex, BUILD_HASH_LEN) != KIT_OK) return BUILD_ERR;
    440   *present = 1;
    441   return BUILD_OK;
    442 }
    443 
    444 static int ensure_external_workspace(KitBuildCoordinator* c,
    445                                      const BuildWorkspace* parent_ws,
    446                                      KitSlice repo, KitSlice instance,
    447                                      BuildExternalWorkspace** out) {
    448   BuildExternalWorkspace* ew;
    449   const BuildWorkspaceExternal* ext;
    450   char key[BUILD_HEX_LEN];
    451   char parent[BUILD_PATH_MAX];
    452   char root[BUILD_PATH_MAX];
    453   int present = 0;
    454   if (!c || !parent_ws || !out || !repo.s || repo.len == 0u ||
    455       !instance.s || instance.len == 0u || instance.len >= BUILD_KEY_MAX)
    456     return BUILD_ERR;
    457   for (ew = c->externals; ew; ew = ew->next) {
    458     if (strlen(ew->name) == instance.len &&
    459         memcmp(ew->name, instance.s, instance.len) == 0) {
    460       *out = ew;
    461       return BUILD_OK;
    462     }
    463   }
    464   ext = build_workspace_external_find(parent_ws, repo);
    465   if (!ext) {
    466     build_diagf(c->ctx, "build: unknown external repo @%.*s",
    467                 KIT_SLICE_ARG(repo));
    468     return BUILD_ERR;
    469   }
    470   if (external_key(ext, key) != BUILD_OK ||
    471       path_join2(parent, sizeof parent, c->store.root, "external") !=
    472           BUILD_OK ||
    473       path_join2(root, sizeof root, parent, key) != BUILD_OK)
    474     return BUILD_ERR;
    475   if (c->host.cas_host->mkdir_p &&
    476       c->host.cas_host->mkdir_p(c->host.cas_host->user, parent) != 0)
    477     return BUILD_ERR;
    478   if (ext->format == BUILD_WS_EXT_TREE) {
    479     if (kit_cas_verify_tree(c->cas, ext->archive) != KIT_OK)
    480       return BUILD_ERR;
    481     if (c->host.cas_host->mkdir_p &&
    482         c->host.cas_host->mkdir_p(c->host.cas_host->user, root) != 0)
    483       return BUILD_ERR;
    484     if (kit_cas_materialize_tree(c->cas, ext->archive, root) != KIT_OK)
    485       return BUILD_ERR;
    486   } else if (ext->format == BUILD_WS_EXT_KPKG) {
    487     KitFileData fd;
    488     KitPkgVerifyOptions opts;
    489     KitPkgVerifyResult result;
    490     KitSlice urls[8];
    491     size_t i;
    492     char blob_path[BUILD_PATH_MAX];
    493     for (i = 0; i < ext->n_urls; ++i) urls[i] = kit_slice_cstr(ext->urls[i]);
    494     if (build_coord_fetch_blob(c, ext->archive, urls, ext->n_urls, blob_path,
    495                                sizeof blob_path) != BUILD_OK)
    496       return BUILD_ERR;
    497     memset(&fd, 0, sizeof fd);
    498     if (kit_cas_get_blob(c->cas, ext->archive, &fd) != KIT_OK) return BUILD_ERR;
    499     memset(&opts, 0, sizeof opts);
    500     memset(&result, 0, sizeof result);
    501     opts.pkg_data = fd.data;
    502     opts.pkg_len = fd.size;
    503     opts.format = KIT_PKG_FORMAT_KPKG;
    504     opts.unpack_dir = root;
    505     opts.tofu = 1;
    506     if (kit_pkg_verify(c->ctx, c->host.cas_host, &opts, &result) != KIT_OK) {
    507       kit_cas_release(c->cas, &fd);
    508       return BUILD_ERR;
    509     }
    510     if (ext->has_package) {
    511       uint8_t got[BUILD_HASH_LEN];
    512       int has_pkgid = 0;
    513       if (trusted_pkgid(result.trusted, got, &has_pkgid) != BUILD_OK ||
    514           !has_pkgid || !build_id_eq(got, ext->package)) {
    515         kit_cas_release(c->cas, &fd);
    516         return BUILD_ERR;
    517       }
    518     }
    519     kit_cas_release(c->cas, &fd);
    520   } else if (ext->format == BUILD_WS_EXT_TARGZ) {
    521     if (c->host.cas_host->mkdir_p &&
    522         c->host.cas_host->mkdir_p(c->host.cas_host->user, root) != 0)
    523       return BUILD_ERR;
    524     if (materialize_targz_external(c, ext, root) != BUILD_OK)
    525       return BUILD_ERR;
    526   } else {
    527     return BUILD_ERR;
    528   }
    529   ew = (BuildExternalWorkspace*)c->ctx->heap->alloc(
    530       c->ctx->heap, sizeof *ew, _Alignof(BuildExternalWorkspace));
    531   if (!ew) return BUILD_ERR;
    532   memset(ew, 0, sizeof *ew);
    533   memcpy(ew->name, instance.s, instance.len);
    534   ew->name[instance.len] = '\0';
    535   snprintf(ew->root, sizeof ew->root, "%s", root);
    536   if (read_workspace_manifest(c, root, &ew->workspace, &present) != BUILD_OK) {
    537     c->ctx->heap->free(c->ctx->heap, ew, sizeof *ew);
    538     return BUILD_ERR;
    539   }
    540   snprintf(ew->def_name, sizeof ew->def_name, "%s",
    541            ew->workspace.def_name[0] ? ew->workspace.def_name : "BUILD.kit");
    542   ew->next = c->externals;
    543   c->externals = ew;
    544   *out = ew;
    545   return BUILD_OK;
    546 }
    547 
    548 static int target_workspace(KitBuildCoordinator* c, KitSlice target,
    549                             char repo[BUILD_KEY_MAX], const char** root_out,
    550                             const char** def_name_out,
    551                             const BuildWorkspace** ws_out) {
    552   char package[BUILD_PATH_MAX];
    553   char local[BUILD_TARGET_MAX];
    554   if (!c || !root_out || !def_name_out || !ws_out) return BUILD_ERR;
    555   if (build_target_split_repo(target, repo, package, local) != BUILD_OK)
    556     return BUILD_ERR;
    557   if (repo && repo[0]) {
    558     const BuildWorkspace* cur_ws = &c->root_workspace;
    559     const char* cur_root = c->workspace_root;
    560     const char* cur_def_name = c->build_def_name;
    561     char instance[BUILD_KEY_MAX];
    562     size_t instance_len = 0;
    563     size_t pos = 0;
    564     size_t repo_len = strlen(repo);
    565     while (pos < repo_len) {
    566       BuildExternalWorkspace* ew = NULL;
    567       KitSlice name;
    568       KitSlice inst;
    569       size_t start = pos;
    570       while (pos < repo_len && repo[pos] != '+') ++pos;
    571       if (pos == start) return BUILD_ERR;
    572       if (instance_len) {
    573         if (instance_len + 1u >= sizeof instance) return BUILD_ERR;
    574         instance[instance_len++] = '+';
    575       }
    576       if (instance_len + (pos - start) + 1u > sizeof instance)
    577         return BUILD_ERR;
    578       memcpy(instance + instance_len, repo + start, pos - start);
    579       instance_len += pos - start;
    580       instance[instance_len] = '\0';
    581       name.s = repo + start;
    582       name.len = pos - start;
    583       inst = kit_slice_cstr(instance);
    584       if (ensure_external_workspace(c, cur_ws, name, inst, &ew) != BUILD_OK)
    585         return BUILD_ERR;
    586       cur_ws = &ew->workspace;
    587       cur_root = ew->root;
    588       cur_def_name = ew->def_name;
    589       if (pos < repo_len && repo[pos] == '+') {
    590         ++pos;
    591         if (pos == repo_len) return BUILD_ERR;
    592       }
    593     }
    594     *root_out = cur_root;
    595     *def_name_out = cur_def_name;
    596     *ws_out = cur_ws;
    597   } else {
    598     *root_out = c->workspace_root;
    599     *def_name_out = c->build_def_name;
    600     *ws_out = &c->root_workspace;
    601   }
    602   return BUILD_OK;
    603 }
    604 
    605 static void free_deepsets(KitBuildCoordinator* c) {
    606   BuildDeepSetMemo* n;
    607   if (!c || !c->ctx || !c->ctx->heap) return;
    608   n = c->deepsets;
    609   while (n) {
    610     BuildDeepSetMemo* next = n->next;
    611     if (n->configs)
    612       c->ctx->heap->free(c->ctx->heap, n->configs,
    613                          n->leaf.n_configs * sizeof *n->configs);
    614     if (n->sources)
    615       c->ctx->heap->free(c->ctx->heap, n->sources,
    616                          n->leaf.n_sources * sizeof *n->sources);
    617     if (n->globs)
    618       c->ctx->heap->free(c->ctx->heap, n->globs,
    619                          n->leaf.n_globs * sizeof *n->globs);
    620     if (n->blobs)
    621       c->ctx->heap->free(c->ctx->heap, n->blobs,
    622                          n->leaf.n_blobs * sizeof *n->blobs);
    623     if (n->children)
    624       c->ctx->heap->free(c->ctx->heap, n->children,
    625                          n->leaf.n_children * sizeof *n->children);
    626     c->ctx->heap->free(c->ctx->heap, n, sizeof *n);
    627     n = next;
    628   }
    629   c->deepsets = NULL;
    630 }
    631 
    632 static void free_targets(KitBuildCoordinator* c) {
    633   BuildTargetFuture* f;
    634   BuildTargetTable* t;
    635   if (!c || !c->ctx || !c->ctx->heap || !c->targets) return;
    636   t = c->targets;
    637   f = t->futures;
    638   while (f) {
    639     BuildTargetFuture* next = f->next;
    640     c->ctx->heap->free(c->ctx->heap, f, sizeof *f);
    641     f = next;
    642   }
    643   c->ctx->heap->free(c->ctx->heap, t, sizeof *t);
    644   c->targets = NULL;
    645 }
    646 
    647 static void free_pulled(KitBuildCoordinator* c) {
    648   BuildPulledSet* n;
    649   if (!c || !c->ctx || !c->ctx->heap) return;
    650   n = c->pulled;
    651   while (n) {
    652     BuildPulledSet* next = n->next;
    653     c->ctx->heap->free(c->ctx->heap, n, sizeof *n);
    654     n = next;
    655   }
    656   c->pulled = NULL;
    657 }
    658 
    659 static void free_external_workspaces(KitBuildCoordinator* c) {
    660   BuildExternalWorkspace* ew;
    661   if (!c || !c->ctx || !c->ctx->heap) return;
    662   ew = c->externals;
    663   while (ew) {
    664     BuildExternalWorkspace* next = ew->next;
    665     c->ctx->heap->free(c->ctx->heap, ew, sizeof *ew);
    666     ew = next;
    667   }
    668   c->externals = NULL;
    669 }
    670 
    671 static void free_sources(KitBuildCoordinator* c) {
    672   BuildSourceMemo* n;
    673   if (!c || !c->ctx || !c->ctx->heap) return;
    674   n = c->sources;
    675   while (n) {
    676     BuildSourceMemo* next = n->next;
    677     c->ctx->heap->free(c->ctx->heap, n, sizeof *n);
    678     n = next;
    679   }
    680   c->sources = NULL;
    681 }
    682 
    683 static void free_globs(KitBuildCoordinator* c) {
    684   BuildGlobMemo* n;
    685   if (!c || !c->ctx || !c->ctx->heap) return;
    686   n = c->globs;
    687   while (n) {
    688     BuildGlobMemo* next = n->next;
    689     if (n->entries)
    690       c->ctx->heap->free(c->ctx->heap, n->entries,
    691                          n->n_entries * sizeof *n->entries);
    692     c->ctx->heap->free(c->ctx->heap, n, sizeof *n);
    693     n = next;
    694   }
    695   c->globs = NULL;
    696 }
    697 
    698 void build_coord_stat_bump(KitBuildCoordinator* c, BuildStatField f) {
    699   if (!c) return;
    700   switch (f) {
    701     case BUILD_STAT_DEEP_HIT: ++c->stats.deep_hits; break;
    702     case BUILD_STAT_SHALLOW_HIT: ++c->stats.shallow_hits; break;
    703     case BUILD_STAT_RECIPE_RUN: ++c->stats.recipes_run; break;
    704     case BUILD_STAT_MATERIALIZE_MISS: ++c->stats.materialize_misses; break;
    705     case BUILD_STAT_OBJECT_FETCH: ++c->stats.object_fetches; break;
    706     case BUILD_STAT_TRACE_PULL: ++c->stats.trace_pulls; break;
    707     case BUILD_STAT_TEST_RUN: ++c->stats.test_runs; break;
    708     case BUILD_STAT_TEST_CACHE_HIT: ++c->stats.test_cache_hits; break;
    709     case BUILD_STAT_TEST_FAILURE: ++c->stats.test_failures; break;
    710   }
    711 }
    712 
    713 void build_coord_tracef(KitBuildCoordinator* c, const char* fmt, ...) {
    714   char line[1024];
    715   KitSlice slice;
    716   va_list ap;
    717   int n;
    718   if (!c || !c->opts.trace || !fmt) return;
    719   va_start(ap, fmt);
    720   n = vsnprintf(line, sizeof line, fmt, ap);
    721   va_end(ap);
    722   if (n < 0) return;
    723   if ((size_t)n >= sizeof line) {
    724     line[sizeof line - 1u] = '\0';
    725     n = (int)strlen(line);
    726   }
    727   slice.s = line;
    728   slice.len = (size_t)n;
    729   c->opts.trace(c->opts.trace_user, slice);
    730 }
    731 
    732 KitStatus build_coord_open(const KitContext* ctx, const KitBuildHost* host,
    733                            KitSlice store_root, const KitBuildOptions* opts,
    734                            KitBuildCoordinator** out) {
    735   KitBuildCoordinator* c;
    736   KitHeap* h;
    737   int have_workspace = 0;
    738   KitStatus st;
    739 
    740   if (!ctx || !ctx->heap || !host || !host->cas_host ||
    741       !host->cas_host->file_io || !host->store_io || !opts || !out)
    742     return KIT_INVALID;
    743   *out = NULL;
    744   h = ctx->heap;
    745   c = (KitBuildCoordinator*)h->alloc(h, sizeof *c,
    746                                      _Alignof(KitBuildCoordinator));
    747   if (!c) return KIT_NOMEM;
    748   memset(c, 0, sizeof *c);
    749   c->ctx_storage = *ctx;
    750   c->ctx = &c->ctx_storage;
    751   c->host = *host;
    752   c->opts = *opts;
    753   c->jobs_limit = opts->jobs > 0 ? opts->jobs : 1;
    754   if (!host->sched) c->jobs_limit = 1;
    755   if (path_set(c->workspace_root, sizeof c->workspace_root,
    756                opts->workspace_root) != BUILD_OK ||
    757       path_set(c->store_root, sizeof c->store_root, store_root) != BUILD_OK ||
    758       path_join2(c->cas_root, sizeof c->cas_root, c->store_root, "cas") !=
    759           BUILD_OK) {
    760     h->free(h, c, sizeof *c);
    761     return KIT_INVALID;
    762   }
    763   build_workspace_init(&c->root_workspace);
    764   if (read_workspace_manifest(c, c->workspace_root, &c->root_workspace,
    765                               &have_workspace) != BUILD_OK) {
    766     h->free(h, c, sizeof *c);
    767     return KIT_MALFORMED;
    768   }
    769 
    770   if (!opts->build_def_path.s || opts->build_def_path.len == 0u) {
    771     if (path_set(c->build_def_name, sizeof c->build_def_name,
    772                  kit_slice_cstr(c->root_workspace.def_name[0]
    773                                     ? c->root_workspace.def_name
    774                                     : "BUILD.kit")) != BUILD_OK) {
    775       h->free(h, c, sizeof *c);
    776       return KIT_INVALID;
    777     }
    778   } else if (!rel_path_safe(opts->build_def_path) ||
    779              memchr(opts->build_def_path.s, '/', opts->build_def_path.len) ||
    780              path_set(c->build_def_name, sizeof c->build_def_name,
    781                       opts->build_def_path) != BUILD_OK) {
    782     build_diagf(&c->ctx_storage, "build: bad build definition name");
    783     h->free(h, c, sizeof *c);
    784     return KIT_INVALID;
    785   }
    786 
    787   st = kit_cas_open(c->ctx, host->cas_host, c->cas_root, &c->cas);
    788   if (st != KIT_OK) {
    789     h->free(h, c, sizeof *c);
    790     return st;
    791   }
    792   if (build_store_open(c->ctx, c->cas, host->store_io, host->cas_host,
    793                        store_root, &c->store) != BUILD_OK) {
    794     kit_cas_close(c->cas);
    795     h->free(h, c, sizeof *c);
    796     return KIT_IO;
    797   }
    798   *out = c;
    799   return KIT_OK;
    800 }
    801 
    802 void build_coord_close(KitBuildCoordinator* c) {
    803   KitHeap* h;
    804   if (!c) return;
    805   h = c->ctx->heap;
    806   free_targets(c);
    807   free_pulled(c);
    808   free_external_workspaces(c);
    809   free_sources(c);
    810   free_globs(c);
    811   free_deepsets(c);
    812   kit_cas_close(c->cas);
    813   h->free(h, c, sizeof *c);
    814 }
    815 
    816 int build_coord_source_hash(KitBuildCoordinator* c, KitSlice path,
    817                             uint8_t out_blob[BUILD_HASH_LEN], int* present) {
    818   return build_coord_source_hash_root(c, c ? c->workspace_root : NULL, path,
    819                                       out_blob, present);
    820 }
    821 
    822 int build_coord_source_hash_target(KitBuildCoordinator* c, KitSlice target,
    823                                    KitSlice path,
    824                                    uint8_t out_blob[BUILD_HASH_LEN],
    825                                    int* present) {
    826   char repo[BUILD_KEY_MAX];
    827   const char* root;
    828   const char* def_name;
    829   const BuildWorkspace* ws;
    830   (void)def_name;
    831   (void)ws;
    832   if (target_workspace(c, target, repo, &root, &def_name, &ws) != BUILD_OK)
    833     return BUILD_ERR;
    834   return build_coord_source_hash_root(c, root, path, out_blob, present);
    835 }
    836 
    837 static int build_coord_source_hash_root(KitBuildCoordinator* c, const char* root,
    838                                         KitSlice path,
    839                                         uint8_t out_blob[BUILD_HASH_LEN],
    840                                         int* present) {
    841   char full[BUILD_PATH_MAX];
    842   char rel[BUILD_PATH_MAX];
    843   KitFileData fd;
    844   KitBlobInfo info;
    845   BuildSourceMemo* memo;
    846   BuildSourceMemo* fresh = NULL;
    847   if (!c || !root || !out_blob || !present || !rel_path_safe(path))
    848     return BUILD_ERR;
    849   if (slice_copy(rel, sizeof rel, path) != BUILD_OK) return BUILD_ERR;
    850   for (memo = c->sources; memo; memo = memo->next) {
    851     if (strcmp(memo->root, root) == 0 && strcmp(memo->path, rel) == 0) {
    852       memcpy(out_blob, memo->blob, BUILD_HASH_LEN);
    853       *present = memo->present;
    854       return BUILD_OK;
    855     }
    856   }
    857   fresh = (BuildSourceMemo*)c->ctx->heap->alloc(c->ctx->heap, sizeof *fresh,
    858                                                 _Alignof(BuildSourceMemo));
    859   if (!fresh) return BUILD_ERR;
    860   memset(fresh, 0, sizeof *fresh);
    861   snprintf(fresh->root, sizeof fresh->root, "%s", root);
    862   snprintf(fresh->path, sizeof fresh->path, "%s", rel);
    863   *present = 0;
    864   if (path_join2(full, sizeof full, root, rel) != BUILD_OK)
    865     goto err;
    866   fd.data = NULL;
    867   fd.size = 0;
    868   fd.token = NULL;
    869   if (c->host.cas_host->file_io->read_all(c->host.cas_host->file_io->user, full,
    870                                           &fd) != KIT_OK) {
    871     memset(out_blob, 0, BUILD_HASH_LEN);
    872     memset(fresh->blob, 0, BUILD_HASH_LEN);
    873     fresh->present = 0;
    874     fresh->next = c->sources;
    875     c->sources = fresh;
    876     return BUILD_OK;
    877   }
    878   if (kit_cas_add_blob(c->cas, fd.data, fd.size, &info) != KIT_OK) {
    879     if (c->host.cas_host->file_io->release)
    880       c->host.cas_host->file_io->release(c->host.cas_host->file_io->user, &fd);
    881     goto err;
    882   }
    883   memcpy(out_blob, info.id, BUILD_HASH_LEN);
    884   memcpy(fresh->blob, info.id, BUILD_HASH_LEN);
    885   *present = 1;
    886   fresh->present = 1;
    887   if (c->host.cas_host->file_io->release)
    888     c->host.cas_host->file_io->release(c->host.cas_host->file_io->user, &fd);
    889   fresh->next = c->sources;
    890   c->sources = fresh;
    891   return BUILD_OK;
    892 
    893 err:
    894   if (fresh) c->ctx->heap->free(c->ctx->heap, fresh, sizeof *fresh);
    895   return BUILD_ERR;
    896 }
    897 
    898 int build_coord_fetch_blob(KitBuildCoordinator* c,
    899                            const uint8_t expected_blob[BUILD_HASH_LEN],
    900                            const KitSlice* urls, size_t nurls, char* path_out,
    901                            size_t path_cap) {
    902   char tmp_parent[BUILD_PATH_MAX];
    903   char tmp_dir[BUILD_PATH_MAX];
    904   char dest[BUILD_PATH_MAX];
    905   size_t i;
    906   int installed = 0;
    907   if (!c || !expected_blob || !path_out || path_cap == 0u)
    908     return BUILD_ERR;
    909   path_out[0] = '\0';
    910   if (kit_cas_has_blob(c->cas, expected_blob) == KIT_OK)
    911     return kit_cas_blob_path(c->cas, expected_blob, path_out, path_cap) ==
    912                    KIT_OK
    913                ? BUILD_OK
    914                : BUILD_ERR;
    915   if (!urls || nurls == 0u) return BUILD_ERR;
    916   if (!c->host.fetch || !c->host.fetch->fetch_url || !c->host.store_io ||
    917       !c->host.store_io->make_temp_dir || !c->host.store_io->remove)
    918     return BUILD_ERR;
    919   if (path_join2(tmp_parent, sizeof tmp_parent, c->store.root, "tmp") !=
    920           BUILD_OK ||
    921       c->host.store_io->make_temp_dir(c->host.store_io->user,
    922                                       kit_slice_cstr(tmp_parent), tmp_dir,
    923                                       sizeof tmp_dir) != 0 ||
    924       path_join2(dest, sizeof dest, tmp_dir, "fetch") != BUILD_OK)
    925     return BUILD_ERR;
    926 
    927   for (i = 0; i < nurls; ++i) {
    928     KitFileData fd;
    929     KitBlobInfo info;
    930     fd.data = NULL;
    931     fd.size = 0;
    932     fd.token = NULL;
    933     if (c->host.fetch->fetch_url(c->host.fetch->user, urls[i],
    934                                  kit_slice_cstr(dest)) != 0)
    935       continue;
    936     if (c->host.cas_host->file_io->read_all(c->host.cas_host->file_io->user,
    937                                             dest, &fd) != KIT_OK)
    938       continue;
    939     kit_blob_info(&info, fd.data, fd.size);
    940     if (build_id_eq(info.id, expected_blob) &&
    941         kit_cas_add_blob(c->cas, fd.data, fd.size, &info) == KIT_OK)
    942       installed = 1;
    943     if (c->host.cas_host->file_io->release)
    944       c->host.cas_host->file_io->release(c->host.cas_host->file_io->user, &fd);
    945     if (installed) break;
    946   }
    947 
    948   (void)c->host.store_io->remove(c->host.store_io->user,
    949                                  kit_slice_cstr(tmp_dir), 1);
    950   if (!installed) return BUILD_ERR;
    951   return kit_cas_blob_path(c->cas, expected_blob, path_out, path_cap) == KIT_OK
    952              ? BUILD_OK
    953              : BUILD_ERR;
    954 }
    955 
    956 int build_coord_glob(KitBuildCoordinator* c, KitSlice pattern,
    957                      uint8_t out_result_hash[BUILD_HASH_LEN],
    958                      BuildCoordGlobFn cb, void* cb_user) {
    959   return build_coord_glob_root(c, c ? c->workspace_root : NULL, pattern,
    960                                out_result_hash, cb, cb_user);
    961 }
    962 
    963 static int build_coord_glob_root(KitBuildCoordinator* c, const char* root,
    964                                  KitSlice pattern,
    965                                  uint8_t out_result_hash[BUILD_HASH_LEN],
    966                                  BuildCoordGlobFn cb, void* cb_user) {
    967   BuildGlobExpand g;
    968   size_t i, out_n = 0;
    969   char pat[BUILD_PATTERN_MAX];
    970   BuildGlobMemo* memo;
    971   BuildGlobMemo* fresh = NULL;
    972   if (!c || !root || !out_result_hash || !glob_pattern_safe(pattern) ||
    973       !c->host.cas_host || !c->host.cas_host->walk_regular_files)
    974     return BUILD_ERR;
    975   if (slice_copy(pat, sizeof pat, pattern) != BUILD_OK) return BUILD_ERR;
    976   for (memo = c->globs; memo; memo = memo->next) {
    977     if (strcmp(memo->root, root) == 0 && strcmp(memo->pattern, pat) == 0) {
    978       memcpy(out_result_hash, memo->result_hash, BUILD_HASH_LEN);
    979       if (cb) {
    980         for (i = 0; i < memo->n_entries; ++i) {
    981           if (cb(cb_user, memo->entries[i].path, memo->entries[i].blob)) break;
    982         }
    983       }
    984       return BUILD_OK;
    985     }
    986   }
    987   memset(&g, 0, sizeof g);
    988   g.c = c;
    989   g.root = root;
    990   g.pattern = pattern;
    991   if (c->host.cas_host->walk_regular_files(c->host.cas_host->user,
    992                                            root, glob_walk_cb,
    993                                            &g) != 0 ||
    994       g.failed)
    995     goto err;
    996   if (g.n) qsort(g.entries, g.n, sizeof *g.entries, path_blob_cmp_qsort);
    997   for (i = 0; i < g.n; ++i) {
    998     if (out_n && strcmp(g.entries[out_n - 1u].path, g.entries[i].path) == 0) {
    999       if (!build_id_eq(g.entries[out_n - 1u].blob, g.entries[i].blob))
   1000         goto err;
   1001       continue;
   1002     }
   1003     if (out_n != i) g.entries[out_n] = g.entries[i];
   1004     ++out_n;
   1005   }
   1006   if (build_glob_result_hash(c->ctx->heap, g.entries, out_n,
   1007                              out_result_hash) != BUILD_OK)
   1008     goto err;
   1009   fresh = (BuildGlobMemo*)c->ctx->heap->alloc(c->ctx->heap, sizeof *fresh,
   1010                                               _Alignof(BuildGlobMemo));
   1011   if (!fresh) goto err;
   1012   memset(fresh, 0, sizeof *fresh);
   1013   snprintf(fresh->root, sizeof fresh->root, "%s", root);
   1014   snprintf(fresh->pattern, sizeof fresh->pattern, "%s", pat);
   1015   memcpy(fresh->result_hash, out_result_hash, BUILD_HASH_LEN);
   1016   if (out_n) {
   1017     fresh->entries = (BuildPathBlob*)c->ctx->heap->alloc(
   1018         c->ctx->heap, out_n * sizeof *fresh->entries, _Alignof(BuildPathBlob));
   1019     if (!fresh->entries) goto err;
   1020     memcpy(fresh->entries, g.entries, out_n * sizeof *fresh->entries);
   1021     fresh->n_entries = out_n;
   1022   }
   1023   fresh->next = c->globs;
   1024   c->globs = fresh;
   1025   fresh = NULL;
   1026   if (cb) {
   1027     for (i = 0; i < out_n; ++i) {
   1028       if (cb(cb_user, g.entries[i].path, g.entries[i].blob)) break;
   1029     }
   1030   }
   1031   if (g.entries)
   1032     c->ctx->heap->free(c->ctx->heap, g.entries, g.cap * sizeof *g.entries);
   1033   return BUILD_OK;
   1034 
   1035 err:
   1036   if (fresh) {
   1037     if (fresh->entries)
   1038       c->ctx->heap->free(c->ctx->heap, fresh->entries,
   1039                          fresh->n_entries * sizeof *fresh->entries);
   1040     c->ctx->heap->free(c->ctx->heap, fresh, sizeof *fresh);
   1041   }
   1042   if (g.entries)
   1043     c->ctx->heap->free(c->ctx->heap, g.entries, g.cap * sizeof *g.entries);
   1044   return BUILD_ERR;
   1045 }
   1046 
   1047 int build_coord_glob_target(KitBuildCoordinator* c, KitSlice target,
   1048                             KitSlice pattern,
   1049                             uint8_t out_result_hash[BUILD_HASH_LEN],
   1050                             BuildCoordGlobFn cb, void* cb_user) {
   1051   char repo[BUILD_KEY_MAX];
   1052   const char* root;
   1053   const char* def_name;
   1054   const BuildWorkspace* ws;
   1055   (void)def_name;
   1056   (void)ws;
   1057   if (target_workspace(c, target, repo, &root, &def_name, &ws) != BUILD_OK)
   1058     return BUILD_ERR;
   1059   return build_coord_glob_root(c, root, pattern, out_result_hash, cb, cb_user);
   1060 }
   1061 
   1062 int build_coord_config_by_id(KitBuildCoordinator* c,
   1063                              const uint8_t config_id[BUILD_HASH_LEN],
   1064                              BuildConfig* out) {
   1065   KitFileData fd;
   1066   int r;
   1067   if (!c || !config_id || !out) return BUILD_ERR;
   1068   fd.data = NULL;
   1069   fd.size = 0;
   1070   fd.token = NULL;
   1071   if (kit_cas_get_blob(c->cas, config_id, &fd) != KIT_OK) return BUILD_ERR;
   1072   r = build_config_parse(fd.data, fd.size, out, NULL, 0);
   1073   kit_cas_release(c->cas, &fd);
   1074   return r;
   1075 }
   1076 
   1077 int build_coord_argv_by_id(KitBuildCoordinator* c,
   1078                            const uint8_t argv_id[BUILD_HASH_LEN],
   1079                            BuildArgv* out) {
   1080   KitFileData fd;
   1081   int r;
   1082   if (!c || !argv_id || !out) return BUILD_ERR;
   1083   fd.data = NULL;
   1084   fd.size = 0;
   1085   fd.token = NULL;
   1086   if (kit_cas_get_blob(c->cas, argv_id, &fd) != KIT_OK) return BUILD_ERR;
   1087   r = build_argv_parse(fd.data, fd.size, out, NULL, 0);
   1088   kit_cas_release(c->cas, &fd);
   1089   return r;
   1090 }
   1091 
   1092 int build_coord_top_config(KitBuildCoordinator* c, const KitBuildKV* overrides,
   1093                            size_t noverrides, BuildConfig* out) {
   1094   char err[160];
   1095   if (!c || !out) return BUILD_ERR;
   1096   if (build_workspace_config_apply(&c->root_workspace, c->opts.profile,
   1097                                    overrides, noverrides, out, err,
   1098                                    sizeof err) != BUILD_OK) {
   1099     build_diagf(c->ctx, "build: %s", err);
   1100     return BUILD_ERR;
   1101   }
   1102   return BUILD_OK;
   1103 }
   1104 
   1105 int build_coord_canonical_target(KitBuildCoordinator* c, KitSlice label,
   1106                                  KitSlice current_repo,
   1107                                  KitSlice current_package,
   1108                                  char out[BUILD_TARGET_MAX]) {
   1109   char err[128];
   1110   if (!c || !out) return BUILD_ERR;
   1111   if (build_target_canonicalize(label, current_repo, current_package, out, err,
   1112                                 sizeof err) != BUILD_OK) {
   1113     build_diagf(c->ctx, "build: %s: %.*s", err, KIT_SLICE_ARG(label));
   1114     return BUILD_ERR;
   1115   }
   1116   return BUILD_OK;
   1117 }
   1118 
   1119 static int package_def_path(const char* root, const char* def_name,
   1120                             const char* package, char out[BUILD_PATH_MAX]) {
   1121   char rel[BUILD_PATH_MAX];
   1122   if (!root || !def_name || !package || !out) return BUILD_ERR;
   1123   if (package[0]) {
   1124     if (path_join2(rel, sizeof rel, package, def_name) != BUILD_OK)
   1125       return BUILD_ERR;
   1126   } else {
   1127     if (path_set(rel, sizeof rel, kit_slice_cstr(def_name)) !=
   1128         BUILD_OK)
   1129       return BUILD_ERR;
   1130   }
   1131   return path_join2(out, BUILD_PATH_MAX, root, rel);
   1132 }
   1133 
   1134 static void release_file_data(KitBuildCoordinator* c, KitFileData* fd) {
   1135   if (!c || !fd) return;
   1136   if (fd->data && c->host.cas_host && c->host.cas_host->file_io &&
   1137       c->host.cas_host->file_io->release)
   1138     c->host.cas_host->file_io->release(c->host.cas_host->file_io->user, fd);
   1139   fd->data = NULL;
   1140   fd->size = 0;
   1141   fd->token = NULL;
   1142 }
   1143 
   1144 static int read_package_defn(KitBuildCoordinator* c, const char* root,
   1145                              const char* def_name, const char* package,
   1146                              BuildDefn* defn, KitFileData* fd, char* err,
   1147                              size_t errcap) {
   1148   char path[BUILD_PATH_MAX];
   1149   BuildTargetDefn* targets = NULL;
   1150   BuildDefaultDefn* defaults = NULL;
   1151   BuildRuleDefn* rules = NULL;
   1152   KitHeap* h;
   1153   enum { MAX_TARGETS = 256, MAX_DEFAULTS = 64, MAX_RULES = 128 };
   1154   if (!c || !defn || !fd) return BUILD_ERR;
   1155   h = c->ctx->heap;
   1156   memset(defn, 0, sizeof *defn);
   1157   memset(fd, 0, sizeof *fd);
   1158   targets = (BuildTargetDefn*)h->alloc(h, MAX_TARGETS * sizeof *targets,
   1159                                        _Alignof(BuildTargetDefn));
   1160   defaults = (BuildDefaultDefn*)h->alloc(h, MAX_DEFAULTS * sizeof *defaults,
   1161                                          _Alignof(BuildDefaultDefn));
   1162   rules = (BuildRuleDefn*)h->alloc(h, MAX_RULES * sizeof *rules,
   1163                                    _Alignof(BuildRuleDefn));
   1164   if (!targets || !defaults || !rules) goto err;
   1165   if (package_def_path(root, def_name, package, path) != BUILD_OK) goto err;
   1166   if (c->host.cas_host->file_io->read_all(c->host.cas_host->file_io->user, path,
   1167                                           fd) != KIT_OK) {
   1168     if (err && errcap) snprintf(err, errcap, "failed to read package %s", path);
   1169     goto err;
   1170   }
   1171   defn->targets = targets;
   1172   defn->cap_targets = MAX_TARGETS;
   1173   defn->defaults = defaults;
   1174   defn->cap_defaults = MAX_DEFAULTS;
   1175   defn->rules = rules;
   1176   defn->cap_rules = MAX_RULES;
   1177   if (build_defn_parse(fd->data, fd->size, defn, err, errcap) != BUILD_OK)
   1178     goto err;
   1179   return BUILD_OK;
   1180 
   1181 err:
   1182   release_file_data(c, fd);
   1183   if (rules) h->free(h, rules, MAX_RULES * sizeof *rules);
   1184   if (defaults) h->free(h, defaults, MAX_DEFAULTS * sizeof *defaults);
   1185   if (targets) h->free(h, targets, MAX_TARGETS * sizeof *targets);
   1186   memset(defn, 0, sizeof *defn);
   1187   return BUILD_ERR;
   1188 }
   1189 
   1190 static void release_package_defn(KitBuildCoordinator* c, BuildDefn* defn,
   1191                                  KitFileData* fd) {
   1192   KitHeap* h;
   1193   if (!c || !defn) return;
   1194   h = c->ctx->heap;
   1195   release_file_data(c, fd);
   1196   if (defn->rules)
   1197     h->free(h, defn->rules, defn->cap_rules * sizeof *defn->rules);
   1198   if (defn->defaults)
   1199     h->free(h, defn->defaults,
   1200             defn->cap_defaults * sizeof *defn->defaults);
   1201   if (defn->targets)
   1202     h->free(h, defn->targets, defn->cap_targets * sizeof *defn->targets);
   1203   memset(defn, 0, sizeof *defn);
   1204 }
   1205 
   1206 static int file_present(KitBuildCoordinator* c, const char* root,
   1207                         const char* rel) {
   1208   char full[BUILD_PATH_MAX];
   1209   KitFileData fd;
   1210   if (!c || !root || !rel) return 0;
   1211   memset(&fd, 0, sizeof fd);
   1212   if (path_join2(full, sizeof full, root, rel) != BUILD_OK)
   1213     return 0;
   1214   if (c->host.cas_host->file_io->read_all(c->host.cas_host->file_io->user, full,
   1215                                           &fd) != KIT_OK)
   1216     return 0;
   1217   release_file_data(c, &fd);
   1218   return 1;
   1219 }
   1220 
   1221 static int join_rel2(char* out, size_t cap, const char* a, const char* b) {
   1222   if (!a || !a[0]) return path_set(out, cap, kit_slice_cstr(b));
   1223   return path_join2(out, cap, a, b);
   1224 }
   1225 
   1226 static int redo_candidate_at(KitBuildCoordinator* c, const char* workspace_root,
   1227                              const char* root, const char* local,
   1228                              char out[BUILD_PATH_MAX]) {
   1229   char cand[BUILD_PATH_MAX];
   1230   char leaf[BUILD_PATH_MAX];
   1231   size_t i;
   1232   if (snprintf(leaf, sizeof leaf, "%s.do", local) >= (int)sizeof leaf)
   1233     return BUILD_ERR;
   1234   if (join_rel2(cand, sizeof cand, root, leaf) == BUILD_OK &&
   1235       file_present(c, workspace_root, cand)) {
   1236     snprintf(out, BUILD_PATH_MAX, "%s", cand);
   1237     return BUILD_OK;
   1238   }
   1239   for (i = 0; local[i]; ++i) {
   1240     if (local[i] != '.') continue;
   1241     if (snprintf(leaf, sizeof leaf, "default%s.do", local + i) >=
   1242         (int)sizeof leaf)
   1243       return BUILD_ERR;
   1244     if (join_rel2(cand, sizeof cand, root, leaf) == BUILD_OK &&
   1245         file_present(c, workspace_root, cand)) {
   1246       snprintf(out, BUILD_PATH_MAX, "%s", cand);
   1247       return BUILD_OK;
   1248     }
   1249   }
   1250   if (join_rel2(cand, sizeof cand, root, "default.do") == BUILD_OK &&
   1251       file_present(c, workspace_root, cand)) {
   1252     snprintf(out, BUILD_PATH_MAX, "%s", cand);
   1253     return BUILD_OK;
   1254   }
   1255   return BUILD_ERR;
   1256 }
   1257 
   1258 static int redo_try_search(KitBuildCoordinator* c, const char* workspace_root,
   1259                            const char* ancestor, const char* search,
   1260                            const char* local, char out[BUILD_PATH_MAX]) {
   1261   char root[BUILD_PATH_MAX];
   1262   if (!search || strcmp(search, ".") == 0) {
   1263     snprintf(root, sizeof root, "%s", ancestor ? ancestor : "");
   1264   } else if (!ancestor || !ancestor[0]) {
   1265     if (path_set(root, sizeof root, kit_slice_cstr(search)) != BUILD_OK)
   1266       return BUILD_ERR;
   1267   } else if (path_join2(root, sizeof root, ancestor, search) != BUILD_OK) {
   1268     return BUILD_ERR;
   1269   }
   1270   return redo_candidate_at(c, workspace_root, root, local, out);
   1271 }
   1272 
   1273 static int parent_package(char pkg[BUILD_PATH_MAX]) {
   1274   size_t n;
   1275   if (!pkg || !pkg[0]) return 0;
   1276   n = strlen(pkg);
   1277   while (n > 0u && pkg[n - 1u] != '/') --n;
   1278   if (n == 0u) {
   1279     pkg[0] = '\0';
   1280   } else {
   1281     pkg[n - 1u] = '\0';
   1282   }
   1283   return 1;
   1284 }
   1285 
   1286 static int resolve_redo(KitBuildCoordinator* c, const char* workspace_root,
   1287                         const BuildDefn* defn, const char* package,
   1288                         const char* local, char out[BUILD_PATH_MAX]) {
   1289   char ancestor[BUILD_PATH_MAX];
   1290   size_t i;
   1291   if (!defn || !defn->redo.enabled) return BUILD_ERR;
   1292   snprintf(ancestor, sizeof ancestor, "%s", package ? package : "");
   1293   for (;;) {
   1294     for (i = 0; i < defn->redo.n_search; ++i) {
   1295       if (redo_try_search(c, workspace_root, ancestor, defn->redo.search[i],
   1296                           local, out) == BUILD_OK)
   1297         return BUILD_OK;
   1298     }
   1299     if (!defn->redo.walk_parents || !parent_package(ancestor)) break;
   1300   }
   1301   return BUILD_ERR;
   1302 }
   1303 
   1304 int build_coord_resolve_recipe(KitBuildCoordinator* c, KitSlice canonical_target,
   1305                                BuildRecipeResolution* out) {
   1306   BuildDefn defn;
   1307   KitFileData fd;
   1308   char repo[BUILD_KEY_MAX];
   1309   char package[BUILD_PATH_MAX];
   1310   char local[BUILD_TARGET_MAX];
   1311   char err[160];
   1312   const char* workspace_root;
   1313   const char* def_name;
   1314   const BuildWorkspace* ws;
   1315   const BuildTargetDefn* target;
   1316   const BuildRuleDefn* rule = NULL;
   1317   const BuildDefaultDefn* def = NULL;
   1318   const char* type = NULL;
   1319   const char* recipe = NULL;
   1320   char redo_recipe[BUILD_PATH_MAX];
   1321   int ambiguous = 0;
   1322   int recipe_workspace_rel = 0;
   1323   int ok = BUILD_ERR;
   1324 
   1325   if (!c || !out ||
   1326       build_target_split_repo(canonical_target, repo, package, local) !=
   1327           BUILD_OK)
   1328     return BUILD_ERR;
   1329   if (target_workspace(c, canonical_target, repo, &workspace_root, &def_name,
   1330                        &ws) != BUILD_OK)
   1331     return BUILD_ERR;
   1332   (void)ws;
   1333   memset(out, 0, sizeof *out);
   1334   if (read_package_defn(c, workspace_root, def_name, package, &defn, &fd, err,
   1335                         sizeof err) != BUILD_OK) {
   1336     build_diagf(c->ctx, "build: %s", err);
   1337     return BUILD_ERR;
   1338   }
   1339 
   1340   target = build_defn_find(&defn, kit_slice_cstr(local));
   1341   if (target) {
   1342     if (target->has_recipe) recipe = target->recipe_path;
   1343     if (target->has_type) type = target->type;
   1344   } else {
   1345     rule = build_defn_rule_match(&defn, kit_slice_cstr(local), &ambiguous);
   1346     if (ambiguous) {
   1347       build_diagf(c->ctx, "build: ambiguous rules for %.*s",
   1348                   KIT_SLICE_ARG(canonical_target));
   1349       goto out_release;
   1350     }
   1351     if (rule) {
   1352       if (rule->has_recipe) recipe = rule->recipe_path;
   1353       if (rule->has_type) type = rule->type;
   1354     }
   1355   }
   1356   if (!recipe &&
   1357       resolve_redo(c, workspace_root, &defn, package, local, redo_recipe) ==
   1358           BUILD_OK) {
   1359     recipe = redo_recipe;
   1360     recipe_workspace_rel = 1;
   1361   }
   1362   if (!recipe && type) {
   1363     def = build_defn_default_find(&defn, kit_slice_cstr(type));
   1364     if (def) recipe = def->recipe_path;
   1365   }
   1366   if (!recipe) {
   1367     build_diagf(c->ctx, "build: unknown target %.*s",
   1368                 KIT_SLICE_ARG(canonical_target));
   1369     goto out_release;
   1370   }
   1371   if ((recipe_workspace_rel
   1372            ? path_set(out->recipe_relpath, sizeof out->recipe_relpath,
   1373                       kit_slice_cstr(recipe))
   1374            : build_recipe_path_resolve(kit_slice_cstr(package),
   1375                                        kit_slice_cstr(recipe),
   1376                                        out->recipe_relpath)) != BUILD_OK ||
   1377       path_join2(out->recipe_abspath, sizeof out->recipe_abspath,
   1378                  workspace_root, out->recipe_relpath) != BUILD_OK)
   1379     goto out_release;
   1380   snprintf(out->canonical_target, sizeof out->canonical_target, "%.*s",
   1381            KIT_SLICE_ARG(canonical_target));
   1382   snprintf(out->repo, sizeof out->repo, "%s", repo);
   1383   snprintf(out->package, sizeof out->package, "%s", package);
   1384   snprintf(out->local_name, sizeof out->local_name, "%s", local);
   1385   snprintf(out->workspace_root, sizeof out->workspace_root, "%s",
   1386            workspace_root);
   1387   ok = BUILD_OK;
   1388 
   1389 out_release:
   1390   release_package_defn(c, &defn, &fd);
   1391   return ok;
   1392 }
   1393 
   1394 int build_coord_recipe_id(KitBuildCoordinator* c, KitSlice target,
   1395                           uint8_t out[BUILD_HASH_LEN]) {
   1396   BuildRecipeResolution r;
   1397   KitFileData fd;
   1398   KitBlobInfo info;
   1399   if (!c || !out) return BUILD_ERR;
   1400   if (build_coord_resolve_recipe(c, target, &r) != BUILD_OK)
   1401     return BUILD_ERR;
   1402   fd.data = NULL;
   1403   fd.size = 0;
   1404   fd.token = NULL;
   1405   if (c->host.cas_host->file_io->read_all(c->host.cas_host->file_io->user,
   1406                                           r.recipe_abspath, &fd) != KIT_OK)
   1407     return BUILD_ERR;
   1408   if (kit_cas_add_blob(c->cas, fd.data, fd.size, &info) != KIT_OK) {
   1409     if (c->host.cas_host->file_io->release)
   1410       c->host.cas_host->file_io->release(c->host.cas_host->file_io->user, &fd);
   1411     return BUILD_ERR;
   1412   }
   1413   memcpy(out, info.id, BUILD_HASH_LEN);
   1414   if (c->host.cas_host->file_io->release)
   1415     c->host.cas_host->file_io->release(c->host.cas_host->file_io->user, &fd);
   1416   return BUILD_OK;
   1417 }
   1418 
   1419 int build_coord_leafset_intern(KitBuildCoordinator* c, const BuildLeafSet* in,
   1420                                const BuildLeafSet** out) {
   1421   BuildDeepSetMemo* n;
   1422   if (!c || !in || !out) return BUILD_ERR;
   1423   for (n = c->deepsets; n; n = n->next) {
   1424     if (build_id_eq(n->leaf.id, in->id)) {
   1425       *out = &n->leaf;
   1426       return BUILD_OK;
   1427     }
   1428   }
   1429   n = (BuildDeepSetMemo*)c->ctx->heap->alloc(c->ctx->heap, sizeof *n,
   1430                                              _Alignof(BuildDeepSetMemo));
   1431   if (!n) return BUILD_ERR;
   1432   memset(n, 0, sizeof *n);
   1433   n->leaf = *in;
   1434   if (in->n_configs) {
   1435     n->configs = (BuildConfigLeaf*)c->ctx->heap->alloc(
   1436         c->ctx->heap, in->n_configs * sizeof *n->configs,
   1437         _Alignof(BuildConfigLeaf));
   1438     if (!n->configs) goto oom;
   1439     memcpy(n->configs, in->configs, in->n_configs * sizeof *n->configs);
   1440   }
   1441   if (in->n_sources) {
   1442     n->sources = (BuildSourceLeaf*)c->ctx->heap->alloc(
   1443         c->ctx->heap, in->n_sources * sizeof *n->sources,
   1444         _Alignof(BuildSourceLeaf));
   1445     if (!n->sources) goto oom;
   1446     memcpy(n->sources, in->sources, in->n_sources * sizeof *n->sources);
   1447   }
   1448   if (in->n_globs) {
   1449     n->globs = (BuildGlobLeaf*)c->ctx->heap->alloc(
   1450         c->ctx->heap, in->n_globs * sizeof *n->globs, _Alignof(BuildGlobLeaf));
   1451     if (!n->globs) goto oom;
   1452     memcpy(n->globs, in->globs, in->n_globs * sizeof *n->globs);
   1453   }
   1454   if (in->n_blobs) {
   1455     n->blobs = (BuildBlobLeaf*)c->ctx->heap->alloc(
   1456         c->ctx->heap, in->n_blobs * sizeof *n->blobs,
   1457         _Alignof(BuildBlobLeaf));
   1458     if (!n->blobs) goto oom;
   1459     memcpy(n->blobs, in->blobs, in->n_blobs * sizeof *n->blobs);
   1460   }
   1461   if (in->n_children) {
   1462     n->children = (const BuildLeafSet**)c->ctx->heap->alloc(
   1463         c->ctx->heap, in->n_children * sizeof *n->children,
   1464         _Alignof(const BuildLeafSet*));
   1465     if (!n->children) goto oom;
   1466     memcpy(n->children, in->children, in->n_children * sizeof *n->children);
   1467   }
   1468   n->leaf.configs = n->configs;
   1469   n->leaf.sources = n->sources;
   1470   n->leaf.globs = n->globs;
   1471   n->leaf.blobs = n->blobs;
   1472   n->leaf.children = n->children;
   1473   n->next = c->deepsets;
   1474   c->deepsets = n;
   1475   *out = &n->leaf;
   1476   return BUILD_OK;
   1477 
   1478 oom:
   1479   if (n->configs)
   1480     c->ctx->heap->free(c->ctx->heap, n->configs,
   1481                        in->n_configs * sizeof *n->configs);
   1482   if (n->sources)
   1483     c->ctx->heap->free(c->ctx->heap, n->sources,
   1484                        in->n_sources * sizeof *n->sources);
   1485   if (n->globs)
   1486     c->ctx->heap->free(c->ctx->heap, n->globs,
   1487                        in->n_globs * sizeof *n->globs);
   1488   if (n->blobs)
   1489     c->ctx->heap->free(c->ctx->heap, n->blobs,
   1490                        in->n_blobs * sizeof *n->blobs);
   1491   if (n->children)
   1492     c->ctx->heap->free(c->ctx->heap, n->children,
   1493                        in->n_children * sizeof *n->children);
   1494   c->ctx->heap->free(c->ctx->heap, n, sizeof *n);
   1495   return BUILD_ERR;
   1496 }
   1497 
   1498 static void count_deepset_rows(const uint8_t* data, size_t len, size_t* ncfg,
   1499                                size_t* ns, size_t* ng, size_t* nf,
   1500                                size_t* nc) {
   1501   size_t pos = 0;
   1502   int sec = 0;
   1503   *ncfg = 0;
   1504   *ns = 0;
   1505   *ng = 0;
   1506   *nf = 0;
   1507   *nc = 0;
   1508   while (pos < len) {
   1509     size_t start = pos;
   1510     while (pos < len && data[pos] != '\n') ++pos;
   1511     if (pos == len) break;
   1512     if (pos > start) {
   1513       size_t n = pos - start;
   1514       if (n == sizeof("[config]") - 1u &&
   1515           memcmp(data + start, "[config]", n) == 0) {
   1516         sec = 1;
   1517       } else if (n == sizeof("[source]") - 1u &&
   1518           memcmp(data + start, "[source]", n) == 0) {
   1519         sec = 2;
   1520       } else if (n == sizeof("[glob]") - 1u &&
   1521                  memcmp(data + start, "[glob]", n) == 0) {
   1522         sec = 3;
   1523       } else if (n == sizeof("[child]") - 1u &&
   1524                  memcmp(data + start, "[child]", n) == 0) {
   1525         sec = 4;
   1526       } else if (n == sizeof("[blob]") - 1u &&
   1527                  memcmp(data + start, "[blob]", n) == 0) {
   1528         sec = 5;
   1529       } else if (data[start] != '[' && data[start] != 'k') {
   1530         if (sec == 1) ++*ncfg;
   1531         if (sec == 2) ++*ns;
   1532         if (sec == 3) ++*ng;
   1533         if (sec == 4) ++*nc;
   1534         if (sec == 5) ++*nf;
   1535       }
   1536     }
   1537     ++pos;
   1538   }
   1539 }
   1540 
   1541 int build_coord_deepset_load(KitBuildCoordinator* c,
   1542                              const uint8_t deepset_id[BUILD_HASH_LEN],
   1543                              const BuildLeafSet** out) {
   1544   BuildDeepSetMemo* memo;
   1545   KitFileData fd;
   1546   BuildDeepSet ds;
   1547   BuildLeafSet leaf;
   1548   BuildConfigLeaf* configs = NULL;
   1549   BuildSourceLeaf* sources = NULL;
   1550   BuildGlobLeaf* globs = NULL;
   1551   BuildBlobLeaf* blobs = NULL;
   1552   uint8_t(*child_ids)[BUILD_HASH_LEN] = NULL;
   1553   const BuildLeafSet** children = NULL;
   1554   size_t ncfg, ns, ng, nf, nc, i;
   1555   int ok = BUILD_ERR;
   1556   if (!c || !deepset_id || !out) return BUILD_ERR;
   1557   for (memo = c->deepsets; memo; memo = memo->next) {
   1558     if (build_id_eq(memo->leaf.id, deepset_id)) {
   1559       *out = &memo->leaf;
   1560       return BUILD_OK;
   1561     }
   1562   }
   1563   fd.data = NULL;
   1564   fd.size = 0;
   1565   fd.token = NULL;
   1566   if (kit_cas_get_blob(c->cas, deepset_id, &fd) != KIT_OK) return BUILD_ERR;
   1567   count_deepset_rows(fd.data, fd.size, &ncfg, &ns, &ng, &nf, &nc);
   1568   if (ncfg) {
   1569     configs = (BuildConfigLeaf*)c->ctx->heap->alloc(
   1570         c->ctx->heap, ncfg * sizeof *configs, _Alignof(BuildConfigLeaf));
   1571     if (!configs) goto out_release;
   1572   }
   1573   if (ns) {
   1574     sources = (BuildSourceLeaf*)c->ctx->heap->alloc(
   1575         c->ctx->heap, ns * sizeof *sources, _Alignof(BuildSourceLeaf));
   1576     if (!sources) goto out_release;
   1577   }
   1578   if (ng) {
   1579     globs = (BuildGlobLeaf*)c->ctx->heap->alloc(
   1580         c->ctx->heap, ng * sizeof *globs, _Alignof(BuildGlobLeaf));
   1581     if (!globs) goto out_release;
   1582   }
   1583   if (nf) {
   1584     blobs = (BuildBlobLeaf*)c->ctx->heap->alloc(
   1585         c->ctx->heap, nf * sizeof *blobs, _Alignof(BuildBlobLeaf));
   1586     if (!blobs) goto out_release;
   1587   }
   1588   if (nc) {
   1589     child_ids = (uint8_t(*)[BUILD_HASH_LEN])c->ctx->heap->alloc(
   1590         c->ctx->heap, nc * sizeof *child_ids, _Alignof(uint8_t));
   1591     children = (const BuildLeafSet**)c->ctx->heap->alloc(
   1592         c->ctx->heap, nc * sizeof *children, _Alignof(const BuildLeafSet*));
   1593     if (!child_ids || !children) goto out_release;
   1594   }
   1595   memset(&ds, 0, sizeof ds);
   1596   ds.configs = configs;
   1597   ds.cap_configs = ncfg;
   1598   ds.sources = sources;
   1599   ds.cap_sources = ns;
   1600   ds.globs = globs;
   1601   ds.cap_globs = ng;
   1602   ds.blobs = blobs;
   1603   ds.cap_blobs = nf;
   1604   ds.children = child_ids;
   1605   ds.cap_children = nc;
   1606   if (build_deepset_parse(fd.data, fd.size, &ds, NULL, 0) != BUILD_OK)
   1607     goto out_release;
   1608   for (i = 0; i < ds.n_children; ++i) {
   1609     if (build_coord_deepset_load(c, ds.children[i], &children[i]) != BUILD_OK)
   1610       goto out_release;
   1611   }
   1612   memset(&leaf, 0, sizeof leaf);
   1613   memcpy(leaf.id, deepset_id, BUILD_HASH_LEN);
   1614   memcpy(leaf.recipe, ds.recipe, BUILD_HASH_LEN);
   1615   snprintf(leaf.target, sizeof leaf.target, "%s", ds.target);
   1616   leaf.configs = configs;
   1617   leaf.n_configs = ds.n_configs;
   1618   leaf.sources = sources;
   1619   leaf.n_sources = ds.n_sources;
   1620   leaf.globs = globs;
   1621   leaf.n_globs = ds.n_globs;
   1622   leaf.blobs = blobs;
   1623   leaf.n_blobs = ds.n_blobs;
   1624   leaf.children = children;
   1625   leaf.n_children = ds.n_children;
   1626   if (build_coord_leafset_intern(c, &leaf, out) != BUILD_OK) goto out_release;
   1627   ok = BUILD_OK;
   1628 
   1629 out_release:
   1630   kit_cas_release(c->cas, &fd);
   1631   if (configs)
   1632     c->ctx->heap->free(c->ctx->heap, configs, ncfg * sizeof *configs);
   1633   if (sources)
   1634     c->ctx->heap->free(c->ctx->heap, sources, ns * sizeof *sources);
   1635   if (globs) c->ctx->heap->free(c->ctx->heap, globs, ng * sizeof *globs);
   1636   if (blobs)
   1637     c->ctx->heap->free(c->ctx->heap, blobs, nf * sizeof *blobs);
   1638   if (child_ids)
   1639     c->ctx->heap->free(c->ctx->heap, child_ids, nc * sizeof *child_ids);
   1640   if (children)
   1641     c->ctx->heap->free(c->ctx->heap, children, nc * sizeof *children);
   1642   return ok;
   1643 }
   1644 
   1645 int build_coord_deepset_valid_get(KitBuildCoordinator* c,
   1646                                   const uint8_t deepset_id[BUILD_HASH_LEN],
   1647                                   int* known, int* valid) {
   1648   BuildDeepSetMemo* n;
   1649   if (known) *known = 0;
   1650   if (valid) *valid = 0;
   1651   if (!c || !deepset_id) return BUILD_ERR;
   1652   for (n = c->deepsets; n; n = n->next) {
   1653     if (build_id_eq(n->leaf.id, deepset_id)) {
   1654       if (known) *known = n->valid_known;
   1655       if (valid) *valid = n->valid;
   1656       return BUILD_OK;
   1657     }
   1658   }
   1659   return BUILD_OK;
   1660 }
   1661 
   1662 void build_coord_deepset_valid_set(KitBuildCoordinator* c,
   1663                                    const uint8_t deepset_id[BUILD_HASH_LEN],
   1664                                    int valid) {
   1665   BuildDeepSetMemo* n;
   1666   if (!c || !deepset_id) return;
   1667   for (n = c->deepsets; n; n = n->next) {
   1668     if (build_id_eq(n->leaf.id, deepset_id)) {
   1669       n->valid_known = 1;
   1670       n->valid = valid ? 1 : 0;
   1671       return;
   1672     }
   1673   }
   1674 }
   1675 
   1676 int build_coord_trace_remote_pull_once(KitBuildCoordinator* c, KitSlice target,
   1677                                        int* pulled_now) {
   1678   BuildPulledSet* n;
   1679   if (!c || !pulled_now) return BUILD_ERR;
   1680   *pulled_now = 0;
   1681   if (!c->opts.n_trace_remotes) return BUILD_OK;
   1682   if (!target.s || target.len == 0u || target.len >= BUILD_TARGET_MAX)
   1683     return BUILD_ERR;
   1684   for (n = c->pulled; n; n = n->next) {
   1685     if (strlen(n->target) == target.len &&
   1686         memcmp(n->target, target.s, target.len) == 0)
   1687       return BUILD_OK;
   1688   }
   1689   n = (BuildPulledSet*)c->ctx->heap->alloc(c->ctx->heap, sizeof *n,
   1690                                            _Alignof(BuildPulledSet));
   1691   if (!n) return BUILD_ERR;
   1692   memset(n, 0, sizeof *n);
   1693   memcpy(n->target, target.s, target.len);
   1694   n->target[target.len] = '\0';
   1695   n->next = c->pulled;
   1696   c->pulled = n;
   1697   if (build_trace_remote_pull(c, target) == BUILD_OK) {
   1698     *pulled_now = 1;
   1699     build_coord_stat_bump(c, BUILD_STAT_TRACE_PULL);
   1700     return BUILD_OK;
   1701   }
   1702   return BUILD_OK;
   1703 }
   1704 
   1705 void build_coord_jobs_acquire(KitBuildCoordinator* c) { (void)c; }
   1706 void build_coord_jobs_release(KitBuildCoordinator* c) { (void)c; }
   1707 
   1708 int build_coord_spawn(KitBuildCoordinator* c, void (*fn)(void*), void* arg) {
   1709   if (!c || !c->host.sched || !c->host.sched->thread_spawn || !fn)
   1710     return BUILD_ERR;
   1711   return c->host.sched->thread_spawn(c->host.sched->user, fn, arg, NULL) == 0
   1712              ? BUILD_OK
   1713              : BUILD_ERR;
   1714 }
   1715 
   1716 int build_coord_target_intern(KitBuildCoordinator* c, KitSlice target,
   1717                               const uint8_t config_id[BUILD_HASH_LEN],
   1718                               const uint8_t argv_id[BUILD_HASH_LEN],
   1719                               BuildTargetFuture** out, int* is_fresh) {
   1720   BuildTargetTable* t;
   1721   BuildTargetFuture* f;
   1722   if (!c || !out || !is_fresh || !target.s || target.len == 0u ||
   1723       target.len >= BUILD_TARGET_MAX || !config_id || !argv_id)
   1724     return BUILD_ERR;
   1725   if (!c->targets) {
   1726     c->targets = (BuildTargetTable*)c->ctx->heap->alloc(
   1727         c->ctx->heap, sizeof *c->targets, _Alignof(BuildTargetTable));
   1728     if (!c->targets) return BUILD_ERR;
   1729     memset(c->targets, 0, sizeof *c->targets);
   1730   }
   1731   t = c->targets;
   1732   for (f = t->futures; f; f = f->next) {
   1733     if (strlen(f->target) == target.len &&
   1734         memcmp(f->target, target.s, target.len) == 0 &&
   1735         build_id_eq(f->config_id, config_id) &&
   1736         build_id_eq(f->argv_id, argv_id)) {
   1737       *out = f;
   1738       *is_fresh = 0;
   1739       return BUILD_OK;
   1740     }
   1741   }
   1742   f = (BuildTargetFuture*)c->ctx->heap->alloc(c->ctx->heap, sizeof *f,
   1743                                               _Alignof(BuildTargetFuture));
   1744   if (!f) return BUILD_ERR;
   1745   memset(f, 0, sizeof *f);
   1746   memcpy(f->target, target.s, target.len);
   1747   f->target[target.len] = '\0';
   1748   memcpy(f->config_id, config_id, BUILD_HASH_LEN);
   1749   memcpy(f->argv_id, argv_id, BUILD_HASH_LEN);
   1750   f->next = t->futures;
   1751   t->futures = f;
   1752   *out = f;
   1753   *is_fresh = 1;
   1754   return BUILD_OK;
   1755 }
   1756 
   1757 int build_coord_target_await(KitBuildCoordinator* c, BuildTargetFuture* f,
   1758                              BuildResolved* out) {
   1759   (void)c;
   1760   if (!f || !out || !f->done || f->failed) return BUILD_ERR;
   1761   *out = f->result;
   1762   return BUILD_OK;
   1763 }
   1764 
   1765 void build_coord_target_complete(KitBuildCoordinator* c, BuildTargetFuture* f,
   1766                                  const BuildResolved* r) {
   1767   (void)c;
   1768   if (!f || !r) return;
   1769   f->result = *r;
   1770   f->done = 1;
   1771   f->failed = 0;
   1772 }
   1773 
   1774 void build_coord_target_fail(KitBuildCoordinator* c, BuildTargetFuture* f) {
   1775   (void)c;
   1776   if (!f) return;
   1777   f->done = 1;
   1778   f->failed = 1;
   1779 }