kit

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

posix.c (52122B)


      1 /* POSIX-shared environment: file I/O, mkdir, sigint, exec_dual registry,
      2  * single-mapping execmem, monotonic clock, stdin/edit, dlsym resolver,
      3  * driver_env_init for hosts where the POSIX TUs are compiled (Mac, Linux,
      4  * FreeBSD). Each function here behaves identically on those three; per-OS
      5  * specifics are isolated in macos.c / linux.c / freebsd.c. */
      6 
      7 #include <dirent.h>
      8 #include <errno.h>
      9 #include <fcntl.h>
     10 #include <pthread.h>
     11 #include <signal.h>
     12 #include <stdint.h>
     13 #include <stdio.h>
     14 #include <stdlib.h>
     15 #include <string.h>
     16 #include <sys/mman.h>
     17 #include <sys/stat.h>
     18 #include <sys/wait.h>
     19 #include <termios.h>
     20 #include <time.h>
     21 #include <unistd.h>
     22 
     23 #include "env_posix.h"
     24 
     25 extern char** environ;
     26 
     27 int driver_path_canonicalize(DriverEnv* env, const char* path, char** out,
     28                              size_t* out_size) {
     29   char* resolved;
     30   char* copy;
     31   size_t size;
     32   if (!env || !path || !path[0] || !out || !out_size) return 1;
     33   resolved = realpath(path, NULL);
     34   if (!resolved) return 1;
     35   size = driver_strlen(resolved) + 1u;
     36   copy = (char*)driver_alloc(env, size);
     37   if (!copy) {
     38     free(resolved);
     39     return 1;
     40   }
     41   driver_memcpy(copy, resolved, size);
     42   free(resolved);
     43   *out = copy;
     44   *out_size = size;
     45   return 0;
     46 }
     47 
     48 /* ---------------- exec memory: single-mapping core + registry ----------------
     49  */
     50 
     51 int kit_to_posix_prot(int prot) {
     52   int p = 0;
     53   if (prot & KIT_PROT_READ) p |= PROT_READ;
     54   if (prot & KIT_PROT_WRITE) p |= PROT_WRITE;
     55   if (prot & KIT_PROT_EXEC) p |= PROT_EXEC;
     56   return p;
     57 }
     58 
     59 /* Registry of EXEC reservations with distinct write/runtime aliases. The
     60  * dbg_os code_write_begin path uses this to translate a runtime address
     61  * into the corresponding write alias on dual-mapping hosts. Single-mapping
     62  * reservations (write == runtime) are not registered. JITs typically hold
     63  * 1-2 reservations live so a linked list keeps the lookup trivial. */
     64 typedef struct ExecDualNode {
     65   void* write_base;
     66   void* runtime_base;
     67   size_t size;
     68   struct ExecDualNode* next;
     69 } ExecDualNode;
     70 
     71 static ExecDualNode* g_jit_dual_map;
     72 static pthread_mutex_t g_jit_dual_map_mu = PTHREAD_MUTEX_INITIALIZER;
     73 
     74 void exec_dual_register(void* write_base, void* runtime_base, size_t size) {
     75   ExecDualNode* n;
     76   if (write_base == runtime_base) return;
     77   n = (ExecDualNode*)malloc(sizeof(*n));
     78   if (!n) return; /* registry is best-effort; lookup will fail open */
     79   n->write_base = write_base;
     80   n->runtime_base = runtime_base;
     81   n->size = size;
     82   pthread_mutex_lock(&g_jit_dual_map_mu);
     83   n->next = g_jit_dual_map;
     84   g_jit_dual_map = n;
     85   pthread_mutex_unlock(&g_jit_dual_map_mu);
     86 }
     87 
     88 void exec_dual_unregister(void* runtime_base) {
     89   ExecDualNode** pp;
     90   pthread_mutex_lock(&g_jit_dual_map_mu);
     91   for (pp = &g_jit_dual_map; *pp; pp = &(*pp)->next) {
     92     if ((*pp)->runtime_base == runtime_base) {
     93       ExecDualNode* dead = *pp;
     94       *pp = dead->next;
     95       free(dead);
     96       break;
     97     }
     98   }
     99   pthread_mutex_unlock(&g_jit_dual_map_mu);
    100 }
    101 
    102 int exec_dual_lookup(void* runtime_addr, size_t n, void** write_out) {
    103   ExecDualNode* cur;
    104   uintptr_t a = (uintptr_t)runtime_addr;
    105   pthread_mutex_lock(&g_jit_dual_map_mu);
    106   for (cur = g_jit_dual_map; cur; cur = cur->next) {
    107     uintptr_t base = (uintptr_t)cur->runtime_base;
    108     if (a >= base && a + n <= base + cur->size) {
    109       *write_out = (void*)((uintptr_t)cur->write_base + (a - base));
    110       pthread_mutex_unlock(&g_jit_dual_map_mu);
    111       return 0;
    112     }
    113   }
    114   pthread_mutex_unlock(&g_jit_dual_map_mu);
    115   return 1;
    116 }
    117 
    118 KitStatus execmem_reserve_single(size_t size, KitExecMemRegion* out) {
    119   void* p =
    120       mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
    121   if (p == MAP_FAILED) return KIT_NOMEM;
    122   out->write = p;
    123   out->runtime = p;
    124   out->size = size;
    125   out->token = NULL; /* munmap suffices on release */
    126   return KIT_OK;
    127 }
    128 
    129 static KitStatus execmem_reserve(void* user, size_t size, int prot,
    130                                  KitExecMemRegion* out) {
    131   (void)user;
    132   if (!out || !size) return KIT_INVALID;
    133   if (prot & KIT_PROT_EXEC) return os_execmem_reserve_exec(size, out);
    134   return execmem_reserve_single(size, out);
    135 }
    136 
    137 static KitStatus execmem_protect(void* user, void* addr, size_t size,
    138                                  int prot) {
    139   (void)user;
    140   return mprotect(addr, size, kit_to_posix_prot(prot)) == 0 ? KIT_OK : KIT_ERR;
    141 }
    142 
    143 static void execmem_release(void* user, KitExecMemRegion* region) {
    144   (void)user;
    145   if (!region || !region->size) return;
    146   if (region->token) {
    147     ExecMemToken* tok = (ExecMemToken*)region->token;
    148     if (tok->runtime_addr && tok->runtime_addr != tok->write_addr) {
    149       exec_dual_unregister(tok->runtime_addr);
    150       munmap(tok->runtime_addr, tok->size);
    151     }
    152     if (tok->write_addr) munmap(tok->write_addr, tok->size);
    153     free(tok);
    154   } else if (region->write) {
    155     munmap(region->write, region->size);
    156   }
    157   region->write = NULL;
    158   region->runtime = NULL;
    159   region->size = 0;
    160   region->token = NULL;
    161 }
    162 
    163 static void execmem_flush_icache(void* user, void* addr, size_t size) {
    164   (void)user;
    165   env_flush_icache(addr, size);
    166 }
    167 
    168 size_t driver_host_page_size(void) {
    169   long p = sysconf(_SC_PAGESIZE);
    170   return (p > 0) ? (size_t)p : (size_t)0x4000;
    171 }
    172 
    173 KitExecMem g_execmem_posix; /* page_size set in driver_env_init */
    174 
    175 /* ---------------- fd writer ---------------- */
    176 
    177 /* Output is buffered: object emit and `-E` text both drive the writer in many
    178  * tiny chunks (a token, a 16-byte nlist entry), and one write() syscall per
    179  * chunk dominated compile/link wall time. The buffer coalesces them into
    180  * page-sized flushes. Invariant: the buffer holds a contiguous run ending at
    181  * the logical position `pos`, so the fd's real offset is always pos-buf_len;
    182  * every seek flushes first to keep that true. */
    183 #define FDW_BUF_CAP 65536u
    184 
    185 typedef struct DriverFdWriter {
    186   KitWriter base; /* must be first; libkit reads via this */
    187   KitHeap* heap;
    188   int fd;
    189   KitStatus status;
    190   uint64_t pos;   /* logical position of the next byte (incl. buffered) */
    191   size_t buf_len; /* bytes buffered but not yet written to fd */
    192   /* Atomic output: when set, bytes go to tmp_path and a successful close
    193    * renames it over final_path, so every build lands a *fresh inode*. This
    194    * avoids clobbering a still-referenced file and — crucially on macOS —
    195    * sidesteps the kernel's per-vnode code-signature cache, which otherwise
    196    * serves a stale "invalid signature" verdict for a re-signed executable
    197    * rewritten in place. NULL for plain fd writers (stdout, fallback). */
    198   char* tmp_path;
    199   char* final_path;
    200   unsigned char buf[FDW_BUF_CAP];
    201 } DriverFdWriter;
    202 
    203 /* Drain `data`/`n` straight to the fd (no buffering, no pos accounting). */
    204 static KitStatus fdw_raw(DriverFdWriter* fw, const unsigned char* p, size_t n) {
    205   while (n > 0) {
    206     ssize_t k = write(fw->fd, p, n);
    207     if (k < 0) {
    208       fw->status = KIT_IO;
    209       return KIT_IO;
    210     }
    211     p += (size_t)k;
    212     n -= (size_t)k;
    213   }
    214   return KIT_OK;
    215 }
    216 
    217 static KitStatus fdw_flush(DriverFdWriter* fw) {
    218   size_t n = fw->buf_len;
    219   if (n == 0) return fw->status;
    220   fw->buf_len = 0;
    221   return fdw_raw(fw, fw->buf, n);
    222 }
    223 
    224 static KitStatus fdw_write(KitWriter* w, const void* data, size_t n) {
    225   DriverFdWriter* fw = (DriverFdWriter*)w;
    226   const unsigned char* p = (const unsigned char*)data;
    227   if (fw->status != KIT_OK) return fw->status;
    228   if (n == 0) return KIT_OK;
    229   /* Large writes bypass the buffer: flush what is pending, then stream the
    230    * payload directly so a multi-MB section never round-trips through buf. */
    231   if (n >= FDW_BUF_CAP) {
    232     if (fdw_flush(fw) != KIT_OK) return fw->status;
    233     if (fdw_raw(fw, p, n) != KIT_OK) return fw->status;
    234     fw->pos += (uint64_t)n;
    235     return KIT_OK;
    236   }
    237   if (fw->buf_len + n > FDW_BUF_CAP) {
    238     if (fdw_flush(fw) != KIT_OK) return fw->status;
    239   }
    240   memcpy(fw->buf + fw->buf_len, p, n);
    241   fw->buf_len += n;
    242   fw->pos += (uint64_t)n;
    243   return KIT_OK;
    244 }
    245 
    246 static KitStatus fdw_seek(KitWriter* w, uint64_t off) {
    247   DriverFdWriter* fw = (DriverFdWriter*)w;
    248   if (fw->status != KIT_OK) return fw->status;
    249   if (fdw_flush(fw) != KIT_OK) return fw->status;
    250   if (lseek(fw->fd, (off_t)off, SEEK_SET) < 0) {
    251     fw->status = KIT_IO;
    252     return KIT_IO;
    253   }
    254   fw->pos = off;
    255   return KIT_OK;
    256 }
    257 
    258 static uint64_t fdw_tell(KitWriter* w) { return ((DriverFdWriter*)w)->pos; }
    259 static KitStatus fdw_status(KitWriter* w) {
    260   return ((DriverFdWriter*)w)->status;
    261 }
    262 
    263 static void fdw_close(KitWriter* w) {
    264   DriverFdWriter* fw = (DriverFdWriter*)w;
    265   KitStatus st;
    266   fdw_flush(fw);
    267   st = fw->status;
    268   if (fw->fd >= 0) {
    269     /* fsync the data before the rename so a crash can't leave the final
    270      * path pointing at a renamed-but-unflushed (truncated) file. */
    271     if (st == KIT_OK && fw->tmp_path && fsync(fw->fd) != 0) st = KIT_IO;
    272     if (close(fw->fd) != 0 && st == KIT_OK) st = KIT_IO;
    273   }
    274   if (fw->tmp_path) {
    275     if (st == KIT_OK && rename(fw->tmp_path, fw->final_path) != 0) {
    276       st = KIT_IO;
    277       fw->status = KIT_IO;
    278     }
    279     /* On any failure the temp never became the target: remove it so a failed
    280      * build leaves the previous output (if any) untouched and no litter. */
    281     if (st != KIT_OK) unlink(fw->tmp_path);
    282     fw->heap->free(fw->heap, fw->tmp_path, strlen(fw->tmp_path) + 1u);
    283     if (fw->final_path)
    284       fw->heap->free(fw->heap, fw->final_path, strlen(fw->final_path) + 1u);
    285   }
    286   fw->heap->free(fw->heap, fw, sizeof(*fw));
    287 }
    288 
    289 static KitWriter* driver_writer_fd(KitHeap* h, int fd) {
    290   DriverFdWriter* fw =
    291       (DriverFdWriter*)h->alloc(h, sizeof(*fw), _Alignof(DriverFdWriter));
    292   if (!fw) return NULL;
    293   fw->base.write = fdw_write;
    294   fw->base.seek = fdw_seek;
    295   fw->base.tell = fdw_tell;
    296   fw->base.status = fdw_status;
    297   fw->base.close = fdw_close;
    298   fw->heap = h;
    299   fw->fd = fd;
    300   fw->status = KIT_OK;
    301   fw->pos = 0;
    302   fw->buf_len = 0;
    303   fw->tmp_path = NULL;
    304   fw->final_path = NULL;
    305   return &fw->base;
    306 }
    307 
    308 /* Duplicate a NUL-terminated string into the heap (NULL on OOM). */
    309 static char* posix_strdup(KitHeap* h, const char* s) {
    310   size_t n = strlen(s) + 1u;
    311   char* p = (char*)h->alloc(h, n, 1);
    312   if (p) memcpy(p, s, n);
    313   return p;
    314 }
    315 
    316 /* Stdout writer routes through stdio so it shares libc's buffer with
    317  * driver_printf. */
    318 typedef struct DriverStdioWriter {
    319   KitWriter base;
    320   KitHeap* heap;
    321   FILE* fp;
    322   KitStatus status;
    323 } DriverStdioWriter;
    324 
    325 static KitStatus stdio_w_write(KitWriter* w, const void* data, size_t n) {
    326   DriverStdioWriter* sw = (DriverStdioWriter*)w;
    327   if (n) {
    328     size_t got = fwrite(data, 1, n, sw->fp);
    329     if (got != n) {
    330       sw->status = KIT_IO;
    331       return KIT_IO;
    332     }
    333   }
    334   return KIT_OK;
    335 }
    336 static KitStatus stdio_w_seek(KitWriter* w, uint64_t off) {
    337   DriverStdioWriter* sw = (DriverStdioWriter*)w;
    338   return fseek(sw->fp, (long)off, SEEK_SET) == 0 ? KIT_OK : KIT_IO;
    339 }
    340 static uint64_t stdio_w_tell(KitWriter* w) {
    341   long t = ftell(((DriverStdioWriter*)w)->fp);
    342   return t < 0 ? 0u : (uint64_t)t;
    343 }
    344 static KitStatus stdio_w_status(KitWriter* w) {
    345   DriverStdioWriter* sw = (DriverStdioWriter*)w;
    346   if (sw->status != KIT_OK) return sw->status;
    347   return ferror(sw->fp) ? KIT_IO : KIT_OK;
    348 }
    349 static void stdio_w_close(KitWriter* w) {
    350   DriverStdioWriter* sw = (DriverStdioWriter*)w;
    351   fflush(sw->fp); /* flush but do not close the borrowed stdio stream */
    352   sw->heap->free(sw->heap, sw, sizeof(*sw));
    353 }
    354 
    355 static KitWriter* driver_stdio_writer(DriverEnv* e, FILE* fp) {
    356   DriverStdioWriter* sw = (DriverStdioWriter*)e->heap->alloc(
    357       e->heap, sizeof(*sw), _Alignof(DriverStdioWriter));
    358   if (!sw) return NULL;
    359   sw->base.write = stdio_w_write;
    360   sw->base.seek = stdio_w_seek;
    361   sw->base.tell = stdio_w_tell;
    362   sw->base.status = stdio_w_status;
    363   sw->base.close = stdio_w_close;
    364   sw->heap = e->heap;
    365   sw->fp = fp;
    366   sw->status = KIT_OK;
    367   return &sw->base;
    368 }
    369 
    370 KitWriter* driver_stdout_writer(DriverEnv* e) {
    371   return driver_stdio_writer(e, stdout);
    372 }
    373 
    374 KitWriter* driver_stderr_writer(DriverEnv* e) {
    375   return driver_stdio_writer(e, stderr);
    376 }
    377 
    378 void driver_writer_abort(KitWriter* writer) {
    379   if (!writer) return;
    380   if (writer->close == fdw_close) {
    381     ((DriverFdWriter*)writer)->status = KIT_ERR;
    382   } else if (writer->close == stdio_w_close) {
    383     ((DriverStdioWriter*)writer)->status = KIT_ERR;
    384   }
    385 }
    386 
    387 const char* const* driver_environ(void) { return (const char* const*)environ; }
    388 
    389 /* ---------------- file_io (POSIX open/read/write/stat) ---------------- */
    390 
    391 static KitStatus posix_read_all(void* user, const char* path,
    392                                 KitFileData* out) {
    393   DriverEnv* env = (DriverEnv*)user;
    394   int fd;
    395   struct stat sb;
    396   size_t size;
    397   size_t got;
    398   void* buf;
    399 
    400   fd = open(path, O_RDONLY);
    401   if (fd < 0) return KIT_NOT_FOUND;
    402   if (fstat(fd, &sb) < 0) {
    403     close(fd);
    404     return KIT_IO;
    405   }
    406   size = (size_t)sb.st_size;
    407   buf = size ? env->heap->alloc(env->heap, size, 1) : NULL;
    408   if (size && !buf) {
    409     close(fd);
    410     return KIT_NOMEM;
    411   }
    412 
    413   got = 0;
    414   while (got < size) {
    415     ssize_t n = read(fd, (unsigned char*)buf + got, size - got);
    416     if (n <= 0) {
    417       env->heap->free(env->heap, buf, size);
    418       close(fd);
    419       return KIT_IO;
    420     }
    421     got += (size_t)n;
    422   }
    423   close(fd);
    424 
    425   out->data = (const uint8_t*)buf;
    426   out->size = size;
    427   out->token = buf;
    428   return KIT_OK;
    429 }
    430 
    431 static void posix_release(void* user, KitFileData* d) {
    432   DriverEnv* env = (DriverEnv*)user;
    433   if (d->token) env->heap->free(env->heap, d->token, d->size);
    434   d->data = NULL;
    435   d->size = 0;
    436   d->token = NULL;
    437 }
    438 
    439 /* Open `path` for output, atomically: write to a sibling temp file and rename
    440  * it over `path` on a clean close (see DriverFdWriter.tmp_path). The temp lives
    441  * in `path`'s own directory so the rename stays within one filesystem (a
    442  * cross-device rename would fail). Special files such as /dev/null are written
    443  * in place; regular file outputs must get a temp file or fail to open. */
    444 static KitStatus posix_open_writer(void* user, const char* path,
    445                                    KitWriter** out) {
    446   DriverEnv* env = (DriverEnv*)user;
    447   KitWriter* w;
    448   DriverFdWriter* fw;
    449   char tmpl[4096];
    450   const char* slash;
    451   size_t dlen;
    452   int fd;
    453   int via_temp = 0;
    454   struct stat sb;
    455 
    456   /* Only regular files (or not-yet-existing targets) get the temp+rename
    457    * treatment; special files must be written in place. */
    458   int special = (stat(path, &sb) == 0 && !S_ISREG(sb.st_mode));
    459 
    460   slash = strrchr(path, '/');
    461   dlen = slash ? (size_t)(slash - path) : 0u; /* dir part, "" => cwd */
    462   /* "<dir>/.kit-tmp-XXXXXX" (or ".kit-tmp-XXXXXX" in cwd). */
    463   if (!special && dlen + sizeof(".kit-tmp-XXXXXX") + 1u <= sizeof(tmpl)) {
    464     if (dlen) {
    465       memcpy(tmpl, path, dlen);
    466       tmpl[dlen] = '/';
    467       memcpy(tmpl + dlen + 1u, ".kit-tmp-XXXXXX", sizeof(".kit-tmp-XXXXXX"));
    468     } else {
    469       memcpy(tmpl, ".kit-tmp-XXXXXX", sizeof(".kit-tmp-XXXXXX"));
    470     }
    471     fd = mkstemp(tmpl);
    472     if (fd >= 0) {
    473       /* mkstemp creates 0600; match the in-place 0644 (the +x bit, when an
    474        * executable, is applied to the final path after close). */
    475       (void)fchmod(fd, 0644);
    476       via_temp = 1;
    477     } else {
    478       return KIT_IO;
    479     }
    480   } else if (special) {
    481     fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    482   } else {
    483     return KIT_IO;
    484   }
    485   if (fd < 0) return KIT_IO;
    486 
    487   w = driver_writer_fd(env->heap, fd);
    488   if (!w) {
    489     close(fd);
    490     if (via_temp) unlink(tmpl);
    491     return KIT_NOMEM;
    492   }
    493   if (via_temp) {
    494     fw = (DriverFdWriter*)w;
    495     fw->tmp_path = posix_strdup(env->heap, tmpl);
    496     fw->final_path = posix_strdup(env->heap, path);
    497     if (!fw->tmp_path || !fw->final_path) {
    498       /* Without both paths we can't rename; degrade to leaving the temp in
    499        * place would be wrong, so fail cleanly. */
    500       if (fw->tmp_path)
    501         env->heap->free(env->heap, fw->tmp_path, strlen(tmpl) + 1u);
    502       if (fw->final_path)
    503         env->heap->free(env->heap, fw->final_path, strlen(path) + 1u);
    504       fw->tmp_path = NULL;
    505       fw->final_path = NULL;
    506       fdw_close(w);
    507       unlink(tmpl);
    508       return KIT_NOMEM;
    509     }
    510   }
    511   *out = w;
    512   return KIT_OK;
    513 }
    514 
    515 /* ---------------- path helpers ---------------- */
    516 
    517 int driver_path_exists(const char* path) {
    518   struct stat sb;
    519   if (!path) return 0;
    520   return stat(path, &sb) == 0;
    521 }
    522 
    523 int driver_path_mtime_ns(const char* path, int64_t* out) {
    524   struct stat sb;
    525   if (!path || !out) return 1;
    526   if (stat(path, &sb) != 0) return 1;
    527   return os_stat_mtime_ns(&sb, out);
    528 }
    529 
    530 static uint8_t posix_mode_to_wasm_filetype(mode_t m) {
    531   if (S_ISREG(m)) return 4;
    532   if (S_ISDIR(m)) return 3;
    533   if (S_ISLNK(m)) return 7;
    534   if (S_ISBLK(m)) return 1;
    535   if (S_ISCHR(m)) return 2;
    536   if (S_ISSOCK(m)) return 6;
    537   return 0;
    538 }
    539 
    540 int driver_path_stat(const char* path, uint64_t* out_size,
    541                      uint64_t* out_mtime_ns, uint8_t* out_filetype) {
    542   struct stat sb;
    543   int64_t mtime;
    544   if (!path || stat(path, &sb) != 0)
    545     return (errno == ENOENT || errno == ENOTDIR) ? 1 : 2;
    546   *out_size = (uint64_t)sb.st_size;
    547   *out_mtime_ns = os_stat_mtime_ns(&sb, &mtime) == 0 ? (uint64_t)mtime : 0u;
    548   *out_filetype = posix_mode_to_wasm_filetype(sb.st_mode);
    549   return 0;
    550 }
    551 
    552 int driver_path_lstat(const char* path, uint64_t* out_size,
    553                       uint8_t* out_filetype, int* out_executable) {
    554   struct stat sb;
    555   if (!path || lstat(path, &sb) != 0)
    556     return (errno == ENOENT || errno == ENOTDIR) ? 1 : 2;
    557   *out_size = (uint64_t)sb.st_size;
    558   *out_filetype = posix_mode_to_wasm_filetype(sb.st_mode);
    559   *out_executable =
    560       S_ISREG(sb.st_mode) && (sb.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0;
    561   return 0;
    562 }
    563 
    564 typedef struct DriverDirEntryRec {
    565   char* name;
    566   size_t name_alloc;
    567   uint32_t name_len;
    568   uint64_t ino;
    569   uint64_t size;
    570   uint64_t mtime_ns;
    571   uint8_t filetype;
    572 } DriverDirEntryRec;
    573 
    574 struct DriverDirHandle {
    575   DriverEnv* env;
    576   DriverDirEntryRec* entries;
    577   size_t entries_alloc;
    578   uint64_t count;
    579 };
    580 
    581 DriverDirHandle* driver_open_dir(DriverEnv* env, const char* path) {
    582   DIR* d;
    583   struct dirent* ent;
    584   DriverDirHandle* h;
    585   uint64_t cap = 0;
    586   uint64_t count = 0;
    587   size_t plen;
    588   int slash;
    589 
    590   if (!env || !path) return NULL;
    591   d = opendir(path);
    592   if (!d) return NULL;
    593   plen = kit_slice_cstr(path).len;
    594   slash = plen && path[plen - 1u] != '/';
    595 
    596   h = (DriverDirHandle*)env->heap->alloc(env->heap, sizeof(*h),
    597                                          _Alignof(DriverDirHandle));
    598   if (!h) {
    599     closedir(d);
    600     return NULL;
    601   }
    602   memset(h, 0, sizeof(*h));
    603   h->env = env;
    604 
    605   while ((ent = readdir(d)) != NULL) {
    606     const char* name = ent->d_name;
    607     size_t name_len;
    608     DriverDirEntryRec* e;
    609     size_t child_alloc;
    610     char* child;
    611     struct stat sb;
    612     int64_t mtime;
    613 
    614     if (driver_streq(name, ".") || driver_streq(name, "..")) continue;
    615     name_len = kit_slice_cstr(name).len;
    616 
    617     /* grow entry array */
    618     if (count >= cap) {
    619       uint64_t new_cap = cap ? cap * 2u : 8u;
    620       size_t new_alloc = (size_t)new_cap * sizeof(DriverDirEntryRec);
    621       DriverDirEntryRec* nv = (DriverDirEntryRec*)env->heap->alloc(
    622           env->heap, new_alloc, _Alignof(DriverDirEntryRec));
    623       if (!nv) goto fail;
    624       if (h->entries) {
    625         memcpy(nv, h->entries, (size_t)count * sizeof(DriverDirEntryRec));
    626         env->heap->free(env->heap, h->entries, h->entries_alloc);
    627       }
    628       h->entries = nv;
    629       h->entries_alloc = new_alloc;
    630       cap = new_cap;
    631     }
    632 
    633     e = &h->entries[count];
    634     memset(e, 0, sizeof(*e));
    635     e->name_alloc = name_len + 1u;
    636     e->name = (char*)env->heap->alloc(env->heap, e->name_alloc, 1u);
    637     if (!e->name) goto fail;
    638     memcpy(e->name, name, name_len);
    639     e->name[name_len] = '\0';
    640     e->name_len = (uint32_t)name_len;
    641 
    642     /* lstat the entry to get metadata */
    643     child_alloc = plen + (size_t)slash + name_len + 1u;
    644     child = (char*)driver_alloc(env, child_alloc);
    645     if (child) {
    646       size_t off = 0;
    647       memcpy(child, path, plen);
    648       off += plen;
    649       if (slash) child[off++] = '/';
    650       memcpy(child + off, name, name_len);
    651       child[off + name_len] = '\0';
    652       if (lstat(child, &sb) == 0) {
    653         e->ino = (uint64_t)sb.st_ino;
    654         e->size = (uint64_t)sb.st_size;
    655         if (os_stat_mtime_ns(&sb, &mtime) == 0) e->mtime_ns = (uint64_t)mtime;
    656         e->filetype = posix_mode_to_wasm_filetype(sb.st_mode);
    657       }
    658       driver_free(env, child, child_alloc);
    659     }
    660     ++count;
    661   }
    662 
    663   closedir(d);
    664   h->count = count;
    665   return h;
    666 
    667 fail:
    668   closedir(d);
    669   driver_close_dir(env, h);
    670   return NULL;
    671 }
    672 
    673 int driver_read_dir_entry(DriverDirHandle* h, uint64_t index,
    674                           const char** out_name, uint32_t* out_name_len,
    675                           uint64_t* out_ino, uint64_t* out_size,
    676                           uint64_t* out_mtime_ns, uint8_t* out_filetype) {
    677   DriverDirEntryRec* e;
    678   if (!h || index >= h->count) return 1;
    679   e = &h->entries[index];
    680   *out_name = e->name;
    681   *out_name_len = e->name_len;
    682   *out_ino = e->ino;
    683   *out_size = e->size;
    684   *out_mtime_ns = e->mtime_ns;
    685   *out_filetype = e->filetype;
    686   return 0;
    687 }
    688 
    689 void driver_close_dir(DriverEnv* env, DriverDirHandle* h) {
    690   uint64_t i;
    691   if (!h) return;
    692   if (!env) env = h->env;
    693   for (i = 0; i < h->count; ++i) {
    694     DriverDirEntryRec* e = &h->entries[i];
    695     if (e->name) env->heap->free(env->heap, e->name, e->name_alloc);
    696   }
    697   if (h->entries) env->heap->free(env->heap, h->entries, h->entries_alloc);
    698   env->heap->free(env->heap, h, sizeof(*h));
    699 }
    700 
    701 int driver_mkdir_p(DriverEnv* env, const char* path) {
    702   size_t len;
    703   char* buf;
    704   size_t i;
    705   struct stat sb;
    706 
    707   if (!path || !path[0]) return 1;
    708   len = kit_slice_cstr(path).len;
    709   buf = (char*)driver_alloc(env, len + 1);
    710   if (!buf) return 1;
    711   memcpy(buf, path, len + 1);
    712 
    713   for (i = 1; i <= len; ++i) {
    714     int at_end = (i == len);
    715     if (!at_end && buf[i] != '/') continue;
    716     if (!at_end) buf[i] = '\0';
    717     if (buf[0] != '\0' && !driver_streq(buf, ".")) {
    718       if (mkdir(buf, 0755) != 0 && errno != EEXIST) {
    719         driver_free(env, buf, len + 1);
    720         return 1;
    721       }
    722       if (stat(buf, &sb) != 0 || !S_ISDIR(sb.st_mode)) {
    723         driver_free(env, buf, len + 1);
    724         return 1;
    725       }
    726     }
    727     if (!at_end) buf[i] = '/';
    728   }
    729 
    730   driver_free(env, buf, len + 1);
    731   return 0;
    732 }
    733 
    734 int driver_mark_executable_output(const char* path) {
    735   mode_t mask;
    736   mode_t mode;
    737   if (!path) return 1;
    738   mask = umask(0);
    739   (void)umask(mask);
    740   mode = (mode_t)(0777 & ~mask);
    741   return chmod(path, mode) == 0 ? 0 : 1;
    742 }
    743 
    744 int driver_path_mode_get(const char* path, uint32_t* mode_out) {
    745   struct stat sb;
    746   if (!path || !mode_out || stat(path, &sb) != 0 || !S_ISREG(sb.st_mode))
    747     return 1;
    748   *mode_out = (uint32_t)(sb.st_mode & 07777u);
    749   return 0;
    750 }
    751 
    752 int driver_path_mode_set(const char* path, uint32_t mode) {
    753   if (!path || mode > 07777u) return 1;
    754   return chmod(path, (mode_t)mode) == 0 ? 0 : 1;
    755 }
    756 
    757 /* ---------------- link helpers (install) ---------------- */
    758 
    759 int driver_create_symlink(const char* target, const char* link_path) {
    760   if (!target || !link_path) return 1;
    761   return symlink(target, link_path) == 0 ? 0 : 1;
    762 }
    763 
    764 int driver_readlink(const char* path, char* buf, size_t cap) {
    765   ssize_t n;
    766   if (!path || !buf || cap == 0) return 1;
    767   /* Read into the full buffer; readlink never NUL-terminates and reports the
    768    * untruncated length, so n >= cap means the target did not fit. */
    769   n = readlink(path, buf, cap);
    770   if (n < 0) return 1;
    771   if ((size_t)n >= cap) return 1;
    772   buf[n] = '\0';
    773   return 0;
    774 }
    775 
    776 int driver_create_hardlink(const char* target, const char* link_path) {
    777   if (!target || !link_path) return 1;
    778   return link(target, link_path) == 0 ? 0 : 1;
    779 }
    780 
    781 int driver_remove_file(const char* path) {
    782   if (!path) return 1;
    783   if (unlink(path) == 0) return 0;
    784   return errno == ENOENT ? 0 : 1; /* already absent is success */
    785 }
    786 
    787 int driver_path_lexists(const char* path) {
    788   struct stat sb;
    789   if (!path) return 0;
    790   return lstat(path, &sb) == 0;
    791 }
    792 
    793 int driver_rename(const char* from, const char* to) {
    794   if (!from || !to) return 1;
    795   return rename(from, to) == 0 ? 0 : 1; /* same-fs rename is atomic */
    796 }
    797 
    798 static int posix_remove_tree(const char* path) {
    799   struct stat sb;
    800   if (lstat(path, &sb) != 0) return errno == ENOENT ? 0 : 1;
    801   if (S_ISDIR(sb.st_mode)) { /* lstat: a symlink-to-dir is unlinked, not entered */
    802     DIR* d = opendir(path);
    803     struct dirent* ent;
    804     int rc = 0;
    805     if (!d) return 1;
    806     while ((ent = readdir(d)) != NULL) {
    807       char child[4096];
    808       const char* name = ent->d_name;
    809       if (name[0] == '.' &&
    810           (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')))
    811         continue;
    812       if ((size_t)snprintf(child, sizeof child, "%s/%s", path, name) >=
    813           sizeof child) {
    814         rc = 1;
    815         continue;
    816       }
    817       if (posix_remove_tree(child) != 0) rc = 1;
    818     }
    819     closedir(d);
    820     if (rmdir(path) != 0) rc = 1;
    821     return rc;
    822   }
    823   return unlink(path) == 0 ? 0 : 1;
    824 }
    825 
    826 int driver_remove_tree(const char* path) {
    827   if (!path) return 1;
    828   return posix_remove_tree(path);
    829 }
    830 
    831 int driver_kit_home(char* buf, size_t cap) {
    832   const char* v;
    833   int n = -1;
    834   size_t len;
    835   if (!buf || cap == 0) return 1;
    836   if ((v = getenv("KIT_HOME")) && *v)
    837     n = snprintf(buf, cap, "%s", v);
    838   else if ((v = getenv("XDG_DATA_HOME")) && *v)
    839     n = snprintf(buf, cap, "%s/kit", v);
    840   else if ((v = getenv("HOME")) && *v)
    841     n = snprintf(buf, cap, "%s/.local/share/kit", v);
    842   if (n < 0 || (size_t)n >= cap) return 1;
    843   len = strlen(buf);
    844   while (len > 1 && buf[len - 1] == '/') buf[--len] = '\0'; /* no trailing '/' */
    845   return 0;
    846 }
    847 
    848 static int fetch_hex_val(char c, unsigned* out) {
    849   if (c >= '0' && c <= '9') {
    850     *out = (unsigned)(c - '0');
    851     return 0;
    852   }
    853   if (c >= 'a' && c <= 'f') {
    854     *out = (unsigned)(c - 'a') + 10u;
    855     return 0;
    856   }
    857   if (c >= 'A' && c <= 'F') {
    858     *out = (unsigned)(c - 'A') + 10u;
    859     return 0;
    860   }
    861   return 1;
    862 }
    863 
    864 static char* fetch_decode_url_path(const char* s) {
    865   char* out;
    866   char* w;
    867   size_t n;
    868   if (!s) return NULL;
    869   n = strlen(s);
    870   out = (char*)malloc(n + 1u);
    871   if (!out) return NULL;
    872   w = out;
    873   while (*s) {
    874     if (*s == '%') {
    875       unsigned hi, lo;
    876       if (!s[1] || !s[2] || fetch_hex_val(s[1], &hi) ||
    877           fetch_hex_val(s[2], &lo)) {
    878         free(out);
    879         return NULL;
    880       }
    881       if (((hi << 4) | lo) == 0u) {
    882         free(out);
    883         return NULL;
    884       }
    885       *w++ = (char)((hi << 4) | lo);
    886       s += 3;
    887     } else {
    888       *w++ = *s++;
    889     }
    890   }
    891   *w = '\0';
    892   return out;
    893 }
    894 
    895 static char* fetch_file_url_path(const char* url) {
    896   const char* p;
    897   if (!url || strncmp(url, "file://", 7) != 0) return NULL;
    898   p = url + 7;
    899   if (strncmp(p, "localhost/", 10) == 0) {
    900     p += 9; /* keep the slash before the absolute path */
    901   } else if (p[0] != '/') {
    902     return NULL;
    903   }
    904   return fetch_decode_url_path(p);
    905 }
    906 
    907 static int fetch_copy_file(const char* src, const char* dest) {
    908   uint8_t buf[32768];
    909   int in_fd, out_fd;
    910   int rc = 1;
    911   if (!src || !dest) return 1;
    912   in_fd = open(src, O_RDONLY);
    913   if (in_fd < 0) return 1;
    914   out_fd = open(dest, O_WRONLY | O_CREAT | O_TRUNC, 0666);
    915   if (out_fd < 0) {
    916     close(in_fd);
    917     return 1;
    918   }
    919   for (;;) {
    920     ssize_t r = read(in_fd, buf, sizeof buf);
    921     size_t off = 0;
    922     if (r == 0) {
    923       rc = 0;
    924       break;
    925     }
    926     if (r < 0) {
    927       if (errno == EINTR) continue;
    928       break;
    929     }
    930     while (off < (size_t)r) {
    931       ssize_t w = write(out_fd, buf + off, (size_t)r - off);
    932       if (w > 0) {
    933         off += (size_t)w;
    934       } else if (w < 0 && errno == EINTR) {
    935         continue;
    936       } else {
    937         goto out;
    938       }
    939     }
    940   }
    941 out:
    942   if (close(out_fd) != 0) rc = 1;
    943   close(in_fd);
    944   return rc;
    945 }
    946 
    947 int driver_fetch_url(const char* url, const char* dest) {
    948   pid_t pid;
    949   int status;
    950   char* file_path;
    951   if (!url || !dest) return 1;
    952   if (strncmp(url, "file://", 7) == 0) {
    953     int rc;
    954     file_path = fetch_file_url_path(url);
    955     if (!file_path) return 1;
    956     rc = fetch_copy_file(file_path, dest);
    957     free(file_path);
    958     return rc;
    959   }
    960   pid = fork();
    961   if (pid < 0) return 1;
    962   if (pid == 0) {
    963     /* Untrusted transport: the URL is a distinct argv element (no shell), so a
    964      * hostile mirror URL cannot inject a command. The production update
    965      * transport is deliberately curl-only so its TLS and redirect behavior is
    966      * one audited contract rather than whichever downloader happens to be
    967      * installed first. */
    968     execlp("curl", "curl", "-fsSL", "-o", dest, "--", url, (char*)NULL);
    969     _exit(127);
    970   }
    971   do {
    972     if (waitpid(pid, &status, 0) < 0) {
    973       if (errno == EINTR) continue;
    974       return 1;
    975     }
    976     break;
    977   } while (1);
    978   return (WIFEXITED(status) && WEXITSTATUS(status) == 0) ? 0 : 1;
    979 }
    980 
    981 int driver_touch(const char* path) {
    982   const struct timespec times[2] = {{0, UTIME_NOW}, {0, UTIME_NOW}};
    983   if (!path) return 1;
    984   if (utimensat(AT_FDCWD, path, times, 0) == 0) return 0;
    985   if (errno == ENOENT) {
    986     int fd = open(path, O_WRONLY | O_CREAT, 0666);
    987     if (fd >= 0) {
    988       close(fd);
    989       return 0;
    990     }
    991   }
    992   return 1;
    993 }
    994 
    995 static int driver_walk_regular_files_at(DriverEnv* env, const char* dir,
    996                                         const char* rel, DriverWalkFileFn cb,
    997                                         void* user) {
    998   DIR* d;
    999   struct dirent* ent;
   1000   int rc = 1;
   1001   d = opendir(dir);
   1002   if (!d) return 1;
   1003   while ((ent = readdir(d)) != NULL) {
   1004     const char* name = ent->d_name;
   1005     char* child;
   1006     char* child_rel;
   1007     struct stat sb;
   1008     int child_rc = 0;
   1009     if (driver_streq(name, ".") || driver_streq(name, "..")) continue;
   1010     child = driver_path_join(env, dir, name, NULL);
   1011     if (!child) goto out;
   1012     child_rel = rel && rel[0] ? driver_path_join(env, rel, name, NULL)
   1013                               : driver_path_join(env, "", name, NULL);
   1014     if (!child_rel) {
   1015       driver_free(env, child, kit_slice_cstr(child).len + 1u);
   1016       goto out;
   1017     }
   1018     if (lstat(child, &sb) != 0) {
   1019       child_rc = 1;
   1020     } else if (S_ISDIR(sb.st_mode)) {
   1021       child_rc = driver_walk_regular_files_at(env, child, child_rel, cb, user);
   1022     } else if (S_ISREG(sb.st_mode)) {
   1023       int x = (sb.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0;
   1024       child_rc = cb(user, child, child_rel, x);
   1025     } else {
   1026       child_rc = 1;
   1027     }
   1028     driver_free(env, child_rel, kit_slice_cstr(child_rel).len + 1u);
   1029     driver_free(env, child, kit_slice_cstr(child).len + 1u);
   1030     if (child_rc) goto out;
   1031   }
   1032   rc = 0;
   1033 
   1034 out:
   1035   closedir(d);
   1036   return rc;
   1037 }
   1038 
   1039 int driver_walk_regular_files(DriverEnv* env, const char* root,
   1040                               DriverWalkFileFn cb, void* user) {
   1041   if (!env || !root || !root[0] || !cb) return 1;
   1042   return driver_walk_regular_files_at(env, root, "", cb, user);
   1043 }
   1044 
   1045 /* ---------------- time ---------------- */
   1046 
   1047 uint64_t driver_now_ns(void) {
   1048   struct timespec ts;
   1049   if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0)
   1050     return (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec;
   1051   return 0;
   1052 }
   1053 
   1054 int driver_random_bytes(uint8_t* out, size_t n) {
   1055   /* /dev/urandom is the most portable CSPRNG across darwin/linux/freebsd and
   1056    * avoids getentropy()'s per-platform header/feature-macro gymnastics. */
   1057   size_t off = 0;
   1058   int fd;
   1059   if (!out) return 1;
   1060   fd = open("/dev/urandom", O_RDONLY);
   1061   if (fd < 0) return 1;
   1062   while (off < n) {
   1063     ssize_t r = read(fd, out + off, n - off);
   1064     if (r > 0) {
   1065       off += (size_t)r;
   1066     } else if (r < 0 && errno == EINTR) {
   1067       continue;
   1068     } else {
   1069       close(fd);
   1070       return 1;
   1071     }
   1072   }
   1073   close(fd);
   1074   return 0;
   1075 }
   1076 
   1077 /* ---------------- load helpers ---------------- */
   1078 
   1079 /* driver_load_bytes / driver_release_bytes are OS-neutral; see env/common.c. */
   1080 
   1081 /* ---------------- stdin / edit_temp / read_line ---------------- */
   1082 
   1083 int driver_read_stdin(DriverEnv* e, uint8_t** out_data, size_t* out_size) {
   1084   size_t cap = 4096;
   1085   size_t len = 0;
   1086   uint8_t* buf = e->heap->alloc(e->heap, cap, 1);
   1087   if (!buf) return 0;
   1088   for (;;) {
   1089     ssize_t n;
   1090     if (len == cap) {
   1091       size_t newcap = cap * 2;
   1092       uint8_t* nb = e->heap->realloc(e->heap, buf, cap, newcap, 1);
   1093       if (!nb) {
   1094         e->heap->free(e->heap, buf, cap);
   1095         return 0;
   1096       }
   1097       buf = nb;
   1098       cap = newcap;
   1099     }
   1100     n = read(STDIN_FILENO, buf + len, cap - len);
   1101     if (n == 0) break;
   1102     if (n < 0) {
   1103       e->heap->free(e->heap, buf, cap);
   1104       return 0;
   1105     }
   1106     len += (size_t)n;
   1107   }
   1108   if (len < cap) {
   1109     uint8_t* shrunk = len ? e->heap->realloc(e->heap, buf, cap, len, 1) : NULL;
   1110     if (len && !shrunk) {
   1111       *out_data = buf;
   1112       *out_size = cap;
   1113       return 1;
   1114     }
   1115     if (!len) {
   1116       e->heap->free(e->heap, buf, cap);
   1117       buf = NULL;
   1118     } else {
   1119       buf = shrunk;
   1120     }
   1121   }
   1122   *out_data = buf;
   1123   *out_size = len;
   1124   return 1;
   1125 }
   1126 
   1127 static int driver_write_fd_all(int fd, const uint8_t* data, size_t n) {
   1128   size_t off = 0;
   1129   while (off < n) {
   1130     ssize_t wr = write(fd, data + off, n - off);
   1131     if (wr < 0) {
   1132       if (errno == EINTR) continue;
   1133       return 0;
   1134     }
   1135     if (wr == 0) return 0;
   1136     off += (size_t)wr;
   1137   }
   1138   return 1;
   1139 }
   1140 
   1141 static char* driver_shell_quote_path(DriverEnv* e, const char* path,
   1142                                      size_t path_len, size_t* quoted_len_out) {
   1143   size_t i;
   1144   size_t quoted_len = 2u;
   1145   char* out;
   1146   char* q;
   1147   for (i = 0; i < path_len; ++i) quoted_len += path[i] == '\'' ? 4u : 1u;
   1148   out = e->heap->alloc(e->heap, quoted_len + 1u, 1);
   1149   if (!out) return NULL;
   1150   q = out;
   1151   *q++ = '\'';
   1152   for (i = 0; i < path_len; ++i) {
   1153     if (path[i] == '\'') {
   1154       *q++ = '\'';
   1155       *q++ = '\\';
   1156       *q++ = '\'';
   1157       *q++ = '\'';
   1158     } else {
   1159       *q++ = path[i];
   1160     }
   1161   }
   1162   *q++ = '\'';
   1163   *q = '\0';
   1164   if (quoted_len_out) *quoted_len_out = quoted_len;
   1165   return out;
   1166 }
   1167 
   1168 int driver_edit_temp(DriverEnv* e, const char* suffix, const uint8_t* initial,
   1169                      size_t initial_size, uint8_t** out_data,
   1170                      size_t* out_size) {
   1171   const char* editor;
   1172   const char* tmpdir;
   1173   const char* base = "/kit-dbg-XXXXXX";
   1174   size_t tmpdir_len;
   1175   size_t base_len;
   1176   size_t suffix_len;
   1177   size_t path_len;
   1178   char* path;
   1179   int fd = -1;
   1180   int ok = 0;
   1181   KitFileData fd_data;
   1182 
   1183   if (!out_data || !out_size) return 0;
   1184   *out_data = NULL;
   1185   *out_size = 0;
   1186   suffix_len = suffix ? kit_slice_cstr(suffix).len : 0u;
   1187   tmpdir = getenv("TMPDIR");
   1188   if (!tmpdir || !*tmpdir) tmpdir = "/tmp";
   1189   tmpdir_len = kit_slice_cstr(tmpdir).len;
   1190   base_len = kit_slice_cstr(base).len;
   1191   path_len = tmpdir_len + base_len + suffix_len;
   1192   path = e->heap->alloc(e->heap, path_len + 1u, 1);
   1193   if (!path) return 0;
   1194   memcpy(path, tmpdir, tmpdir_len);
   1195   memcpy(path + tmpdir_len, base, base_len);
   1196   if (suffix_len) memcpy(path + tmpdir_len + base_len, suffix, suffix_len);
   1197   path[path_len] = '\0';
   1198 
   1199   fd = mkstemps(path, (int)suffix_len);
   1200   if (fd < 0) goto out;
   1201   if (initial_size &&
   1202       !driver_write_fd_all(fd, initial ? initial : (const uint8_t*)"",
   1203                            initial_size))
   1204     goto out;
   1205   if (close(fd) != 0) {
   1206     fd = -1;
   1207     goto out;
   1208   }
   1209   fd = -1;
   1210 
   1211   editor = getenv("VISUAL");
   1212   if (!editor || !*editor) editor = getenv("EDITOR");
   1213   if (!editor || !*editor) editor = "vi";
   1214   {
   1215     size_t editor_len = kit_slice_cstr(editor).len;
   1216     size_t quoted_len = 0;
   1217     char* quoted = driver_shell_quote_path(e, path, path_len, &quoted_len);
   1218     char* cmd;
   1219     int status;
   1220     pid_t pid;
   1221     if (!quoted) goto out;
   1222     cmd = e->heap->alloc(e->heap, editor_len + 1u + quoted_len + 1u, 1);
   1223     if (!cmd) {
   1224       e->heap->free(e->heap, quoted, quoted_len + 1u);
   1225       goto out;
   1226     }
   1227     memcpy(cmd, editor, editor_len);
   1228     cmd[editor_len] = ' ';
   1229     memcpy(cmd + editor_len + 1u, quoted, quoted_len + 1u);
   1230     e->heap->free(e->heap, quoted, quoted_len + 1u);
   1231     pid = fork();
   1232     if (pid == 0) {
   1233       execl("/bin/sh", "sh", "-c", cmd, (char*)NULL);
   1234       _exit(127);
   1235     }
   1236     e->heap->free(e->heap, cmd, editor_len + 1u + quoted_len + 1u);
   1237     if (pid < 0) goto out;
   1238     do {
   1239       if (waitpid(pid, &status, 0) < 0) {
   1240         if (errno == EINTR) continue;
   1241         goto out;
   1242       }
   1243       break;
   1244     } while (1);
   1245     if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) goto out;
   1246   }
   1247 
   1248   fd_data.data = NULL;
   1249   fd_data.size = 0;
   1250   fd_data.token = NULL;
   1251   if (posix_read_all(e, path, &fd_data) != KIT_OK) goto out;
   1252   *out_data = (uint8_t*)fd_data.data;
   1253   *out_size = fd_data.size;
   1254   ok = 1;
   1255 
   1256 out:
   1257   if (fd >= 0) close(fd);
   1258   if (path) {
   1259     unlink(path);
   1260     e->heap->free(e->heap, path, path_len + 1u);
   1261   }
   1262   return ok;
   1263 }
   1264 
   1265 int driver_read_line(char* buf, size_t cap) {
   1266   size_t len = 0;
   1267   if (!buf || cap < 2) return -1;
   1268   for (;;) {
   1269     int c;
   1270     errno = 0;
   1271     c = fgetc(stdin);
   1272     if (c == EOF) {
   1273       buf[len] = '\0';
   1274       if (errno == EINTR) {
   1275         clearerr(stdin);
   1276         return -2;
   1277       }
   1278       if (ferror(stdin)) return -1;
   1279       if (len == 0) return 0;
   1280       return (int)len;
   1281     }
   1282     if (c == '\n') {
   1283       buf[len] = '\0';
   1284       return (int)len;
   1285     }
   1286     if (len + 1 < cap) buf[len++] = (char)c;
   1287   }
   1288 }
   1289 
   1290 static void line_completion_list_fini(DriverLineCompletionList* l) {
   1291   uint32_t i;
   1292   if (!l || !l->env) return;
   1293   for (i = 0; i < l->count; ++i) {
   1294     if (l->items[i].text)
   1295       driver_free(l->env, l->items[i].text, l->items[i].size);
   1296   }
   1297   if (l->items)
   1298     driver_free(l->env, l->items, (size_t)l->cap * sizeof(*l->items));
   1299 }
   1300 
   1301 static int line_history_add(DriverEnv* env, DriverLineHistory* h,
   1302                             const char* line, size_t len) {
   1303   char** ni;
   1304   size_t* ns;
   1305   char* copy;
   1306   uint32_t nc;
   1307   size_t old_items_size;
   1308   size_t new_items_size;
   1309   size_t old_sizes_size;
   1310   size_t new_sizes_size;
   1311   if (!env || !h || !line || len == 0) return 0;
   1312   if (h->count > 0 && h->items[h->count - 1] &&
   1313       strlen(h->items[h->count - 1]) == len &&
   1314       memcmp(h->items[h->count - 1], line, len) == 0)
   1315     return 0;
   1316   if (h->count == h->cap) {
   1317     nc = h->cap ? h->cap * 2u : 32u;
   1318     old_items_size = (size_t)h->cap * sizeof(*h->items);
   1319     new_items_size = (size_t)nc * sizeof(*h->items);
   1320     old_sizes_size = (size_t)h->cap * sizeof(*h->sizes);
   1321     new_sizes_size = (size_t)nc * sizeof(*h->sizes);
   1322     ni = (char**)env->heap->realloc(env->heap, h->items, old_items_size,
   1323                                     new_items_size, _Alignof(char*));
   1324     if (!ni) return 1;
   1325     h->items = ni;
   1326     ns = (size_t*)env->heap->realloc(env->heap, h->sizes, old_sizes_size,
   1327                                      new_sizes_size, _Alignof(size_t));
   1328     if (!ns) return 1;
   1329     h->sizes = ns;
   1330     h->cap = nc;
   1331   }
   1332   copy = (char*)driver_alloc(env, len + 1u);
   1333   if (!copy) return 1;
   1334   memcpy(copy, line, len);
   1335   copy[len] = '\0';
   1336   h->items[h->count] = copy;
   1337   h->sizes[h->count] = len + 1u;
   1338   h->count++;
   1339   return 0;
   1340 }
   1341 
   1342 static void line_redraw(const char* prompt, const char* buf, size_t len,
   1343                         size_t cursor) {
   1344   size_t back = len - cursor;
   1345   fputc('\r', stdout);
   1346   fputs(prompt, stdout);
   1347   if (len) fwrite(buf, 1, len, stdout);
   1348   fputs("\033[K", stdout);
   1349   if (back) fprintf(stdout, "\033[%zuD", back);
   1350   fflush(stdout);
   1351 }
   1352 
   1353 static void line_set(char* buf, size_t cap, size_t* len, size_t* cursor,
   1354                      const char* src) {
   1355   size_t n = strlen(src);
   1356   if (n >= cap) n = cap - 1u;
   1357   memmove(buf, src, n);
   1358   buf[n] = '\0';
   1359   *len = n;
   1360   *cursor = n;
   1361 }
   1362 
   1363 static int line_replace(char* buf, size_t cap, size_t* len, size_t* cursor,
   1364                         size_t start, size_t end, const char* rep,
   1365                         size_t rep_len) {
   1366   size_t tail;
   1367   if (start > end || end > *len) return 1;
   1368   tail = *len - end;
   1369   if (start + rep_len + tail + 1u > cap) return 1;
   1370   memmove(buf + start + rep_len, buf + end, tail + 1u);
   1371   if (rep_len) memcpy(buf + start, rep, rep_len);
   1372   *len = start + rep_len + tail;
   1373   *cursor = start + rep_len;
   1374   return 0;
   1375 }
   1376 
   1377 static size_t line_common_prefix(const DriverLineCompletionList* l) {
   1378   size_t n;
   1379   uint32_t i;
   1380   if (!l || l->count == 0) return 0;
   1381   n = strlen(l->items[0].text);
   1382   for (i = 1; i < l->count; ++i) {
   1383     size_t j = 0;
   1384     const char* s = l->items[i].text;
   1385     while (j < n && s[j] && s[j] == l->items[0].text[j]) ++j;
   1386     n = j;
   1387   }
   1388   return n;
   1389 }
   1390 
   1391 static void line_default_complete_range(const char* buf, size_t cursor,
   1392                                         size_t* start, size_t* end) {
   1393   size_t s = cursor;
   1394   while (s > 0 && buf[s - 1] != ' ' && buf[s - 1] != '\t') --s;
   1395   *start = s;
   1396   *end = cursor;
   1397 }
   1398 
   1399 static void line_complete(DriverEnv* env, char* buf, size_t cap, size_t* len,
   1400                           size_t* cursor, const char* prompt,
   1401                           DriverLineCompleteFn complete, void* complete_user) {
   1402   DriverLineCompletionList list;
   1403   size_t common;
   1404   size_t cur_len;
   1405   uint32_t i;
   1406   if (!complete) return;
   1407   {
   1408     DriverLineCompletionList z = {0};
   1409     list = z;
   1410   }
   1411   list.env = env;
   1412   line_default_complete_range(buf, *cursor, &list.replace_start,
   1413                               &list.replace_end);
   1414   complete(complete_user, buf, *cursor, &list);
   1415   if (list.replace_end > *len) list.replace_end = *len;
   1416   if (list.replace_start > list.replace_end)
   1417     list.replace_start = list.replace_end;
   1418   if (list.count == 1) {
   1419     size_t rn = strlen(list.items[0].text);
   1420     if (line_replace(buf, cap, len, cursor, list.replace_start,
   1421                      list.replace_end, list.items[0].text, rn) != 0)
   1422       fputc('\a', stdout);
   1423     line_redraw(prompt, buf, *len, *cursor);
   1424     line_completion_list_fini(&list);
   1425     return;
   1426   }
   1427   if (list.count > 1) {
   1428     common = line_common_prefix(&list);
   1429     cur_len = *cursor > list.replace_start ? *cursor - list.replace_start : 0;
   1430     if (common > cur_len) {
   1431       if (line_replace(buf, cap, len, cursor, list.replace_start,
   1432                        list.replace_end, list.items[0].text, common) != 0)
   1433         fputc('\a', stdout);
   1434       line_redraw(prompt, buf, *len, *cursor);
   1435     } else {
   1436       fputc('\n', stdout);
   1437       for (i = 0; i < list.count; ++i)
   1438         fprintf(stdout, "  %s\n", list.items[i].text);
   1439       line_redraw(prompt, buf, *len, *cursor);
   1440     }
   1441   } else {
   1442     fputc('\a', stdout);
   1443     fflush(stdout);
   1444   }
   1445   line_completion_list_fini(&list);
   1446 }
   1447 
   1448 int driver_read_line_edit(DriverEnv* env, const char* prompt, char* buf,
   1449                           size_t cap, DriverLineHistory* hist,
   1450                           DriverLineCompleteFn complete, void* complete_user,
   1451                           const char* edit_suffix) {
   1452   struct termios orig;
   1453   struct termios raw;
   1454   char* saved = NULL;
   1455   size_t saved_size = 0;
   1456   size_t len = 0;
   1457   size_t cursor = 0;
   1458   uint32_t hist_pos = 0;
   1459   int have_saved = 0;
   1460   int raw_enabled = 0;
   1461   int out_rc = -1;
   1462 
   1463   if (!buf || cap < 2) return -1;
   1464   if (!prompt) prompt = "";
   1465   if (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO)) {
   1466     int n;
   1467     fputs(prompt, stdout);
   1468     fflush(stdout);
   1469     n = driver_read_line(buf, cap);
   1470     if (n > 0 && hist) (void)line_history_add(env, hist, buf, (size_t)n);
   1471     return n;
   1472   }
   1473 
   1474   if (tcgetattr(STDIN_FILENO, &orig) != 0) goto out;
   1475   raw = orig;
   1476   raw.c_lflag &= (tcflag_t) ~(ICANON | ECHO | IEXTEN);
   1477   raw.c_iflag &= (tcflag_t) ~(IXON);
   1478   raw.c_cc[VMIN] = 1;
   1479   raw.c_cc[VTIME] = 0;
   1480   if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) != 0) goto out;
   1481   raw_enabled = 1;
   1482 
   1483   saved = (char*)driver_alloc(env, cap);
   1484   if (!saved) goto out;
   1485   saved_size = cap;
   1486   hist_pos = hist ? hist->count : 0;
   1487   buf[0] = '\0';
   1488 
   1489   fputs(prompt, stdout);
   1490   fflush(stdout);
   1491   for (;;) {
   1492     unsigned char c;
   1493     ssize_t r = read(STDIN_FILENO, &c, 1);
   1494     if (r < 0) {
   1495       if (errno == EINTR) {
   1496         out_rc = -2;
   1497         goto out;
   1498       }
   1499       out_rc = -1;
   1500       goto out;
   1501     }
   1502     if (r == 0) {
   1503       out_rc = len ? (int)len : 0;
   1504       goto out;
   1505     }
   1506     if (c == '\r' || c == '\n') {
   1507       buf[len] = '\0';
   1508       fputc('\n', stdout);
   1509       if (hist) (void)line_history_add(env, hist, buf, len);
   1510       out_rc = (int)len;
   1511       goto out;
   1512     }
   1513     if (c == 4) {
   1514       if (len == 0) {
   1515         buf[0] = '\0';
   1516         out_rc = 0;
   1517         goto out;
   1518       }
   1519       continue;
   1520     }
   1521     if (c == 7) {
   1522       /* Ctrl-G: hand the currently typed line to $EDITOR (seeded with the
   1523        * buffer), then reload whatever was saved. Drop to cooked mode so the
   1524        * editor owns the terminal, then restore raw mode and redraw. */
   1525       uint8_t* edited = NULL;
   1526       size_t edited_size = 0;
   1527       buf[len] = '\0';
   1528       tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig);
   1529       if (driver_edit_temp(env, edit_suffix, (const uint8_t*)buf, len, &edited,
   1530                            &edited_size)) {
   1531         size_t n = edited_size;
   1532         /* Editors append a trailing newline; drop trailing CR/LF so the
   1533          * reloaded prompt stays a single line. */
   1534         while (n > 0 && (edited[n - 1] == '\n' || edited[n - 1] == '\r')) --n;
   1535         if (n >= cap) n = cap - 1u;
   1536         memcpy(buf, edited, n);
   1537         buf[n] = '\0';
   1538         len = n;
   1539         cursor = n;
   1540         have_saved = 0; /* edited text supersedes history navigation */
   1541         hist_pos = hist ? hist->count : 0;
   1542       }
   1543       if (edited) driver_free(env, edited, edited_size);
   1544       tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
   1545       line_redraw(prompt, buf, len, cursor);
   1546       continue;
   1547     }
   1548     if (c == '\t') {
   1549       buf[len] = '\0';
   1550       line_complete(env, buf, cap, &len, &cursor, prompt, complete,
   1551                     complete_user);
   1552       continue;
   1553     }
   1554     if (c == 1) {
   1555       cursor = 0;
   1556       line_redraw(prompt, buf, len, cursor);
   1557       continue;
   1558     }
   1559     if (c == 5) {
   1560       cursor = len;
   1561       line_redraw(prompt, buf, len, cursor);
   1562       continue;
   1563     }
   1564     if (c == 11) {
   1565       buf[cursor] = '\0';
   1566       len = cursor;
   1567       line_redraw(prompt, buf, len, cursor);
   1568       continue;
   1569     }
   1570     if (c == 127 || c == 8) {
   1571       if (cursor == 0) {
   1572         fputc('\a', stdout);
   1573         fflush(stdout);
   1574         continue;
   1575       }
   1576       memmove(buf + cursor - 1u, buf + cursor, len - cursor + 1u);
   1577       --cursor;
   1578       --len;
   1579       line_redraw(prompt, buf, len, cursor);
   1580       continue;
   1581     }
   1582     if (c == 27) {
   1583       unsigned char seq[3];
   1584       ssize_t r1 = read(STDIN_FILENO, seq, 1);
   1585       ssize_t r2 = read(STDIN_FILENO, seq + 1, 1);
   1586       if (r1 != 1 || r2 != 1 || seq[0] != '[') continue;
   1587       if (seq[1] == 'A') {
   1588         if (hist && hist->count > 0 && hist_pos > 0) {
   1589           if (!have_saved) {
   1590             memcpy(saved, buf, len + 1u);
   1591             have_saved = 1;
   1592           }
   1593           --hist_pos;
   1594           line_set(buf, cap, &len, &cursor, hist->items[hist_pos]);
   1595           line_redraw(prompt, buf, len, cursor);
   1596         }
   1597       } else if (seq[1] == 'B') {
   1598         if (hist && have_saved && hist_pos < hist->count) {
   1599           ++hist_pos;
   1600           if (hist_pos == hist->count)
   1601             line_set(buf, cap, &len, &cursor, saved);
   1602           else
   1603             line_set(buf, cap, &len, &cursor, hist->items[hist_pos]);
   1604           line_redraw(prompt, buf, len, cursor);
   1605         }
   1606       } else if (seq[1] == 'C') {
   1607         if (cursor < len) {
   1608           ++cursor;
   1609           line_redraw(prompt, buf, len, cursor);
   1610         }
   1611       } else if (seq[1] == 'D') {
   1612         if (cursor > 0) {
   1613           --cursor;
   1614           line_redraw(prompt, buf, len, cursor);
   1615         }
   1616       } else if (seq[1] == 'H') {
   1617         cursor = 0;
   1618         line_redraw(prompt, buf, len, cursor);
   1619       } else if (seq[1] == 'F') {
   1620         cursor = len;
   1621         line_redraw(prompt, buf, len, cursor);
   1622       } else if (seq[1] >= '1' && seq[1] <= '9') {
   1623         ssize_t r3 = read(STDIN_FILENO, seq + 2, 1);
   1624         if (r3 == 1 && seq[1] == '3' && seq[2] == '~' && cursor < len) {
   1625           memmove(buf + cursor, buf + cursor + 1u, len - cursor);
   1626           --len;
   1627           line_redraw(prompt, buf, len, cursor);
   1628         }
   1629       }
   1630       continue;
   1631     }
   1632     if (c >= 32 && c != 127) {
   1633       if (len + 1u >= cap) {
   1634         fputc('\a', stdout);
   1635         fflush(stdout);
   1636         continue;
   1637       }
   1638       memmove(buf + cursor + 1u, buf + cursor, len - cursor + 1u);
   1639       buf[cursor] = (char)c;
   1640       ++cursor;
   1641       ++len;
   1642       line_redraw(prompt, buf, len, cursor);
   1643     }
   1644   }
   1645 
   1646 out:
   1647   if (raw_enabled) tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig);
   1648   if (saved) driver_free(env, saved, saved_size);
   1649   return out_rc;
   1650 }
   1651 
   1652 /* ---------------- dlsym resolver ---------------- */
   1653 
   1654 void* driver_dlsym_resolver(void* user, KitSlice name_s) {
   1655   /* The linker hands us interned/pool slices that are NUL-terminated, so
   1656    * we can pass .s straight through to the OS-specific os_dlsym. */
   1657   (void)user;
   1658   if (!name_s.s || name_s.len == 0) return NULL;
   1659   return os_dlsym(name_s.s);
   1660 }
   1661 
   1662 /* ---------------- SIGINT handler for the dbg REPL ---------------- */
   1663 
   1664 static void (*s_sigint_cb)(void*);
   1665 static void* s_sigint_cb_user;
   1666 
   1667 static void sigint_trampoline(int sig) {
   1668   (void)sig;
   1669   if (s_sigint_cb) s_sigint_cb(s_sigint_cb_user);
   1670 }
   1671 
   1672 int driver_install_sigint(void (*cb)(void*), void* user) {
   1673   struct sigaction sa;
   1674   s_sigint_cb = cb;
   1675   s_sigint_cb_user = user;
   1676   sa.sa_handler = sigint_trampoline;
   1677   sigemptyset(&sa.sa_mask);
   1678   sa.sa_flags = 0; /* no SA_RESTART: fgetc returns EINTR */
   1679   return sigaction(SIGINT, &sa, NULL) == 0 ? 0 : 1;
   1680 }
   1681 
   1682 void driver_restore_sigint(void) {
   1683   struct sigaction sa;
   1684   s_sigint_cb = NULL;
   1685   s_sigint_cb_user = NULL;
   1686   sa.sa_handler = SIG_DFL;
   1687   sigemptyset(&sa.sa_mask);
   1688   sa.sa_flags = 0;
   1689   sigaction(SIGINT, &sa, NULL);
   1690 }
   1691 
   1692 /* ---------------- host target ---------------- */
   1693 
   1694 static KitArchKind host_arch_self(void) {
   1695 #if defined(__x86_64__)
   1696   return KIT_ARCH_X86_64;
   1697 #elif defined(__aarch64__)
   1698   return KIT_ARCH_ARM_64;
   1699 #elif defined(__arm__)
   1700   return KIT_ARCH_ARM_32;
   1701 #elif defined(__i386__)
   1702   return KIT_ARCH_X86_32;
   1703 #elif defined(__riscv) && (__riscv_xlen == 64)
   1704   return KIT_ARCH_RV64;
   1705 #elif defined(__riscv) && (__riscv_xlen == 32)
   1706   return KIT_ARCH_RV32;
   1707 #elif defined(__wasm__)
   1708   return KIT_ARCH_WASM;
   1709 #else
   1710   return KIT_ARCH_X86_64;
   1711 #endif
   1712 }
   1713 
   1714 KitTargetSpec driver_host_target(void) {
   1715   KitTargetSpec t = {0};
   1716   t.arch = host_arch_self();
   1717   os_host_target_fill(&t);
   1718   t.ptr_size = (uint8_t)sizeof(void*);
   1719   t.ptr_align = (uint8_t)sizeof(void*);
   1720   t.big_endian = 0;
   1721   t.pic = driver_default_pic(t.obj, t.os);
   1722   t.code_model = KIT_CM_DEFAULT;
   1723   return t;
   1724 }
   1725 
   1726 /* ---------------- env wiring (POSIX) ---------------- */
   1727 
   1728 char g_cache_dir[4096];
   1729 
   1730 void driver_env_init(DriverEnv* e) {
   1731   e->heap = &g_heap_libc;
   1732   e->diag = &g_diag_stderr;
   1733   e->file_io.read_all = posix_read_all;
   1734   e->file_io.release = posix_release;
   1735   e->file_io.open_writer = posix_open_writer;
   1736   e->file_io.user = e;
   1737 
   1738   g_execmem_posix.page_size = driver_host_page_size();
   1739   g_execmem_posix.reserve = execmem_reserve;
   1740   g_execmem_posix.protect = execmem_protect;
   1741   g_execmem_posix.release = execmem_release;
   1742   g_execmem_posix.flush_icache = execmem_flush_icache;
   1743   g_execmem_posix.user = NULL;
   1744   e->execmem = &g_execmem_posix;
   1745 
   1746   e->dbg_os = &g_dbg_os_posix;
   1747   /* Opt-in compile metrics (KIT_METRICS): one process-wide profiler backs both
   1748    * the heap counters and libkit's scope timers. NULL when unset -- the metrics
   1749    * hot path is then a single pointer check. */
   1750   e->profiler = driver_metrics_profiler();
   1751 
   1752   {
   1753     const char* xdg = getenv("XDG_CACHE_HOME");
   1754     const char* home = getenv("HOME");
   1755     if (xdg && *xdg) {
   1756       snprintf(g_cache_dir, sizeof(g_cache_dir), "%s/kit", xdg);
   1757     } else if (home && *home) {
   1758       snprintf(g_cache_dir, sizeof(g_cache_dir), "%s/.cache/kit", home);
   1759     } else {
   1760       snprintf(g_cache_dir, sizeof(g_cache_dir), "build/kit-cache");
   1761     }
   1762     e->cache_dir = g_cache_dir;
   1763   }
   1764 
   1765   /* Reproducible-build precedent: SOURCE_DATE_EPOCH wins over wall clock. */
   1766   {
   1767     const char* sde = getenv("SOURCE_DATE_EPOCH");
   1768     if (sde && *sde) {
   1769       char* endp = NULL;
   1770       long long v = strtoll(sde, &endp, 10);
   1771       e->now = (endp != sde && v >= 0) ? (int64_t)v : (int64_t)-1;
   1772     } else {
   1773       time_t t = time(NULL);
   1774       e->now = (t == (time_t)-1) ? (int64_t)-1 : (int64_t)t;
   1775     }
   1776   }
   1777 }
   1778 
   1779 void driver_env_fini(DriverEnv* e) {
   1780   /* Singletons; nothing to release. */
   1781   (void)e;
   1782 }
   1783 
   1784 KitContext driver_env_to_context(const DriverEnv* e) {
   1785   KitContext c;
   1786   c.heap = e->heap;
   1787   c.file_io = &e->file_io;
   1788   c.diag = e->diag;
   1789   c.profiler = e->profiler;
   1790   c.now = e->now;
   1791   return c;
   1792 }
   1793 
   1794 KitJitHost driver_env_to_jit_host(const DriverEnv* e) {
   1795   KitJitHost h;
   1796   h.execmem = e->execmem;
   1797   return h;
   1798 }
   1799 
   1800 KitDbgHost driver_env_to_dbg_host(const DriverEnv* e) {
   1801   KitDbgHost h;
   1802   h.os = e->dbg_os;
   1803   return h;
   1804 }