defn.c (21766B)
1 #include "defn.h" 2 3 #include <stdio.h> 4 #include <string.h> 5 6 static int set_err(char* err, size_t cap, const char* msg) { 7 if (err && cap) snprintf(err, cap, "%s", msg); 8 return BUILD_ERR; 9 } 10 11 static int slice_eq_cstr(KitSlice s, const char* c) { 12 size_t n = c ? strlen(c) : 0u; 13 return s.len == n && (n == 0u || memcmp(s.s, c, n) == 0); 14 } 15 16 static int token_valid(KitSlice s, size_t cap) { 17 size_t i; 18 if (s.len == 0u || s.len >= cap || !s.s) return 0; 19 for (i = 0; i < s.len; ++i) { 20 unsigned char c = (unsigned char)s.s[i]; 21 if (c == '\0' || c == '\n' || c == '\r' || c == ' ' || c == '\t') 22 return 0; 23 } 24 return 1; 25 } 26 27 static int local_name_valid(KitSlice s) { 28 size_t i; 29 if (!token_valid(s, BUILD_TARGET_MAX)) return 0; 30 for (i = 0; i < s.len; ++i) { 31 if (s.s[i] == '/' || s.s[i] == ':' || s.s[i] == '\\') return 0; 32 } 33 return 1; 34 } 35 36 static int type_valid(KitSlice s) { 37 size_t i; 38 if (!token_valid(s, BUILD_KEY_MAX)) return 0; 39 for (i = 0; i < s.len; ++i) { 40 if (s.s[i] == '/' || s.s[i] == ':' || s.s[i] == '\\') return 0; 41 } 42 return 1; 43 } 44 45 static int package_valid(KitSlice s) { 46 size_t i, start = 0; 47 if (!s.s && s.len != 0u) return 0; 48 if (s.len >= BUILD_PATH_MAX) return 0; 49 if (s.len == 0u) return 1; 50 if (s.s[0] == '/') return 0; 51 for (i = 0; i <= s.len; ++i) { 52 if (i == s.len || s.s[i] == '/') { 53 size_t n = i - start; 54 if (n == 0u) return 0; 55 if (n == 1u && s.s[start] == '.') return 0; 56 if (n == 2u && s.s[start] == '.' && s.s[start + 1u] == '.') return 0; 57 start = i + 1u; 58 } else if (s.s[i] == '\0' || s.s[i] == '\\' || s.s[i] == ':') { 59 return 0; 60 } 61 } 62 return 1; 63 } 64 65 static int rel_path_valid(KitSlice s, int allow_single_dot) { 66 size_t i; 67 size_t start = 0; 68 if (!token_valid(s, BUILD_PATH_MAX)) return 0; 69 if (allow_single_dot && s.len == 1u && s.s[0] == '.') return 1; 70 if (s.s[0] == '/') return 0; 71 for (i = 0; i <= s.len; ++i) { 72 if (i == s.len || s.s[i] == '/') { 73 size_t n = i - start; 74 if (n == 0u) return 0; 75 if (n == 1u && s.s[start] == '.') return 0; 76 if (n == 2u && s.s[start] == '.' && s.s[start + 1u] == '.') return 0; 77 start = i + 1u; 78 } else if (s.s[i] == '\\' || s.s[i] == ':') { 79 return 0; 80 } 81 } 82 return 1; 83 } 84 85 static int recipe_path_valid(KitSlice s) { 86 if (s.len > 2u && s.s[0] == '/' && s.s[1] == '/') 87 return rel_path_valid((KitSlice){.s = s.s + 2u, .len = s.len - 2u}, 0); 88 return rel_path_valid(s, 0); 89 } 90 91 static int pattern_valid(KitSlice s) { 92 size_t i; 93 if (!token_valid(s, BUILD_TARGET_MAX)) return 0; 94 for (i = 0; i < s.len; ++i) { 95 if (s.s[i] == '/' || s.s[i] == ':' || s.s[i] == '\\') return 0; 96 } 97 return 1; 98 } 99 100 static int next_line(const uint8_t* data, size_t len, size_t* pos, 101 KitSlice* line) { 102 size_t start = *pos; 103 size_t end = start; 104 while (end < len && data[end] != '\n') ++end; 105 if (end == len) return BUILD_ERR; 106 line->data = data + start; 107 line->len = end - start; 108 *pos = end + 1u; 109 return BUILD_OK; 110 } 111 112 static void copy_slice(char* dst, size_t cap, KitSlice s) { 113 (void)cap; 114 if (s.len) memcpy(dst, s.s, s.len); 115 dst[s.len] = '\0'; 116 } 117 118 static int cstr_slice_cmp(const char* a, KitSlice b) { 119 size_t i = 0; 120 for (;;) { 121 int ac = a[i] ? (int)(unsigned char)a[i] : 0; 122 int bc = i < b.len ? (int)(unsigned char)b.s[i] : 0; 123 if (ac != bc) return ac < bc ? -1 : 1; 124 if (!ac) return 0; 125 ++i; 126 } 127 } 128 129 static int parse_bool(KitSlice s, int* out) { 130 if (slice_eq_cstr(s, "true")) { 131 *out = 1; 132 return BUILD_OK; 133 } 134 if (slice_eq_cstr(s, "false")) { 135 *out = 0; 136 return BUILD_OK; 137 } 138 return BUILD_ERR; 139 } 140 141 static int split_word(KitSlice line, KitSlice* key, KitSlice* rest) { 142 size_t i = 0; 143 while (i < line.len && line.s[i] != ' ' && line.s[i] != '\t') ++i; 144 key->s = line.s; 145 key->len = i; 146 while (i < line.len && (line.s[i] == ' ' || line.s[i] == '\t')) ++i; 147 rest->s = line.s + i; 148 rest->len = line.len - i; 149 return key->len != 0u ? BUILD_OK : BUILD_ERR; 150 } 151 152 static int parse_header(KitSlice line, KitSlice* kind, KitSlice* arg) { 153 size_t i; 154 if (line.len < 3u || line.s[0] != '[' || line.s[line.len - 1u] != ']') 155 return BUILD_ERR; 156 line.s++; 157 line.len -= 2u; 158 for (i = 0; i < line.len && line.s[i] != ' ' && line.s[i] != '\t'; ++i) {} 159 kind->s = line.s; 160 kind->len = i; 161 while (i < line.len && (line.s[i] == ' ' || line.s[i] == '\t')) ++i; 162 arg->s = line.s + i; 163 arg->len = line.len - i; 164 return kind->len != 0u ? BUILD_OK : BUILD_ERR; 165 } 166 167 static int line_is_header(KitSlice line) { 168 return line.len >= 2u && line.s[0] == '[' && line.s[line.len - 1u] == ']'; 169 } 170 171 static int glob_match(const char* pat, size_t pn, const char* text, size_t tn) { 172 size_t pi = 0, ti = 0, star = (size_t)-1, mark = 0; 173 while (ti < tn) { 174 if (pi < pn && pat[pi] == '*') { 175 star = pi++; 176 mark = ti; 177 } else if (pi < pn && (pat[pi] == '?' || pat[pi] == text[ti])) { 178 ++pi; 179 ++ti; 180 } else if (star != (size_t)-1) { 181 pi = star + 1u; 182 ti = ++mark; 183 } else { 184 return 0; 185 } 186 } 187 while (pi < pn && pat[pi] == '*') ++pi; 188 return pi == pn; 189 } 190 191 int build_defn_parse(const uint8_t* data, size_t len, BuildDefn* out, char* err, 192 size_t errcap) { 193 size_t pos = 0; 194 KitSlice line; 195 enum { ST_NONE, ST_TARGET, ST_DEFAULT, ST_RULE, ST_REDO } stanza = ST_NONE; 196 BuildTargetDefn* cur_target = NULL; 197 BuildDefaultDefn* cur_default = NULL; 198 BuildRuleDefn* cur_rule = NULL; 199 200 if (!data || !out) return set_err(err, errcap, "missing build definition"); 201 if ((out->cap_targets && !out->targets) || 202 (out->cap_defaults && !out->defaults) || 203 (out->cap_rules && !out->rules)) 204 return set_err(err, errcap, "missing build definition storage"); 205 out->bytes = data; 206 out->len = len; 207 out->version = 0; 208 out->n_targets = 0; 209 out->n_defaults = 0; 210 out->n_rules = 0; 211 memset(&out->redo, 0, sizeof out->redo); 212 out->redo.walk_parents = 1; 213 214 if (next_line(data, len, &pos, &line) != BUILD_OK) 215 return set_err(err, errcap, "bad build definition magic/version"); 216 if (kit_slice_eq_cstr(line, "kit-build 1")) { 217 out->version = 1; 218 } else if (kit_slice_eq_cstr(line, "kit-build 2")) { 219 out->version = 2; 220 } else { 221 return set_err(err, errcap, "bad build definition magic/version"); 222 } 223 224 while (pos < len) { 225 KitSlice key, rest, kind, arg; 226 if (next_line(data, len, &pos, &line) != BUILD_OK) 227 return set_err(err, errcap, "unterminated stanza"); 228 if (line.len == 0u) continue; 229 230 if (line_is_header(line)) { 231 if (parse_header(line, &kind, &arg) != BUILD_OK) 232 return set_err(err, errcap, "bad stanza header"); 233 cur_target = NULL; 234 cur_default = NULL; 235 cur_rule = NULL; 236 if (slice_eq_cstr(kind, "target")) { 237 if (arg.len == 0u) return set_err(err, errcap, "missing target name"); 238 if (!local_name_valid(arg)) 239 return set_err(err, errcap, "invalid target name"); 240 if (out->n_targets > 0 && 241 cstr_slice_cmp(out->targets[out->n_targets - 1u].name, arg) >= 0) 242 return set_err(err, errcap, "non-canonical target ordering"); 243 if (out->n_targets >= out->cap_targets) 244 return set_err(err, errcap, "too many targets"); 245 cur_target = &out->targets[out->n_targets++]; 246 memset(cur_target, 0, sizeof *cur_target); 247 copy_slice(cur_target->name, sizeof cur_target->name, arg); 248 stanza = ST_TARGET; 249 } else if (slice_eq_cstr(kind, "default")) { 250 if (out->version < 2) 251 return set_err(err, errcap, "default requires kit-build 2"); 252 if (arg.len == 0u) return set_err(err, errcap, "missing default type"); 253 if (!type_valid(arg)) return set_err(err, errcap, "invalid default type"); 254 if (out->n_defaults > 0 && 255 cstr_slice_cmp(out->defaults[out->n_defaults - 1u].type, arg) >= 0) 256 return set_err(err, errcap, "non-canonical default ordering"); 257 if (out->n_defaults >= out->cap_defaults) 258 return set_err(err, errcap, "too many defaults"); 259 cur_default = &out->defaults[out->n_defaults++]; 260 memset(cur_default, 0, sizeof *cur_default); 261 copy_slice(cur_default->type, sizeof cur_default->type, arg); 262 stanza = ST_DEFAULT; 263 } else if (slice_eq_cstr(kind, "rule")) { 264 if (out->version < 2) 265 return set_err(err, errcap, "rule requires kit-build 2"); 266 if (arg.len == 0u) return set_err(err, errcap, "missing rule name"); 267 if (!type_valid(arg)) return set_err(err, errcap, "invalid rule name"); 268 if (out->n_rules > 0 && 269 cstr_slice_cmp(out->rules[out->n_rules - 1u].name, arg) >= 0) 270 return set_err(err, errcap, "non-canonical rule ordering"); 271 if (out->n_rules >= out->cap_rules) 272 return set_err(err, errcap, "too many rules"); 273 cur_rule = &out->rules[out->n_rules++]; 274 memset(cur_rule, 0, sizeof *cur_rule); 275 copy_slice(cur_rule->name, sizeof cur_rule->name, arg); 276 stanza = ST_RULE; 277 } else if (slice_eq_cstr(kind, "redo-defaults")) { 278 if (out->version < 2) 279 return set_err(err, errcap, "redo-defaults requires kit-build 2"); 280 if (arg.len != 0u && !slice_eq_cstr(arg, "settings")) 281 return set_err(err, errcap, "bad redo-defaults header"); 282 stanza = ST_REDO; 283 } else { 284 return set_err(err, errcap, "unknown stanza"); 285 } 286 continue; 287 } 288 289 if (split_word(line, &key, &rest) != BUILD_OK) 290 return set_err(err, errcap, "bad stanza line"); 291 if (stanza == ST_TARGET) { 292 if (!cur_target) return set_err(err, errcap, "internal target state"); 293 if (slice_eq_cstr(key, "recipe")) { 294 if (cur_target->has_recipe || !recipe_path_valid(rest)) 295 return set_err(err, errcap, "invalid recipe path"); 296 copy_slice(cur_target->recipe_path, sizeof cur_target->recipe_path, 297 rest); 298 cur_target->has_recipe = 1; 299 } else if (slice_eq_cstr(key, "type")) { 300 if (cur_target->has_type || !type_valid(rest)) 301 return set_err(err, errcap, "invalid target type"); 302 copy_slice(cur_target->type, sizeof cur_target->type, rest); 303 cur_target->has_type = 1; 304 } else { 305 return set_err(err, errcap, "unknown target field"); 306 } 307 } else if (stanza == ST_DEFAULT) { 308 if (!cur_default) return set_err(err, errcap, "internal default state"); 309 if (!slice_eq_cstr(key, "recipe") || cur_default->recipe_path[0] || 310 !recipe_path_valid(rest)) 311 return set_err(err, errcap, "invalid default field"); 312 copy_slice(cur_default->recipe_path, sizeof cur_default->recipe_path, 313 rest); 314 } else if (stanza == ST_RULE) { 315 if (!cur_rule) return set_err(err, errcap, "internal rule state"); 316 if (slice_eq_cstr(key, "match")) { 317 if (cur_rule->match[0] || !pattern_valid(rest)) 318 return set_err(err, errcap, "invalid rule match"); 319 copy_slice(cur_rule->match, sizeof cur_rule->match, rest); 320 } else if (slice_eq_cstr(key, "recipe")) { 321 if (cur_rule->has_recipe || !recipe_path_valid(rest)) 322 return set_err(err, errcap, "invalid rule recipe"); 323 copy_slice(cur_rule->recipe_path, sizeof cur_rule->recipe_path, rest); 324 cur_rule->has_recipe = 1; 325 } else if (slice_eq_cstr(key, "type")) { 326 if (cur_rule->has_type || !type_valid(rest)) 327 return set_err(err, errcap, "invalid rule type"); 328 copy_slice(cur_rule->type, sizeof cur_rule->type, rest); 329 cur_rule->has_type = 1; 330 } else { 331 return set_err(err, errcap, "unknown rule field"); 332 } 333 } else if (stanza == ST_REDO) { 334 if (slice_eq_cstr(key, "enabled")) { 335 if (parse_bool(rest, &out->redo.enabled) != BUILD_OK) 336 return set_err(err, errcap, "invalid redo enabled"); 337 } else if (slice_eq_cstr(key, "walk-parents")) { 338 if (parse_bool(rest, &out->redo.walk_parents) != BUILD_OK) 339 return set_err(err, errcap, "invalid redo walk-parents"); 340 } else if (slice_eq_cstr(key, "search")) { 341 KitSlice word; 342 size_t start = 0, i; 343 for (i = 0; i <= rest.len; ++i) { 344 if (i == rest.len || rest.s[i] == ' ' || rest.s[i] == '\t') { 345 if (i == start) { 346 start = i + 1u; 347 continue; 348 } 349 word.s = rest.s + start; 350 word.len = i - start; 351 if (!rel_path_valid(word, 1) || 352 out->redo.n_search >= 353 sizeof out->redo.search / sizeof out->redo.search[0]) 354 return set_err(err, errcap, "invalid redo search"); 355 copy_slice(out->redo.search[out->redo.n_search], 356 sizeof out->redo.search[out->redo.n_search], word); 357 ++out->redo.n_search; 358 start = i + 1u; 359 } 360 } 361 } else { 362 return set_err(err, errcap, "unknown redo-defaults field"); 363 } 364 } else { 365 return set_err(err, errcap, "field outside stanza"); 366 } 367 } 368 369 for (pos = 0; pos < out->n_targets; ++pos) { 370 if (!out->targets[pos].has_recipe && !out->targets[pos].has_type) 371 return set_err(err, errcap, "target has no recipe or type"); 372 } 373 for (pos = 0; pos < out->n_defaults; ++pos) { 374 if (!out->defaults[pos].recipe_path[0]) 375 return set_err(err, errcap, "default has no recipe"); 376 } 377 for (pos = 0; pos < out->n_rules; ++pos) { 378 if (!out->rules[pos].match[0]) 379 return set_err(err, errcap, "rule has no match"); 380 } 381 if (out->redo.enabled && out->redo.n_search == 0u) { 382 snprintf(out->redo.search[out->redo.n_search++], 383 sizeof out->redo.search[0], "."); 384 } 385 return BUILD_OK; 386 } 387 388 const BuildTargetDefn* build_defn_find(const BuildDefn* defn, KitSlice name) { 389 size_t lo, hi; 390 if (!defn || !local_name_valid(name)) return NULL; 391 lo = 0; 392 hi = defn->n_targets; 393 while (lo < hi) { 394 size_t mid = lo + (hi - lo) / 2u; 395 int cmp = cstr_slice_cmp(defn->targets[mid].name, name); 396 if (cmp == 0) return &defn->targets[mid]; 397 if (cmp < 0) 398 lo = mid + 1u; 399 else 400 hi = mid; 401 } 402 return NULL; 403 } 404 405 const BuildDefaultDefn* build_defn_default_find(const BuildDefn* defn, 406 KitSlice type) { 407 size_t lo, hi; 408 if (!defn || !type_valid(type)) return NULL; 409 lo = 0; 410 hi = defn->n_defaults; 411 while (lo < hi) { 412 size_t mid = lo + (hi - lo) / 2u; 413 int cmp = cstr_slice_cmp(defn->defaults[mid].type, type); 414 if (cmp == 0) return &defn->defaults[mid]; 415 if (cmp < 0) 416 lo = mid + 1u; 417 else 418 hi = mid; 419 } 420 return NULL; 421 } 422 423 const BuildRuleDefn* build_defn_rule_match(const BuildDefn* defn, 424 KitSlice name, int* ambiguous) { 425 const BuildRuleDefn* match = NULL; 426 size_t i; 427 if (ambiguous) *ambiguous = 0; 428 if (!defn || !local_name_valid(name)) return NULL; 429 for (i = 0; i < defn->n_rules; ++i) { 430 const BuildRuleDefn* r = &defn->rules[i]; 431 if (!glob_match(r->match, strlen(r->match), name.s, name.len)) continue; 432 if (match) { 433 if (ambiguous) *ambiguous = 1; 434 return NULL; 435 } 436 match = r; 437 } 438 return match; 439 } 440 441 static int last_component(KitSlice s, KitSlice* out) { 442 size_t i = s.len; 443 if (s.len == 0u) return BUILD_ERR; 444 while (i > 0u && s.s[i - 1u] != '/') --i; 445 out->s = s.s + i; 446 out->len = s.len - i; 447 return local_name_valid(*out) ? BUILD_OK : BUILD_ERR; 448 } 449 450 int build_target_canonicalize(KitSlice label, KitSlice current_repo, 451 KitSlice current_package, 452 char out[BUILD_TARGET_MAX], char* err, 453 size_t errcap) { 454 KitSlice repo = current_repo; 455 KitSlice pkg, local; 456 char repo_buf[BUILD_KEY_MAX]; 457 size_t colon = (size_t)-1, i; 458 if (!out || !label.s || label.len == 0u || !package_valid(current_package)) 459 return set_err(err, errcap, "invalid target label"); 460 if (repo.len && !token_valid(repo, BUILD_KEY_MAX)) 461 return set_err(err, errcap, "invalid target repo"); 462 463 if (label.s[0] == '@') { 464 KitSlice explicit_repo; 465 size_t slash = 1u; 466 while (slash < label.len && label.s[slash] != '/') ++slash; 467 if (slash <= 1u || slash + 1u >= label.len || label.s[slash + 1u] != '/') 468 return set_err(err, errcap, "bad target label"); 469 explicit_repo.s = label.s + 1u; 470 explicit_repo.len = slash - 1u; 471 if (!token_valid(explicit_repo, BUILD_KEY_MAX)) 472 return set_err(err, errcap, "bad target repo"); 473 if (current_repo.len) { 474 if (current_repo.len + explicit_repo.len + 2u > sizeof repo_buf) 475 return set_err(err, errcap, "target repo too long"); 476 memcpy(repo_buf, current_repo.s, current_repo.len); 477 repo_buf[current_repo.len] = '+'; 478 memcpy(repo_buf + current_repo.len + 1u, explicit_repo.s, 479 explicit_repo.len); 480 repo_buf[current_repo.len + 1u + explicit_repo.len] = '\0'; 481 repo = kit_slice_cstr(repo_buf); 482 } else { 483 repo = explicit_repo; 484 } 485 label.s += slash; 486 label.len -= slash; 487 } 488 489 if (label.len >= 2u && label.s[0] == '/' && label.s[1] == '/') { 490 KitSlice body = {.s = label.s + 2u, .len = label.len - 2u}; 491 for (i = 0; i < body.len; ++i) { 492 if (body.s[i] == ':') { 493 if (colon != (size_t)-1) return set_err(err, errcap, "bad target label"); 494 colon = i; 495 } 496 } 497 if (colon == (size_t)-1) { 498 pkg = body; 499 if (!package_valid(pkg) || last_component(pkg, &local) != BUILD_OK) 500 return set_err(err, errcap, "bad target label"); 501 } else { 502 pkg.s = body.s; 503 pkg.len = colon; 504 local.s = body.s + colon + 1u; 505 local.len = body.len - colon - 1u; 506 if (!package_valid(pkg) || !local_name_valid(local)) 507 return set_err(err, errcap, "bad target label"); 508 } 509 } else if (label.s[0] == ':') { 510 pkg = current_package; 511 local.s = label.s + 1u; 512 local.len = label.len - 1u; 513 if (!local_name_valid(local)) 514 return set_err(err, errcap, "bad target label"); 515 } else { 516 pkg = current_package; 517 local = label; 518 if (!local_name_valid(local)) 519 return set_err(err, errcap, "bad target label"); 520 } 521 522 if (repo.len) { 523 if (repo.len + pkg.len + local.len + 5u > BUILD_TARGET_MAX) 524 return set_err(err, errcap, "target label too long"); 525 out[0] = '@'; 526 memcpy(out + 1u, repo.s, repo.len); 527 out[1u + repo.len] = '/'; 528 out[2u + repo.len] = '/'; 529 if (pkg.len) memcpy(out + 3u + repo.len, pkg.s, pkg.len); 530 out[3u + repo.len + pkg.len] = ':'; 531 memcpy(out + 4u + repo.len + pkg.len, local.s, local.len); 532 out[4u + repo.len + pkg.len + local.len] = '\0'; 533 } else if (pkg.len == 0u) { 534 if (local.len + 4u > BUILD_TARGET_MAX) 535 return set_err(err, errcap, "target label too long"); 536 out[0] = '/'; 537 out[1] = '/'; 538 out[2] = ':'; 539 memcpy(out + 3u, local.s, local.len); 540 out[3u + local.len] = '\0'; 541 } else { 542 if (pkg.len + local.len + 4u > BUILD_TARGET_MAX) 543 return set_err(err, errcap, "target label too long"); 544 out[0] = '/'; 545 out[1] = '/'; 546 memcpy(out + 2u, pkg.s, pkg.len); 547 out[2u + pkg.len] = ':'; 548 memcpy(out + 3u + pkg.len, local.s, local.len); 549 out[3u + pkg.len + local.len] = '\0'; 550 } 551 return BUILD_OK; 552 } 553 554 int build_target_split_repo(KitSlice canonical, char repo[BUILD_KEY_MAX], 555 char package[BUILD_PATH_MAX], 556 char local[BUILD_TARGET_MAX]) { 557 KitSlice pkg, name; 558 size_t i, colon = (size_t)-1, body_start = 0; 559 if (!canonical.s || canonical.len < 4u) 560 return BUILD_ERR; 561 if (repo) repo[0] = '\0'; 562 if (canonical.s[0] == '@') { 563 size_t slash = 1u; 564 while (slash < canonical.len && canonical.s[slash] != '/') ++slash; 565 if (slash <= 1u || slash + 1u >= canonical.len || 566 canonical.s[slash + 1u] != '/') 567 return BUILD_ERR; 568 if (repo) { 569 KitSlice r = {.s = canonical.s + 1u, .len = slash - 1u}; 570 if (!token_valid(r, BUILD_KEY_MAX)) return BUILD_ERR; 571 copy_slice(repo, BUILD_KEY_MAX, r); 572 } 573 body_start = slash; 574 } 575 if (canonical.len < body_start + 4u || canonical.s[body_start] != '/' || 576 canonical.s[body_start + 1u] != '/') 577 return BUILD_ERR; 578 for (i = body_start + 2u; i < canonical.len; ++i) { 579 if (canonical.s[i] == ':') { 580 colon = i; 581 break; 582 } 583 } 584 if (colon == (size_t)-1) return BUILD_ERR; 585 pkg.s = canonical.s + body_start + 2u; 586 pkg.len = colon - body_start - 2u; 587 name.s = canonical.s + colon + 1u; 588 name.len = canonical.len - colon - 1u; 589 if (!package_valid(pkg) || !local_name_valid(name)) return BUILD_ERR; 590 copy_slice(package, BUILD_PATH_MAX, pkg); 591 copy_slice(local, BUILD_TARGET_MAX, name); 592 return BUILD_OK; 593 } 594 595 int build_target_split(KitSlice canonical, char package[BUILD_PATH_MAX], 596 char local[BUILD_TARGET_MAX]) { 597 return build_target_split_repo(canonical, NULL, package, local); 598 } 599 600 int build_recipe_path_resolve(KitSlice package, KitSlice recipe, 601 char out[BUILD_PATH_MAX]) { 602 if (!out || !package_valid(package) || !recipe_path_valid(recipe)) 603 return BUILD_ERR; 604 if (recipe.len > 2u && recipe.s[0] == '/' && recipe.s[1] == '/') { 605 if (recipe.len - 2u + 1u > BUILD_PATH_MAX) return BUILD_ERR; 606 memcpy(out, recipe.s + 2u, recipe.len - 2u); 607 out[recipe.len - 2u] = '\0'; 608 return BUILD_OK; 609 } 610 if (package.len == 0u) { 611 copy_slice(out, BUILD_PATH_MAX, recipe); 612 return BUILD_OK; 613 } 614 if (package.len + 1u + recipe.len + 1u > BUILD_PATH_MAX) return BUILD_ERR; 615 memcpy(out, package.s, package.len); 616 out[package.len] = '/'; 617 memcpy(out + package.len + 1u, recipe.s, recipe.len); 618 out[package.len + 1u + recipe.len] = '\0'; 619 return BUILD_OK; 620 }