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 }