kit

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

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 }