kit

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

release.c (12550B)


      1 #include "release.h"
      2 
      3 #include <stdio.h>
      4 #include <string.h>
      5 
      6 #include "dist_parse.h"
      7 
      8 /* The `kit-release 1` channel index: byte-stable canonical text in the package
      9  * manifest idiom (src/dist/manifest.c). Top-level keys are emitted in the fixed
     10  * order channel/version/hash; each [host] block is emitted sorted ascending by
     11  * target, with keys target/kpkg/targz/size then the url mirror list in stored
     12  * order. Operates directly on the public KitReleaseIndex. */
     13 
     14 _Static_assert(KIT_CAS_HASH_LEN == DIST_BLAKE2B_LEN, "release hash len");
     15 
     16 static int emit(KitWriter* out, const char* s) {
     17   return kit_writer_write(out, s, strlen(s)) == KIT_OK ? DIST_OK : DIST_ERR;
     18 }
     19 
     20 static int emit_hex(KitWriter* out, const char* key,
     21                     const uint8_t h[DIST_BLAKE2B_LEN]) {
     22   char hex[2 * DIST_BLAKE2B_LEN + 1];
     23   dist_hex_encode(hex, h, DIST_BLAKE2B_LEN);
     24   return dist_emit_kv(out, key, hex);
     25 }
     26 
     27 static int emit_u64(KitWriter* out, const char* key, uint64_t v) {
     28   char num[24];
     29   snprintf(num, sizeof num, "%llu", (unsigned long long)v);
     30   return dist_emit_kv(out, key, num);
     31 }
     32 
     33 /* Per-host seen-key bits for the strict parser. */
     34 #define R1_F_TARGET 0x00000001u
     35 #define R1_F_KPKG 0x00000002u
     36 #define R1_F_TARGZ 0x00000004u
     37 #define R1_F_SIZE 0x00000008u
     38 
     39 /* Top-level seen-key bits. */
     40 #define R1_T_CHANNEL 0x00000001u
     41 #define R1_T_VERSION 0x00000002u
     42 #define R1_T_HASH 0x00000004u
     43 
     44 typedef enum { R1_SEC_TOP, R1_SEC_HOST } ReleaseSection;
     45 
     46 static int field_text_valid(const char* s, int required) {
     47   if (required && !s[0]) return 0;
     48   for (; *s; ++s) {
     49     if (*s == '\n' || *s == '\r') return 0;
     50   }
     51   return 1;
     52 }
     53 
     54 /* A target triple must be non-empty, fit, and contain no whitespace. */
     55 static int target_text_valid(const char* s) {
     56   if (!s[0]) return 0;
     57   for (; *s; ++s) {
     58     if (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r') return 0;
     59   }
     60   return 1;
     61 }
     62 
     63 static int decode_id(uint8_t out[DIST_BLAKE2B_LEN], const char* val,
     64                      const char* err_msg, char* err, size_t errcap) {
     65   if (strlen(val) != 2u * DIST_BLAKE2B_LEN ||
     66       dist_hex_decode(out, val, DIST_BLAKE2B_LEN) != DIST_OK)
     67     return dist_set_err(err, errcap, err_msg);
     68   return DIST_OK;
     69 }
     70 
     71 static int seen_once(uint32_t* seen, uint32_t bit, const char* msg, char* err,
     72                      size_t errcap) {
     73   if (*seen & bit) return dist_set_err(err, errcap, msg);
     74   *seen |= bit;
     75   return DIST_OK;
     76 }
     77 
     78 /* Parse "YYYY.MINOR.PATCH" into three uint64 fields. Each field must be a
     79  * non-empty run of decimal digits with no overflow; exactly three fields,
     80  * dot-separated, are required. */
     81 static int calver_split(const char* s, uint64_t out[3]) {
     82   size_t field = 0;
     83   if (!s) return DIST_ERR;
     84   for (;;) {
     85     uint64_t v = 0;
     86     int digits = 0;
     87     if (field >= 3) return DIST_ERR;
     88     while (*s >= '0' && *s <= '9') {
     89       unsigned d = (unsigned)(*s - '0');
     90       if (v > (UINT64_MAX - (uint64_t)d) / 10u) return DIST_ERR;
     91       v = v * 10u + (uint64_t)d;
     92       ++digits;
     93       ++s;
     94     }
     95     if (!digits) return DIST_ERR;
     96     out[field++] = v;
     97     if (*s == '\0') break;
     98     if (*s != '.') return DIST_ERR;
     99     ++s;
    100   }
    101   return field == 3 ? DIST_OK : DIST_ERR;
    102 }
    103 
    104 int dist_calver_compare(const char* a, const char* b, int* cmp) {
    105   uint64_t fa[3], fb[3];
    106   size_t i;
    107   if (!cmp) return DIST_ERR;
    108   if (calver_split(a, fa) != DIST_OK || calver_split(b, fb) != DIST_OK)
    109     return DIST_ERR;
    110   for (i = 0; i < 3; ++i) {
    111     if (fa[i] != fb[i]) {
    112       *cmp = fa[i] < fb[i] ? -1 : 1;
    113       return DIST_OK;
    114     }
    115   }
    116   *cmp = 0;
    117   return DIST_OK;
    118 }
    119 
    120 int dist_release_index_validate(const KitReleaseIndex* idx, char* err,
    121                                 size_t errcap) {
    122   uint64_t ver[3];
    123   unsigned i, j;
    124 
    125   if (!field_text_valid(idx->channel, 1))
    126     return dist_set_err(err, errcap, "bad or empty channel");
    127   if (calver_split(idx->version, ver) != DIST_OK)
    128     return dist_set_err(err, errcap, "bad CalVer version");
    129   if (idx->n_hosts > KIT_RELEASE_MAX_HOSTS)
    130     return dist_set_err(err, errcap, "too many hosts");
    131 
    132   for (i = 0; i < idx->n_hosts; ++i) {
    133     const KitReleaseHost* h = &idx->hosts[i];
    134     if (!target_text_valid(h->target))
    135       return dist_set_err(err, errcap, "bad or empty host target");
    136     if (h->n_urls > KIT_RELEASE_MAX_URLS)
    137       return dist_set_err(err, errcap, "too many urls");
    138     for (j = 0; j < h->n_urls; ++j) {
    139       if (!field_text_valid(h->urls[j], 1))
    140         return dist_set_err(err, errcap, "bad or empty url");
    141     }
    142     /* Targets must be unique, but the in-memory array need not be pre-sorted:
    143      * emit canonicalizes the [host] order (and parse enforces ascending in the
    144      * file), so a caller may build hosts in any order. */
    145     for (j = 0; j < i; ++j) {
    146       if (strcmp(idx->hosts[j].target, h->target) == 0)
    147         return dist_set_err(err, errcap, "duplicate host target");
    148     }
    149   }
    150   return DIST_OK;
    151 }
    152 
    153 /* Stable ascending order over host targets without mutating the input: emit
    154  * iterates an index permutation. The host count is small (<= caps), so an
    155  * O(n^2) selection of the next-smallest target is fine and keeps the emit
    156  * order canonical even if the caller built the index unsorted. */
    157 static unsigned next_host(const KitReleaseIndex* idx, int* used) {
    158   unsigned i, best = idx->n_hosts;
    159   for (i = 0; i < idx->n_hosts; ++i) {
    160     if (used[i]) continue;
    161     if (best == idx->n_hosts ||
    162         strcmp(idx->hosts[i].target, idx->hosts[best].target) < 0)
    163       best = i;
    164   }
    165   return best;
    166 }
    167 
    168 int dist_release_index_emit(const KitReleaseIndex* idx, KitWriter* out) {
    169   int used[KIT_RELEASE_MAX_HOSTS];
    170   char err[128];
    171   unsigned k;
    172 
    173   if (dist_release_index_validate(idx, err, sizeof err) != DIST_OK)
    174     return DIST_ERR;
    175 
    176   if (emit(out, DIST_RELEASE1_MAGIC "\n") != DIST_OK) return DIST_ERR;
    177   if (dist_emit_kv(out, "channel", idx->channel) != DIST_OK) return DIST_ERR;
    178   if (dist_emit_kv(out, "version", idx->version) != DIST_OK) return DIST_ERR;
    179   if (dist_emit_kv(out, "hash", DIST_RELEASE1_HASH) != DIST_OK) return DIST_ERR;
    180 
    181   memset(used, 0, sizeof used);
    182   for (k = 0; k < idx->n_hosts; ++k) {
    183     unsigned hi = next_host(idx, used);
    184     const KitReleaseHost* h;
    185     unsigned u;
    186     used[hi] = 1;
    187     h = &idx->hosts[hi];
    188     if (emit(out, "\n[host]\n") != DIST_OK) return DIST_ERR;
    189     if (dist_emit_kv(out, "target", h->target) != DIST_OK) return DIST_ERR;
    190     if (h->has_kpkg && emit_hex(out, "kpkg", h->kpkg_id) != DIST_OK)
    191       return DIST_ERR;
    192     if (h->has_targz && emit_hex(out, "targz", h->targz_id) != DIST_OK)
    193       return DIST_ERR;
    194     if (h->size && emit_u64(out, "size", h->size) != DIST_OK) return DIST_ERR;
    195     for (u = 0; u < h->n_urls; ++u) {
    196       if (dist_emit_kv(out, "url", h->urls[u]) != DIST_OK) return DIST_ERR;
    197     }
    198   }
    199   return DIST_OK;
    200 }
    201 
    202 int dist_release_index_parse(const uint8_t* data, size_t len,
    203                              KitReleaseIndex* out, char* err, size_t errcap) {
    204   size_t pos = 0;
    205   int first = 1;
    206   ReleaseSection sec = R1_SEC_TOP;
    207   uint32_t tseen = 0, hseen = 0;
    208   KitReleaseHost* host = NULL;
    209 
    210   memset(out, 0, sizeof *out);
    211 
    212   while (pos < len) {
    213     char buf[DIST_KV_LINE_MAX];
    214     size_t end = pos;
    215     size_t n, i;
    216     char *t, *key, *val, *eq;
    217 
    218     while (end < len && data[end] != '\n') ++end;
    219     n = end - pos;
    220     if (n >= sizeof buf) return dist_set_err(err, errcap, "line too long");
    221     for (i = pos; i < end; ++i)
    222       if (data[i] == 0)
    223         return dist_set_err(err, errcap, "NUL byte in release index");
    224     memcpy(buf, data + pos, n);
    225     buf[n] = '\0';
    226     pos = (end < len) ? end + 1 : end;
    227     dist_trim_trail(buf);
    228 
    229     if (first) {
    230       first = 0;
    231       if (strcmp(buf, DIST_RELEASE1_MAGIC) != 0)
    232         return dist_set_err(err, errcap, "bad release index magic/version");
    233       continue;
    234     }
    235 
    236     t = dist_trim_lead(buf);
    237     if (*t == '\0' || *t == '#') continue;
    238 
    239     if (*t == '[') {
    240       if (strcmp(t, "[host]") != 0)
    241         return dist_set_err(err, errcap, "unknown section");
    242       if (sec == R1_SEC_TOP) {
    243         if ((tseen & (R1_T_CHANNEL | R1_T_VERSION | R1_T_HASH)) !=
    244             (R1_T_CHANNEL | R1_T_VERSION | R1_T_HASH))
    245           return dist_set_err(err, errcap, "missing required top-level key");
    246       } else if (!(hseen & R1_F_TARGET)) {
    247         return dist_set_err(err, errcap, "[host] missing target");
    248       }
    249       if (out->n_hosts >= KIT_RELEASE_MAX_HOSTS)
    250         return dist_set_err(err, errcap, "too many hosts");
    251       sec = R1_SEC_HOST;
    252       hseen = 0;
    253       host = &out->hosts[out->n_hosts++];
    254       continue;
    255     }
    256 
    257     eq = strchr(t, '=');
    258     if (!eq) return dist_set_err(err, errcap, "expected key = value");
    259     *eq = '\0';
    260     key = t;
    261     dist_trim_trail(key);
    262     val = dist_trim_lead(eq + 1);
    263 
    264     if (sec == R1_SEC_TOP) {
    265       if (strcmp(key, "channel") == 0) {
    266         if (seen_once(&tseen, R1_T_CHANNEL, "duplicate top-level key", err,
    267                       errcap) != DIST_OK)
    268           return DIST_ERR;
    269         if (!field_text_valid(val, 1))
    270           return dist_set_err(err, errcap, "bad channel");
    271         if (dist_copy_field(out->channel, sizeof out->channel, val, err,
    272                             errcap))
    273           return DIST_ERR;
    274       } else if (strcmp(key, "version") == 0) {
    275         if (seen_once(&tseen, R1_T_VERSION, "duplicate top-level key", err,
    276                       errcap) != DIST_OK)
    277           return DIST_ERR;
    278         if (!field_text_valid(val, 1))
    279           return dist_set_err(err, errcap, "bad version");
    280         if (dist_copy_field(out->version, sizeof out->version, val, err,
    281                             errcap))
    282           return DIST_ERR;
    283       } else if (strcmp(key, "hash") == 0) {
    284         if (seen_once(&tseen, R1_T_HASH, "duplicate top-level key", err,
    285                       errcap) != DIST_OK)
    286           return DIST_ERR;
    287         if (strcmp(val, DIST_RELEASE1_HASH) != 0)
    288           return dist_set_err(err, errcap, "unsupported hash algorithm");
    289       } else {
    290         return dist_set_err(err, errcap, "unknown top-level key");
    291       }
    292     } else {
    293       if (strcmp(key, "target") == 0) {
    294         if (seen_once(&hseen, R1_F_TARGET, "duplicate [host] key", err,
    295                       errcap) != DIST_OK)
    296           return DIST_ERR;
    297         if (!target_text_valid(val))
    298           return dist_set_err(err, errcap, "bad host target");
    299         if (dist_copy_field(host->target, sizeof host->target, val, err,
    300                             errcap))
    301           return DIST_ERR;
    302         if (out->n_hosts >= 2u &&
    303             strcmp(out->hosts[out->n_hosts - 2u].target, val) >= 0)
    304           return dist_set_err(err, errcap,
    305                               "hosts not ascending/unique by target");
    306       } else if (strcmp(key, "kpkg") == 0) {
    307         if (seen_once(&hseen, R1_F_KPKG, "duplicate [host] key", err, errcap) !=
    308             DIST_OK)
    309           return DIST_ERR;
    310         if (decode_id(host->kpkg_id, val, "bad kpkg id", err, errcap) !=
    311             DIST_OK)
    312           return DIST_ERR;
    313         host->has_kpkg = 1;
    314       } else if (strcmp(key, "targz") == 0) {
    315         if (seen_once(&hseen, R1_F_TARGZ, "duplicate [host] key", err, errcap) !=
    316             DIST_OK)
    317           return DIST_ERR;
    318         if (decode_id(host->targz_id, val, "bad targz id", err, errcap) !=
    319             DIST_OK)
    320           return DIST_ERR;
    321         host->has_targz = 1;
    322       } else if (strcmp(key, "size") == 0) {
    323         if (seen_once(&hseen, R1_F_SIZE, "duplicate [host] key", err, errcap) !=
    324             DIST_OK)
    325           return DIST_ERR;
    326         if (dist_parse_u64(val, &host->size) != DIST_OK)
    327           return dist_set_err(err, errcap, "bad size");
    328       } else if (strcmp(key, "url") == 0) {
    329         if (host->n_urls >= KIT_RELEASE_MAX_URLS)
    330           return dist_set_err(err, errcap, "too many urls");
    331         if (!field_text_valid(val, 1))
    332           return dist_set_err(err, errcap, "bad url");
    333         if (dist_copy_field(host->urls[host->n_urls], sizeof host->urls[0], val,
    334                             err, errcap))
    335           return DIST_ERR;
    336         ++host->n_urls;
    337       } else {
    338         return dist_set_err(err, errcap, "unknown [host] key");
    339       }
    340     }
    341   }
    342 
    343   if (first) return dist_set_err(err, errcap, "bad release index magic/version");
    344   if (sec == R1_SEC_TOP) {
    345     if ((tseen & (R1_T_CHANNEL | R1_T_VERSION | R1_T_HASH)) !=
    346         (R1_T_CHANNEL | R1_T_VERSION | R1_T_HASH))
    347       return dist_set_err(err, errcap, "missing required top-level key");
    348   } else if (!(hseen & R1_F_TARGET)) {
    349     return dist_set_err(err, errcap, "[host] missing target");
    350   }
    351   return dist_release_index_validate(out, err, errcap);
    352 }