update.c (35221B)
1 #include <kit/cas.h> 2 #include <kit/core.h> 3 #include <kit/package.h> 4 #include <stddef.h> 5 #include <stdint.h> 6 #include <stdio.h> 7 #include <string.h> 8 9 #include "dist_host.h" 10 #include "driver.h" 11 #include "env.h" 12 #include "install_links.h" 13 14 /* `kit update` — verify and install a kit release into the single-root data 15 * layout, and manage installed versions. See doc/plan/SELFDIST.md. 16 * 17 * $KIT_HOME/ (default $XDG_DATA_HOME/kit ≡ ~/.local/share/kit) 18 * versions/<ver>/{bin,lib,include,support,VERSION,...} 19 * current -> versions/<ver> atomic active-toolchain pointer 20 * bin/ PATH links -> ../current/bin/kit 21 * config/ cache/downloads/ 22 * 23 * Offline-first: `kit update <file.kpkg>` verifies a local package against the 24 * embedded release key set (driver/release_key.c) — or an explicit --key — and 25 * installs it with no network. Networked forms authenticate the detached 26 * signature on the channel index before parsing it, then require the package 27 * to carry the same signer identity and the advertised version/package id. 28 * URLs and mirrors are untrusted transport hints. */ 29 30 #define UPDATE_TOOL "update" 31 #define UP_PATH 4096u 32 #define UP_MAX_VERS 256 33 34 /* ---------------------------------------------------------------------- */ 35 /* layout */ 36 /* ---------------------------------------------------------------------- */ 37 38 typedef struct UpPaths { 39 char home[UP_PATH]; 40 char versions[UP_PATH]; 41 char current[UP_PATH]; 42 char bin[UP_PATH]; 43 char config[UP_PATH]; 44 char downloads[UP_PATH]; 45 } UpPaths; 46 47 static int up_resolve_paths(UpPaths* p) { 48 if (driver_kit_home(p->home, sizeof p->home) != 0) { 49 driver_errf(UPDATE_TOOL, 50 "cannot determine kit home (set KIT_HOME, XDG_DATA_HOME, or HOME)"); 51 return 1; 52 } 53 snprintf(p->versions, sizeof p->versions, "%s/versions", p->home); 54 snprintf(p->current, sizeof p->current, "%s/current", p->home); 55 snprintf(p->bin, sizeof p->bin, "%s/bin", p->home); 56 snprintf(p->config, sizeof p->config, "%s/config", p->home); 57 snprintf(p->downloads, sizeof p->downloads, "%s/cache/downloads", p->home); 58 return 0; 59 } 60 61 static int up_ensure_dirs(DriverEnv* env, const UpPaths* p) { 62 if (driver_mkdir_p(env, p->versions) != 0) return 1; 63 if (driver_mkdir_p(env, p->bin) != 0) return 1; 64 if (driver_mkdir_p(env, p->config) != 0) return 1; 65 if (driver_mkdir_p(env, p->downloads) != 0) return 1; 66 return 0; 67 } 68 69 /* Active version from the `current` symlink target ("versions/<ver>"). Returns 70 * 0 and fills ver[] when current resolves, 1 when there is no current. */ 71 static int up_current_version(const UpPaths* p, char* ver, size_t cap) { 72 char target[UP_PATH]; 73 size_t i, last = 0; 74 int saw = 0; 75 if (driver_readlink(p->current, target, sizeof target) != 0) return 1; 76 for (i = 0; target[i]; ++i) 77 if (target[i] == '/') { 78 last = i + 1u; 79 saw = 1; 80 } 81 snprintf(ver, cap, "%s", saw ? target + last : target); 82 return 0; 83 } 84 85 static int up_version_installed(const UpPaths* p, const char* ver) { 86 char dir[UP_PATH]; 87 snprintf(dir, sizeof dir, "%s/%s", p->versions, ver); 88 return driver_path_lexists(dir); 89 } 90 91 /* Snapshot installed version names into vers[] (ascending CalVer). Returns the 92 * count. Non-CalVer entries keep insertion order relative to each other. */ 93 static int up_list_installed(DriverEnv* env, const UpPaths* p, 94 char (*vers)[KIT_PKG_VERSION_MAX], int cap) { 95 DriverDirHandle* d = driver_open_dir(env, p->versions); 96 int n = 0, a; 97 uint64_t idx = 0; 98 if (!d) return 0; 99 for (;;) { 100 const char* name; 101 uint32_t nl; 102 uint64_t ino, sz, mt; 103 uint8_t ft; 104 if (driver_read_dir_entry(d, idx++, &name, &nl, &ino, &sz, &mt, &ft) != 0) 105 break; 106 if (n >= cap) break; 107 if (nl == 0 || nl >= KIT_PKG_VERSION_MAX) continue; 108 memcpy(vers[n], name, nl); 109 vers[n][nl] = '\0'; 110 ++n; 111 } 112 driver_close_dir(env, d); 113 for (a = 1; a < n; ++a) { /* insertion sort by CalVer */ 114 char tmp[KIT_PKG_VERSION_MAX]; 115 int b = a - 1, cmp; 116 memcpy(tmp, vers[a], sizeof tmp); 117 while (b >= 0 && 118 kit_calver_compare(vers[b], tmp, &cmp) == KIT_OK && cmp > 0) { 119 memcpy(vers[b + 1], vers[b], KIT_PKG_VERSION_MAX); 120 --b; 121 } 122 memcpy(vers[b + 1], tmp, KIT_PKG_VERSION_MAX); 123 } 124 return n; 125 } 126 127 /* ---------------------------------------------------------------------- */ 128 /* verify */ 129 /* ---------------------------------------------------------------------- */ 130 131 static int up_read(const KitContext* ctx, const char* path, KitFileData* out) { 132 out->data = NULL; 133 out->size = 0; 134 out->token = NULL; 135 return ctx->file_io->read_all(ctx->file_io->user, path, out) == KIT_OK; 136 } 137 138 static void up_release(const KitContext* ctx, KitFileData* fd) { 139 if ((fd->token || fd->data) && ctx->file_io->release) 140 ctx->file_io->release(ctx->file_io->user, fd); 141 } 142 143 /* Verify detached bytes against --key when present, otherwise the compiled 144 * release-key set. The matching signer is returned so the caller can require 145 * the package selected by a channel index to use that same identity. */ 146 static int up_verify_detached(const KitContext* ctx, const uint8_t* data, 147 size_t data_len, const uint8_t* signature, 148 size_t signature_len, const uint8_t* key_bytes, 149 size_t key_len, 150 uint8_t signer[KIT_PKG_KEYID_LEN]) { 151 KitPkgDetachedVerifyOptions opts; 152 KitPkgDetachedVerifyResult result; 153 const KitReleaseKey* keys; 154 unsigned nkeys = 0, i; 155 memset(&opts, 0, sizeof opts); 156 opts.data = data; 157 opts.data_len = data_len; 158 opts.signature = signature; 159 opts.signature_len = signature_len; 160 if (key_bytes) { 161 opts.pubkey_bytes = key_bytes; 162 opts.pubkey_len = key_len; 163 if (kit_pkg_verify_detached(ctx, &opts, &result) != KIT_OK) return 1; 164 memcpy(signer, result.keyid, KIT_PKG_KEYID_LEN); 165 return 0; 166 } 167 keys = driver_release_keys(&nkeys); 168 if (!keys || nkeys == 0) { 169 driver_errf(UPDATE_TOOL, 170 "no compiled release keys; pass --key <pubkey>"); 171 return 1; 172 } 173 for (i = 0; i < nkeys; ++i) { 174 opts.pubkey_bytes = (const uint8_t*)keys[i].pubkey; 175 opts.pubkey_len = strlen(keys[i].pubkey); 176 if (kit_pkg_verify_detached(ctx, &opts, &result) == KIT_OK) { 177 memcpy(signer, result.keyid, KIT_PKG_KEYID_LEN); 178 return 0; 179 } 180 } 181 return 1; 182 } 183 184 /* Pull the 64-hex package id out of a verified trusted comment ("...pkgid=<hex> 185 * ..."). Returns 0 + fills out[65] on success. */ 186 static int up_extract_pkgid(const char* trusted, char* out) { 187 const char* s = trusted; 188 const char* tag = "pkgid="; 189 int i; 190 out[0] = '\0'; 191 for (; *s; ++s) { 192 const char* a = s; 193 const char* b = tag; 194 while (*b && *a == *b) { 195 ++a; 196 ++b; 197 } 198 if (!*b) { /* matched "pkgid=" at s */ 199 for (i = 0; i < 64; ++i) { 200 char c = a[i]; 201 int hex = (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || 202 (c >= 'A' && c <= 'F'); 203 if (!hex) return 1; 204 out[i] = (c >= 'A' && c <= 'F') ? (char)(c - 'A' + 'a') : c; 205 } 206 out[64] = '\0'; 207 return 0; 208 } 209 } 210 return 1; 211 } 212 213 /* Verify `data`/`len` as a fat kit kpkg. With key_bytes != NULL, verify against 214 * just that minisign public-key file content; otherwise try every embedded 215 * release key until one verifies. When unpack_dir != NULL, materialize the 216 * default output there. On success returns 0 and fills name/version/pkgid. */ 217 static int up_verify(DriverEnv* env, const KitContext* ctx, const uint8_t* data, 218 size_t len, const uint8_t* key_bytes, size_t key_len, 219 const char* unpack_dir, char* name, size_t namecap, 220 char* version, size_t vercap, char* pkgid_hex, 221 uint8_t signer[KIT_PKG_KEYID_LEN]) { 222 KitCasHost host = driver_cas_host(env); 223 KitPkgVerifyOptions opts; 224 KitPkgVerifyResult res; 225 const KitReleaseKey* keys; 226 unsigned nkeys = 0, i; 227 228 if (key_bytes) { 229 memset(&opts, 0, sizeof opts); 230 opts.pkg_data = data; 231 opts.pkg_len = len; 232 opts.format = KIT_PKG_FORMAT_KPKG; 233 opts.unpack_dir = unpack_dir; 234 opts.pubkey_bytes = key_bytes; 235 opts.pubkey_len = key_len; 236 if (kit_pkg_verify(ctx, &host, &opts, &res) != KIT_OK) return 1; 237 snprintf(name, namecap, "%s", res.name); 238 snprintf(version, vercap, "%s", res.version); 239 memcpy(signer, res.keyid, KIT_PKG_KEYID_LEN); 240 return up_extract_pkgid(res.trusted, pkgid_hex); 241 } 242 243 keys = driver_release_keys(&nkeys); 244 for (i = 0; i < nkeys; ++i) { 245 memset(&opts, 0, sizeof opts); 246 opts.pkg_data = data; 247 opts.pkg_len = len; 248 opts.format = KIT_PKG_FORMAT_KPKG; 249 opts.unpack_dir = unpack_dir; 250 opts.pubkey_bytes = (const uint8_t*)keys[i].pubkey; 251 opts.pubkey_len = (size_t)strlen(keys[i].pubkey); 252 if (kit_pkg_verify(ctx, &host, &opts, &res) == KIT_OK) { 253 snprintf(name, namecap, "%s", res.name); 254 snprintf(version, vercap, "%s", res.version); 255 memcpy(signer, res.keyid, KIT_PKG_KEYID_LEN); 256 return up_extract_pkgid(res.trusted, pkgid_hex); 257 } 258 } 259 return 1; 260 } 261 262 /* ---------------------------------------------------------------------- */ 263 /* swap + links */ 264 /* ---------------------------------------------------------------------- */ 265 266 /* Atomically point `current` at versions/<version> (temp symlink + rename). */ 267 static int up_flip_current(const UpPaths* p, const char* version) { 268 char tmplink[UP_PATH], target[UP_PATH]; 269 snprintf(tmplink, sizeof tmplink, "%s/.current.tmp", p->home); 270 snprintf(target, sizeof target, "versions/%s", version); /* relative target */ 271 driver_remove_file(tmplink); 272 if (driver_create_symlink(target, tmplink) != 0) return 1; 273 if (driver_rename(tmplink, p->current) != 0) { 274 driver_remove_file(tmplink); 275 return 1; 276 } 277 return 0; 278 } 279 280 /* Rebuild $KIT_HOME/bin from scratch so tools that went away disappear and new 281 * ones appear, each link resolving through `current` to the active version. */ 282 static int up_refresh_links(DriverEnv* env, const UpPaths* p) { 283 char target_exe[UP_PATH]; 284 DriverInstallLinkOpts opts; 285 unsigned done = 0, fail = 0; 286 driver_remove_tree(p->bin); 287 if (driver_mkdir_p(env, p->bin) != 0) return 1; 288 snprintf(target_exe, sizeof target_exe, "%s/bin/kit%s", p->current, 289 driver_host_target().os == KIT_OS_WINDOWS ? ".exe" : ""); 290 memset(&opts, 0, sizeof opts); 291 opts.target_exe = target_exe; 292 opts.tool_tag = UPDATE_TOOL; 293 opts.use_hardlink = (driver_host_target().os == KIT_OS_WINDOWS) ? 1 : 0; 294 opts.force = 1; 295 if (driver_install_link_one(env, p->bin, "kit", &opts) != 0) ++fail; 296 fail += driver_install_links( 297 env, p->bin, DRIVER_GROUP_TOOLCHAIN | DRIVER_GROUP_BYTEUTIL, &opts, &done); 298 return fail ? 1 : 0; 299 } 300 301 /* Flip to an already-installed version (offline) and refresh links. */ 302 static int up_activate(DriverEnv* env, const UpPaths* p, const char* version, 303 int dry_run) { 304 if (!up_version_installed(p, version)) { 305 driver_errf(UPDATE_TOOL, "version %s is not installed", version); 306 return 1; 307 } 308 if (dry_run) { 309 driver_printf("would make kit %s current\n", version); 310 return 0; 311 } 312 if (up_ensure_dirs(env, p) != 0) { 313 driver_errf(UPDATE_TOOL, "cannot create install layout under %s", p->home); 314 return 1; 315 } 316 if (up_flip_current(p, version) != 0) { 317 driver_errf(UPDATE_TOOL, "cannot update the current pointer"); 318 return 1; 319 } 320 if (up_refresh_links(env, p) != 0) 321 driver_errf(UPDATE_TOOL, "warning: one or more bin links could not be laid"); 322 driver_printf("kit %s is now current\n", version); 323 return 0; 324 } 325 326 /* ---------------------------------------------------------------------- */ 327 /* install from verified bytes */ 328 /* ---------------------------------------------------------------------- */ 329 330 /* Verify `data`/`len`, then unpack + activate. expect_version (or NULL) and 331 * expect_pkgid_hex (or NULL) are post-verification cross-checks for the 332 * networked path (the fetched bytes must match the signed channel index). */ 333 static int up_install_bytes(DriverEnv* env, const KitContext* ctx, 334 const UpPaths* p, const uint8_t* data, size_t len, 335 const uint8_t* key_bytes, size_t key_len, 336 int dry_run, const char* expect_version, 337 const char* expect_pkgid_hex, 338 const uint8_t* expect_signer) { 339 char name[KIT_PKG_NAME_MAX], version[KIT_PKG_VERSION_MAX], pkgid[65]; 340 char tmp[UP_PATH], verdir[UP_PATH]; 341 uint8_t signer[KIT_PKG_KEYID_LEN]; 342 343 /* Discovery pass: verify only (no materialize), to learn name/version/id and 344 * confirm a key matches before touching the filesystem. */ 345 if (up_verify(env, ctx, data, len, key_bytes, key_len, NULL, name, 346 sizeof name, version, sizeof version, pkgid, signer) != 0) { 347 driver_errf(UPDATE_TOOL, 348 "signature verification failed (no trusted key matched)"); 349 return 1; 350 } 351 if (strcmp(name, "kit") != 0) { 352 driver_errf(UPDATE_TOOL, "not a kit release (package name is '%s')", name); 353 return 1; 354 } 355 if (expect_version && strcmp(expect_version, version) != 0) { 356 driver_errf(UPDATE_TOOL, "package version %s does not match expected %s", 357 version, expect_version); 358 return 1; 359 } 360 if (expect_pkgid_hex && strcmp(expect_pkgid_hex, pkgid) != 0) { 361 driver_errf(UPDATE_TOOL, 362 "package id does not match the channel index entry"); 363 return 1; 364 } 365 if (expect_signer && 366 memcmp(expect_signer, signer, KIT_PKG_KEYID_LEN) != 0) { 367 driver_errf(UPDATE_TOOL, 368 "package signer does not match authenticated channel signer"); 369 return 1; 370 } 371 if (dry_run) { 372 driver_printf("would install kit %s and make it current\n", version); 373 return 0; 374 } 375 376 if (up_ensure_dirs(env, p) != 0) { 377 driver_errf(UPDATE_TOOL, "cannot create install layout under %s", p->home); 378 return 1; 379 } 380 381 /* Materialize into a clean scratch dir, then rename into versions/<ver>. */ 382 snprintf(tmp, sizeof tmp, "%s/.unpack-tmp", p->downloads); 383 driver_remove_tree(tmp); 384 if (driver_mkdir_p(env, tmp) != 0) { 385 driver_errf(UPDATE_TOOL, "cannot create unpack dir %s", tmp); 386 return 1; 387 } 388 if (up_verify(env, ctx, data, len, key_bytes, key_len, tmp, name, sizeof name, 389 version, sizeof version, pkgid, signer) != 0) { 390 driver_errf(UPDATE_TOOL, "verification failed while unpacking"); 391 driver_remove_tree(tmp); 392 return 1; 393 } 394 snprintf(verdir, sizeof verdir, "%s/%s", p->versions, version); 395 driver_remove_tree(verdir); /* replace on reinstall */ 396 if (driver_rename(tmp, verdir) != 0) { 397 driver_errf(UPDATE_TOOL, "cannot move new version into place: %s", verdir); 398 driver_remove_tree(tmp); 399 return 1; 400 } 401 if (up_flip_current(p, version) != 0) { 402 driver_errf(UPDATE_TOOL, "installed kit %s but could not make it current", 403 version); 404 return 1; 405 } 406 if (up_refresh_links(env, p) != 0) 407 driver_errf(UPDATE_TOOL, "warning: one or more bin links could not be laid"); 408 driver_printf("installed kit %s (now current)\n", version); 409 return 0; 410 } 411 412 /* Install from a local .kpkg file (offline). A local file is a deliberate 413 * choice, so no monotonic version guard applies (it is still fully verified). */ 414 static int up_install_file(DriverEnv* env, const KitContext* ctx, 415 const UpPaths* p, const char* file, 416 const uint8_t* key_bytes, size_t key_len, 417 int dry_run) { 418 KitFileData fd; 419 int rc; 420 if (!up_read(ctx, file, &fd)) { 421 driver_errf(UPDATE_TOOL, "cannot read package: %s", file); 422 return 1; 423 } 424 rc = up_install_bytes(env, ctx, p, fd.data, fd.size, key_bytes, key_len, 425 dry_run, NULL, NULL, NULL); 426 up_release(ctx, &fd); 427 return rc; 428 } 429 430 /* ---------------------------------------------------------------------- */ 431 /* list / prune / rollback */ 432 /* ---------------------------------------------------------------------- */ 433 434 static int up_list(DriverEnv* env, const UpPaths* p) { 435 char vers[UP_MAX_VERS][KIT_PKG_VERSION_MAX]; 436 char cur[KIT_PKG_VERSION_MAX]; 437 int n = up_list_installed(env, p, vers, UP_MAX_VERS), i, have_cur; 438 have_cur = (up_current_version(p, cur, sizeof cur) == 0); 439 if (n == 0) { 440 driver_printf("no kit versions installed under %s\n", p->home); 441 return 0; 442 } 443 for (i = 0; i < n; ++i) 444 driver_printf("%s %s\n", 445 (have_cur && strcmp(vers[i], cur) == 0) ? "*" : " ", vers[i]); 446 return 0; 447 } 448 449 static int up_prune(DriverEnv* env, const UpPaths* p, int dry_run) { 450 char vers[UP_MAX_VERS][KIT_PKG_VERSION_MAX]; 451 char cur[KIT_PKG_VERSION_MAX]; 452 int n = up_list_installed(env, p, vers, UP_MAX_VERS), i, removed = 0; 453 if (up_current_version(p, cur, sizeof cur) != 0) { 454 driver_errf(UPDATE_TOOL, "no current version; refusing to prune"); 455 return 1; 456 } 457 for (i = 0; i < n; ++i) { 458 char dir[UP_PATH]; 459 if (strcmp(vers[i], cur) == 0) continue; 460 if (dry_run) { 461 driver_printf("would remove kit %s\n", vers[i]); 462 ++removed; 463 continue; 464 } 465 snprintf(dir, sizeof dir, "%s/%s", p->versions, vers[i]); 466 if (driver_remove_tree(dir) != 0) 467 driver_errf(UPDATE_TOOL, "warning: could not remove %s", dir); 468 else { 469 driver_printf("removed kit %s\n", vers[i]); 470 ++removed; 471 } 472 } 473 if (removed == 0) driver_printf("nothing to prune (only kit %s)\n", cur); 474 return 0; 475 } 476 477 static int up_rollback(DriverEnv* env, const UpPaths* p, int dry_run) { 478 char vers[UP_MAX_VERS][KIT_PKG_VERSION_MAX]; 479 char cur[KIT_PKG_VERSION_MAX]; 480 int n = up_list_installed(env, p, vers, UP_MAX_VERS), i, ci = -1; 481 if (up_current_version(p, cur, sizeof cur) != 0) { 482 driver_errf(UPDATE_TOOL, "no current version to roll back from"); 483 return 1; 484 } 485 for (i = 0; i < n; ++i) 486 if (strcmp(vers[i], cur) == 0) ci = i; 487 if (ci <= 0) { 488 driver_errf(UPDATE_TOOL, "no previous version installed to roll back to"); 489 return 1; 490 } 491 if (up_activate(env, p, vers[ci - 1], dry_run) != 0) return 1; 492 driver_printf( 493 "note: rolled back the active version only; channel tracking is " 494 "unchanged, so the next `kit update` will re-upgrade unless you pin " 495 "with --version\n"); 496 return 0; 497 } 498 499 /* ---------------------------------------------------------------------- */ 500 /* networked: channel index */ 501 /* ---------------------------------------------------------------------- */ 502 503 static const char* up_index_url(const char* index_opt) { 504 const char* env; 505 const char* compiled; 506 if (index_opt) return index_opt; 507 env = driver_getenv("KIT_UPDATE_INDEX_URL"); 508 if (env && *env) return env; 509 compiled = driver_release_index_url(); 510 return (compiled && *compiled) ? compiled : NULL; 511 } 512 513 /* Fetch + parse the channel index and locate this host's entry. Returns 0 on 514 * success (idx filled, *host points into idx). Both INDEX and INDEX.minisig are 515 * fetched through curl; the detached signature is authenticated before any 516 * index bytes are parsed. */ 517 static int up_load_index(DriverEnv* env, const KitContext* ctx, 518 const char* download_dir, const char* index_url, 519 const uint8_t* key_bytes, size_t key_len, 520 KitReleaseIndex* idx, const KitReleaseHost** host, 521 uint8_t signer[KIT_PKG_KEYID_LEN]) { 522 char dest[UP_PATH], sig_dest[UP_PATH], sig_url[UP_PATH]; 523 KitFileData fd, sigfd; 524 const char* triple = kit_host_triple(); 525 unsigned i; 526 int fd_loaded = 0, sig_loaded = 0, rc = 1; 527 int n = snprintf(sig_url, sizeof sig_url, "%s.minisig", index_url); 528 if (n < 0 || (size_t)n >= sizeof sig_url) { 529 driver_errf(UPDATE_TOOL, "channel signature URL is too long"); 530 return 1; 531 } 532 snprintf(dest, sizeof dest, "%s/channel.index", download_dir); 533 snprintf(sig_dest, sizeof sig_dest, "%s/channel.index.minisig", download_dir); 534 if (driver_mkdir_p(env, download_dir) != 0) { 535 driver_errf(UPDATE_TOOL, "could not create update download directory"); 536 return 1; 537 } 538 if (driver_fetch_url(index_url, dest) != 0) { 539 driver_errf(UPDATE_TOOL, "could not fetch channel index from %s", index_url); 540 return 1; 541 } 542 if (driver_fetch_url(sig_url, sig_dest) != 0) { 543 driver_errf(UPDATE_TOOL, "could not fetch channel signature from %s", 544 sig_url); 545 return 1; 546 } 547 if (!up_read(ctx, dest, &fd)) { 548 driver_errf(UPDATE_TOOL, "could not read fetched channel index"); 549 return 1; 550 } 551 fd_loaded = 1; 552 if (!up_read(ctx, sig_dest, &sigfd)) { 553 driver_errf(UPDATE_TOOL, "could not read fetched channel signature"); 554 goto done; 555 } 556 sig_loaded = 1; 557 if (up_verify_detached(ctx, fd.data, fd.size, sigfd.data, sigfd.size, 558 key_bytes, key_len, signer) != 0) { 559 driver_errf(UPDATE_TOOL, "channel index authentication failed"); 560 goto done; 561 } 562 if (kit_release_index_parse(ctx, fd.data, fd.size, idx) != KIT_OK) { 563 goto done; /* parse already emitted detail via ctx->diag */ 564 } 565 *host = NULL; 566 for (i = 0; i < idx->n_hosts; ++i) 567 if (strcmp(idx->hosts[i].target, triple) == 0) { 568 *host = &idx->hosts[i]; 569 rc = 0; 570 goto done; 571 } 572 driver_errf(UPDATE_TOOL, "channel has no release for this host (%s)", triple); 573 done: 574 if (sig_loaded) up_release(ctx, &sigfd); 575 if (fd_loaded) up_release(ctx, &fd); 576 return rc; 577 } 578 579 /* Download the host's .kpkg (trying each mirror), verify, and install. */ 580 static int up_fetch_install(DriverEnv* env, const KitContext* ctx, 581 const UpPaths* p, const char* download_dir, 582 const KitReleaseIndex* idx, 583 const KitReleaseHost* host, const uint8_t* key_bytes, 584 size_t key_len, 585 const uint8_t signer[KIT_PKG_KEYID_LEN], 586 int dry_run) { 587 char dest[UP_PATH], expect_id[65]; 588 KitFileData fd; 589 unsigned i; 590 int got = 0, rc; 591 if (!host->has_kpkg || host->n_urls == 0) { 592 driver_errf(UPDATE_TOOL, "channel index has no .kpkg URL for this host"); 593 return 1; 594 } 595 kit_hex_encode(expect_id, host->kpkg_id, KIT_CAS_HASH_LEN); 596 snprintf(dest, sizeof dest, "%s/kit-%s-%s.kpkg", download_dir, idx->version, 597 host->target); 598 for (i = 0; i < host->n_urls; ++i) { 599 if (driver_fetch_url(host->urls[i], dest) == 0) { 600 got = 1; 601 break; 602 } 603 driver_errf(UPDATE_TOOL, "mirror failed: %s", host->urls[i]); 604 } 605 if (!got) { 606 driver_errf(UPDATE_TOOL, "all mirrors failed for kit %s", idx->version); 607 return 1; 608 } 609 if (!up_read(ctx, dest, &fd)) { 610 driver_errf(UPDATE_TOOL, "cannot read downloaded package: %s", dest); 611 return 1; 612 } 613 rc = up_install_bytes(env, ctx, p, fd.data, fd.size, key_bytes, key_len, 614 dry_run, idx->version, expect_id, signer); 615 up_release(ctx, &fd); 616 return rc; 617 } 618 619 /* ---------------------------------------------------------------------- */ 620 /* help + main */ 621 /* ---------------------------------------------------------------------- */ 622 623 void driver_help_update(void) { 624 driver_printf( 625 "kit update — verify and install kit, and manage installed versions\n" 626 "\n" 627 "USAGE\n" 628 " kit update [OPTIONS] [<file.kpkg>]\n" 629 "\n" 630 "DESCRIPTION\n" 631 " Installs a kit release into the single-root layout under $KIT_HOME\n" 632 " (default $XDG_DATA_HOME/kit, i.e. ~/.local/share/kit), verifying its\n" 633 " signature against the built-in release key set (or --key) before any\n" 634 " change. A local <file.kpkg> installs fully offline. Network operations\n" 635 " use curl and authenticate INDEX.minisig before parsing INDEX. Channel\n" 636 " selection is --index, then KIT_UPDATE_INDEX_URL, then the compiled\n" 637 " stable URL (development builds may have no compiled URL).\n" 638 "\n" 639 " Put $KIT_HOME/bin on PATH once; `current` is flipped atomically and\n" 640 " the bin links refreshed on each install. All versions are retained,\n" 641 " so any prior one is an instant offline flip via --version / --rollback.\n" 642 "\n" 643 "OPTIONS\n" 644 " <file.kpkg> verify + install a local package (offline)\n" 645 " --from <file.kpkg> explicit local-file form\n" 646 " --version <ver> flip to an installed version, else fetch it\n" 647 " --check report installed vs available; change nothing\n" 648 " --list list installed versions, mark current\n" 649 " --rollback flip to the previous installed version\n" 650 " --prune remove all non-current installed versions\n" 651 " --key <pubkey> verify against an explicit minisign public key\n" 652 " --index <url> channel index URL (overrides env/compiled URL)\n" 653 " --allow-downgrade permit a channel-driven downgrade\n" 654 " --dry-run print the plan; change nothing\n" 655 " -h, --help show this help\n" 656 "\n" 657 "STATE AND ENVIRONMENT\n" 658 " KIT_HOME installation root\n" 659 " XDG_DATA_HOME parent of default KIT_HOME\n" 660 " KIT_UPDATE_INDEX_URL channel index when --index is absent\n" 661 " KIT_HOME contains versions/, current, bin/, config/, and downloaded\n" 662 " update state. --index overrides KIT_UPDATE_INDEX_URL, which overrides\n" 663 " the compiled stable URL. Put only\n" 664 " $KIT_HOME/bin on PATH after a successful install. Updates retain old\n" 665 " versions until --prune; --rollback and --version VER switch locally\n" 666 " when possible. Here --version requires VER; use `kit --version` for\n" 667 " the running binary's identity.\n" 668 "\n" 669 "EXAMPLES\n" 670 " state=$(mktemp -d)\n" 671 " mkdir -p \"$state/home\" \"$state/data\" \"$state/cache\"\n" 672 " HOME=\"$state/home\" XDG_DATA_HOME=\"$state/data\" \\\n" 673 " XDG_CACHE_HOME=\"$state/cache\" KIT_HOME=\"$state/kit\" \\\n" 674 " kit update --key release.pub --from release.kpkg --dry-run\n" 675 " HOME=\"$state/home\" XDG_DATA_HOME=\"$state/data\" \\\n" 676 " KIT_HOME=\"$state/kit\" kit update --key release.pub release.kpkg\n" 677 " KIT_UPDATE_INDEX_URL=https://example.invalid/stable.index \\\n" 678 " kit update --check\n" 679 "\n" 680 "OUTPUT\n" 681 " Plans, version lists, and successful actions go to stdout; diagnostics\n" 682 " go to stderr. --dry-run and --check do not modify the installation.\n" 683 "\n" 684 "EXIT CODES\n" 685 " 0 successful action or authenticated check\n" 686 " 1 missing channel, fetch/auth/validation/install/I/O failure\n" 687 " 2 bad usage\n"); 688 } 689 690 int driver_update(int argc, char** argv) { 691 DriverEnv env; 692 KitContext ctx; 693 UpPaths paths; 694 const char *file = NULL, *want_version = NULL, *keyfile = NULL; 695 const char* index_opt = NULL; 696 int do_check = 0, do_list = 0, do_prune = 0, do_rollback = 0; 697 int dry_run = 0, allow_downgrade = 0, opts_done = 0, i, rc = 2; 698 char transient_dir[UP_PATH]; 699 int transient_fetch_active = 0, transient_home_existed = 1; 700 KitFileData keyfd; 701 int key_loaded = 0; 702 const uint8_t* key_bytes = NULL; 703 size_t key_len = 0; 704 705 if (driver_argv_wants_help(argc, argv, 1)) { 706 driver_help_update(); 707 return 0; 708 } 709 710 for (i = 1; i < argc; ++i) { 711 const char* a = argv[i]; 712 if (!opts_done && driver_streq(a, "--")) { 713 opts_done = 1; 714 continue; 715 } 716 if (!opts_done && a[0] == '-' && a[1] != '\0') { 717 if (driver_streq(a, "--from") && i + 1 < argc) 718 file = argv[++i]; 719 else if (driver_streq(a, "--version") && i + 1 < argc) 720 want_version = argv[++i]; 721 else if (driver_streq(a, "--key") && i + 1 < argc) 722 keyfile = argv[++i]; 723 else if (driver_streq(a, "--index") && i + 1 < argc) 724 index_opt = argv[++i]; 725 else if (driver_streq(a, "--check")) 726 do_check = 1; 727 else if (driver_streq(a, "--list")) 728 do_list = 1; 729 else if (driver_streq(a, "--prune")) 730 do_prune = 1; 731 else if (driver_streq(a, "--rollback")) 732 do_rollback = 1; 733 else if (driver_streq(a, "--allow-downgrade")) 734 allow_downgrade = 1; 735 else if (driver_streq(a, "--dry-run") || driver_streq(a, "-n")) 736 dry_run = 1; 737 else { 738 driver_errf(UPDATE_TOOL, "unknown option: %s", a); 739 return 2; 740 } 741 continue; 742 } 743 if (!file) 744 file = a; 745 else { 746 driver_errf(UPDATE_TOOL, "unexpected argument: %s", a); 747 return 2; 748 } 749 } 750 751 /* --check is specifically an authenticated channel-index query. Do not 752 * let an accompanying local action bypass that query (or, for an install, 753 * mutate state despite the read-only spelling). */ 754 if (do_check && 755 (file || want_version || do_list || do_prune || do_rollback)) { 756 driver_errf(UPDATE_TOOL, 757 "--check cannot be combined with a package, --version, " 758 "--list, --prune, or --rollback"); 759 return 2; 760 } 761 762 driver_env_init(&env); 763 ctx = driver_env_to_context(&env); 764 if (up_resolve_paths(&paths) != 0) { 765 rc = 1; 766 goto done; 767 } 768 769 if (keyfile) { 770 if (!up_read(&ctx, keyfile, &keyfd)) { 771 driver_errf(UPDATE_TOOL, "cannot read public key: %s", keyfile); 772 rc = 1; 773 goto done; 774 } 775 key_loaded = 1; 776 key_bytes = keyfd.data; 777 key_len = keyfd.size; 778 } 779 780 /* Local operations first. */ 781 if (do_list) { 782 rc = up_list(&env, &paths); 783 goto done; 784 } 785 if (do_prune) { 786 rc = up_prune(&env, &paths, dry_run); 787 goto done; 788 } 789 if (do_rollback) { 790 rc = up_rollback(&env, &paths, dry_run); 791 goto done; 792 } 793 794 /* Local-file install (offline; deliberate, so no monotonic guard). */ 795 if (file) { 796 rc = up_install_file(&env, &ctx, &paths, file, key_bytes, key_len, dry_run); 797 goto done; 798 } 799 800 /* --version: offline flip if installed, else fetch that exact version. */ 801 if (want_version && up_version_installed(&paths, want_version)) { 802 rc = up_activate(&env, &paths, want_version, dry_run); 803 goto done; 804 } 805 806 /* Networked paths need a channel index URL. */ 807 { 808 const char* idx_url = up_index_url(index_opt); 809 const char* download_dir = paths.downloads; 810 KitReleaseIndex* idx; 811 const KitReleaseHost* host = NULL; 812 char cur[KIT_PKG_VERSION_MAX]; 813 uint8_t channel_signer[KIT_PKG_KEYID_LEN]; 814 int have_cur, cmp; 815 816 if (!idx_url) { 817 if (want_version) 818 driver_errf(UPDATE_TOOL, 819 "version %s is not installed; networked fetch needs " 820 "--index <url> or $KIT_UPDATE_INDEX_URL", 821 want_version); 822 else 823 driver_errf(UPDATE_TOOL, 824 "no channel index configured; pass a local <file.kpkg>, " 825 "or set --index <url> / $KIT_UPDATE_INDEX_URL"); 826 rc = 1; 827 if (do_check) { /* still report what is installed locally */ 828 up_list(&env, &paths); 829 } 830 goto done; 831 } 832 833 /* --check and --dry-run may fetch/authenticate bytes, but must leave no 834 * managed cache or installation state behind. Use a reserved transient 835 * directory and remove the newly-created KIT_HOME as well when it did not 836 * exist on entry. */ 837 if (do_check || dry_run) { 838 transient_home_existed = driver_path_lexists(paths.home); 839 snprintf(transient_dir, sizeof transient_dir, "%s/.update-readonly-tmp", 840 paths.home); 841 driver_remove_tree(transient_dir); 842 download_dir = transient_dir; 843 transient_fetch_active = 1; 844 } 845 846 idx = (KitReleaseIndex*)driver_alloc_zeroed(&env, sizeof *idx); 847 if (!idx) { 848 driver_errf(UPDATE_TOOL, "out of memory"); 849 rc = 1; 850 goto done; 851 } 852 if (up_load_index(&env, &ctx, download_dir, idx_url, key_bytes, key_len, 853 idx, &host, channel_signer) != 0) { 854 driver_free(&env, idx, sizeof *idx); 855 rc = 1; 856 goto done; 857 } 858 859 have_cur = (up_current_version(&paths, cur, sizeof cur) == 0); 860 861 if (do_check) { 862 driver_printf("channel %s: latest %s for %s\n", idx->channel, 863 idx->version, host->target); 864 if (have_cur) 865 driver_printf("installed current: %s\n", cur); 866 else 867 driver_printf("installed current: (none)\n"); 868 driver_free(&env, idx, sizeof *idx); 869 rc = 0; 870 goto done; 871 } 872 873 if (want_version) { 874 /* The single-version channel index can only fetch its own version. */ 875 if (strcmp(want_version, idx->version) != 0) { 876 driver_errf(UPDATE_TOOL, 877 "version %s is not installed and not the current channel " 878 "release (%s)", 879 want_version, idx->version); 880 driver_free(&env, idx, sizeof *idx); 881 rc = 1; 882 goto done; 883 } 884 } else { 885 /* No-arg channel update: monotonic unless --allow-downgrade. */ 886 if (have_cur && kit_calver_compare(idx->version, cur, &cmp) == KIT_OK) { 887 if (cmp == 0) { 888 driver_printf("already up to date (kit %s)\n", cur); 889 driver_free(&env, idx, sizeof *idx); 890 rc = 0; 891 goto done; 892 } 893 if (cmp < 0 && !allow_downgrade) { 894 driver_errf(UPDATE_TOOL, 895 "channel offers kit %s, older than installed %s; pass " 896 "--allow-downgrade to move back", 897 idx->version, cur); 898 driver_free(&env, idx, sizeof *idx); 899 rc = 1; 900 goto done; 901 } 902 } 903 } 904 905 /* A binary launched outside the managed layout still supplies a downgrade 906 * floor. Do not silently replace a newer running Kit with an older channel 907 * when there is no current symlink to compare against. An explicit local 908 * package remains a deliberate offline choice and bypasses this policy. */ 909 if (!have_cur && !allow_downgrade && 910 kit_calver_compare(idx->version, kit_version_string(), &cmp) == KIT_OK && 911 cmp < 0) { 912 driver_errf(UPDATE_TOOL, 913 "channel offers kit %s, older than running kit %s; pass " 914 "--allow-downgrade to move back", 915 idx->version, kit_version_string()); 916 driver_free(&env, idx, sizeof *idx); 917 rc = 1; 918 goto done; 919 } 920 921 rc = up_fetch_install(&env, &ctx, &paths, download_dir, idx, host, 922 key_bytes, key_len, channel_signer, dry_run); 923 driver_free(&env, idx, sizeof *idx); 924 goto done; 925 } 926 927 done: 928 if (transient_fetch_active) { 929 driver_remove_tree(transient_dir); 930 if (!transient_home_existed) driver_remove_tree(paths.home); 931 } 932 if (key_loaded) up_release(&ctx, &keyfd); 933 driver_env_fini(&env); 934 return rc; 935 }