kit

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

windows.c (79207B)


      1 /* Windows host environment. Replaces posix.c and posix_dbg.c on Win32
      2  * builds; common.c is reused unchanged. Built against the Win32 API with
      3  * MinGW-w64 in mind.
      4  *
      5  * Coverage:
      6  *   - file_io (CreateFileW/ReadFile/WriteFile + UTF-8 path conversion)
      7  *   - path helpers (GetFileAttributesExW for exists/mtime, mkdir_p via
      8  *     CreateDirectoryW, mark_executable is a no-op on NTFS)
      9  *   - stdin / read_line / edit_temp (GetTempPath + system())
     10  *   - monotonic time (QueryPerformanceCounter)
     11  *   - SIGINT shim via SetConsoleCtrlHandler
     12  *   - dlsym via GetProcAddress over an EnumProcessModules snapshot
     13  *   - execmem: dual-mapping via CreateFileMappingW + MapViewOfFile; the
     14  *     write alias is RW, the runtime alias is RX after a protect flip.
     15  *     Single-mapping fallback uses VirtualAlloc.
     16  *   - dbg_os: CreateThread for the worker, event objects, vectored
     17  *     exception handling for SEGV/ILL/BP/etc, VEH-based guarded_copy,
     18  *     setjmp/longjmp for call_with_catch / thread_abort, SuspendThread +
     19  *     GetThreadContext for the interrupt path (the interrupt's on_fault
     20  *     runs on the *caller* thread; natural faults run on the worker
     21  *     thread inside the VEH, matching POSIX semantics there).
     22  *
     23  * The W^X model on Windows mirrors the Linux/FreeBSD memfd path: a single
     24  * pagefile-backed file-mapping object is mapped twice -- once RW (write
     25  * alias) and once RX (runtime alias). The dbg code-write path translates
     26  * a runtime address into the corresponding write alias via the same
     27  * exec_dual registry the POSIX side uses (re-implemented locally with
     28  * SRWLock since we don't link the POSIX TUs). Single-mapping reservations
     29  * (no KIT_PROT_EXEC) just VirtualAlloc a single RW region.
     30  */
     31 
     32 #ifndef WIN32_LEAN_AND_MEAN
     33 #define WIN32_LEAN_AND_MEAN
     34 #endif
     35 #ifndef _WIN32_WINNT
     36 #define _WIN32_WINNT \
     37   0x0601 /* Windows 7+: SRWLock, AddVectoredExceptionHandler */
     38 #endif
     39 /* rand_s (the OS CSPRNG, used by driver_random_bytes) is declared in
     40  * <stdlib.h> only when _CRT_RAND_S is defined first. It must be set before any
     41  * include: several headers below transitively pull in <stdlib.h>, and on some
     42  * arches (x86_64 mingw) that happens before stdlib.h's own include, so a
     43  * define placed right above #include <stdlib.h> arrives too late. */
     44 #ifndef _CRT_RAND_S
     45 #define _CRT_RAND_S
     46 #endif
     47 /* windows.h must precede psapi.h/process.h: the mingw SDK headers use
     48  * windows.h's WINBOOL/DWORD/LPVOID etc. and do not self-include it. It sits in
     49  * its OWN include group (blank line below) so a formatter's per-group alphabetic
     50  * sort cannot reorder it after psapi.h — which would leave EnumProcesses et al.
     51  * as bare `WINAPI` decls that the mingw cross-build's frontend then rejects. */
     52 // clang-format off
     53 #include <windows.h>
     54 // clang-format on
     55 
     56 #include <fcntl.h>
     57 #include <io.h>
     58 #include <process.h>
     59 #include <psapi.h>
     60 #include <setjmp.h>
     61 #include <stdint.h>
     62 #include <stdio.h>
     63 #include <stdlib.h>
     64 #include <string.h>
     65 #include <sys/stat.h>
     66 #include <time.h>
     67 #include <wchar.h>
     68 
     69 #include "env_internal.h"
     70 
     71 /* _environ comes from <stdlib.h> (mingw defines it as a macro over
     72  * __p__environ()); driver_environ() below uses it directly — no manual extern,
     73  * which would clash with the header's dllimport declaration under a strict cc. */
     74 
     75 /* Win32 dbg interrupt code: a synthetic signo handed up to on_fault. The
     76  * value just needs to be distinct from real exception codes; we pick a
     77  * small positive int so it round-trips through the int field of
     78  * KitDbgOs.interrupt_signo. */
     79 #define DBG_WIN_INTERRUPT_SIGNO 100
     80 
     81 /* ============================================================
     82  *   UTF-8 <-> UTF-16 path conversion
     83  * ============================================================ */
     84 
     85 /* Convert a UTF-8 path to a freshly-malloc'd wide string. Returns NULL on
     86  * empty input or allocation failure. Callers free with `free`. */
     87 static wchar_t* widen(const char* utf8) {
     88   int need;
     89   wchar_t* w;
     90   if (!utf8 || !*utf8) return NULL;
     91   need = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0);
     92   if (need <= 0) return NULL;
     93   w = (wchar_t*)malloc((size_t)need * sizeof(wchar_t));
     94   if (!w) return NULL;
     95   if (MultiByteToWideChar(CP_UTF8, 0, utf8, -1, w, need) <= 0) {
     96     free(w);
     97     return NULL;
     98   }
     99   return w;
    100 }
    101 
    102 static char* narrow(const wchar_t* wide) {
    103   int need;
    104   char* out;
    105   if (!wide) return NULL;
    106   need = WideCharToMultiByte(CP_UTF8, 0, wide, -1, NULL, 0, NULL, NULL);
    107   if (need <= 0) return NULL;
    108   out = (char*)malloc((size_t)need);
    109   if (!out) return NULL;
    110   if (WideCharToMultiByte(CP_UTF8, 0, wide, -1, out, need, NULL, NULL) <= 0) {
    111     free(out);
    112     return NULL;
    113   }
    114   return out;
    115 }
    116 
    117 int driver_path_canonicalize(DriverEnv* env, const char* path, char** out,
    118                              size_t* out_size) {
    119   wchar_t* wpath;
    120   wchar_t* wfull;
    121   DWORD need;
    122   char* narrowed;
    123   char* copy;
    124   size_t size;
    125   if (!env || !path || !path[0] || !out || !out_size) return 1;
    126   wpath = widen(path);
    127   if (!wpath) return 1;
    128   need = GetFullPathNameW(wpath, 0, NULL, NULL);
    129   if (!need) {
    130     free(wpath);
    131     return 1;
    132   }
    133   wfull = (wchar_t*)malloc((size_t)need * sizeof(*wfull));
    134   if (!wfull || GetFullPathNameW(wpath, need, wfull, NULL) == 0) {
    135     free(wfull);
    136     free(wpath);
    137     return 1;
    138   }
    139   free(wpath);
    140   narrowed = narrow(wfull);
    141   free(wfull);
    142   if (!narrowed || !driver_path_exists(narrowed)) {
    143     free(narrowed);
    144     return 1;
    145   }
    146   size = driver_strlen(narrowed) + 1u;
    147   copy = (char*)driver_alloc(env, size);
    148   if (!copy) {
    149     free(narrowed);
    150     return 1;
    151   }
    152   driver_memcpy(copy, narrowed, size);
    153   free(narrowed);
    154   *out = copy;
    155   *out_size = size;
    156   return 0;
    157 }
    158 
    159 /* ============================================================
    160  *   exec_dual registry (write/runtime alias bookkeeping)
    161  * ============================================================ */
    162 
    163 typedef struct ExecDualNode {
    164   void* write_base;
    165   void* runtime_base;
    166   size_t size;
    167   struct ExecDualNode* next;
    168 } ExecDualNode;
    169 
    170 static ExecDualNode* g_jit_dual_map;
    171 static SRWLOCK g_jit_dual_map_lock = SRWLOCK_INIT;
    172 
    173 static void exec_dual_register_w(void* write_base, void* runtime_base,
    174                                  size_t size) {
    175   ExecDualNode* n;
    176   if (write_base == runtime_base) return;
    177   n = (ExecDualNode*)malloc(sizeof(*n));
    178   if (!n) return;
    179   n->write_base = write_base;
    180   n->runtime_base = runtime_base;
    181   n->size = size;
    182   AcquireSRWLockExclusive(&g_jit_dual_map_lock);
    183   n->next = g_jit_dual_map;
    184   g_jit_dual_map = n;
    185   ReleaseSRWLockExclusive(&g_jit_dual_map_lock);
    186 }
    187 
    188 static void exec_dual_unregister_w(void* runtime_base) {
    189   ExecDualNode** pp;
    190   AcquireSRWLockExclusive(&g_jit_dual_map_lock);
    191   for (pp = &g_jit_dual_map; *pp; pp = &(*pp)->next) {
    192     if ((*pp)->runtime_base == runtime_base) {
    193       ExecDualNode* dead = *pp;
    194       *pp = dead->next;
    195       free(dead);
    196       break;
    197     }
    198   }
    199   ReleaseSRWLockExclusive(&g_jit_dual_map_lock);
    200 }
    201 
    202 static int exec_dual_lookup_w(void* runtime_addr, size_t n, void** write_out) {
    203   ExecDualNode* cur;
    204   uintptr_t a = (uintptr_t)runtime_addr;
    205   AcquireSRWLockShared(&g_jit_dual_map_lock);
    206   for (cur = g_jit_dual_map; cur; cur = cur->next) {
    207     uintptr_t base = (uintptr_t)cur->runtime_base;
    208     if (a >= base && a + n <= base + cur->size) {
    209       *write_out = (void*)((uintptr_t)cur->write_base + (a - base));
    210       ReleaseSRWLockShared(&g_jit_dual_map_lock);
    211       return 0;
    212     }
    213   }
    214   ReleaseSRWLockShared(&g_jit_dual_map_lock);
    215   return 1;
    216 }
    217 
    218 /* ============================================================
    219  *   exec memory: dual-map via CreateFileMappingW, single via VirtualAlloc
    220  * ============================================================ */
    221 
    222 typedef struct ExecMemTokenWin {
    223   HANDLE mapping; /* NULL for single-mapping reservations */
    224   void* write_addr;
    225   void* runtime_addr;
    226   size_t size;
    227 } ExecMemTokenWin;
    228 
    229 static DWORD kit_to_win_prot(int prot) {
    230   int r = (prot & KIT_PROT_READ) != 0;
    231   int w = (prot & KIT_PROT_WRITE) != 0;
    232   int x = (prot & KIT_PROT_EXEC) != 0;
    233   if (x && w) return PAGE_EXECUTE_READWRITE;
    234   if (x && r) return PAGE_EXECUTE_READ;
    235   if (x) return PAGE_EXECUTE;
    236   if (w) return PAGE_READWRITE;
    237   if (r) return PAGE_READONLY;
    238   return PAGE_NOACCESS;
    239 }
    240 
    241 static size_t driver_host_page_size_win(void) {
    242   SYSTEM_INFO si;
    243   GetSystemInfo(&si);
    244   return si.dwPageSize ? (size_t)si.dwPageSize : (size_t)0x1000;
    245 }
    246 
    247 static KitStatus execmem_reserve_single_win(size_t size,
    248                                             KitExecMemRegion* out) {
    249   void* p = VirtualAlloc(NULL, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    250   if (!p) return KIT_NOMEM;
    251   out->write = p;
    252   out->runtime = p;
    253   out->size = size;
    254   out->token = NULL;
    255   return KIT_OK;
    256 }
    257 
    258 static KitStatus execmem_reserve_dual_win(size_t size, KitExecMemRegion* out) {
    259   HANDLE map;
    260   void* w;
    261   void* r;
    262   ExecMemTokenWin* tok;
    263   DWORD lo = (DWORD)(size & 0xFFFFFFFFu);
    264   DWORD hi = (DWORD)((uint64_t)size >> 32);
    265 
    266   /* PAGE_EXECUTE_READWRITE on the section object is the max protection any
    267    * view can request; per-view protections are narrower (RW vs RX). */
    268   map = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, PAGE_EXECUTE_READWRITE,
    269                            hi, lo, NULL);
    270   if (!map) return KIT_ERR;
    271 
    272   w = MapViewOfFile(map, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, size);
    273   if (!w) {
    274     CloseHandle(map);
    275     return KIT_NOMEM;
    276   }
    277   /* Include FILE_MAP_WRITE in the runtime/exec view's mapping access. A view's
    278    * VirtualProtect ceiling is bounded by its map-access flags, not just the
    279    * section's max protection: a view mapped READ|EXECUTE can be reprotected to
    280    * R/RX/RO but NOT to PAGE_READWRITE (err 87). JIT images carry writable
    281    * runtime segments (an import GOT, .data/.bss) whose final perms are RW, so
    282    * the exec view must permit write to be VirtualProtect'd RW. Per-segment page
    283    * protection still enforces W^X (code stays RX, never writable). */
    284   r = MapViewOfFile(map, FILE_MAP_READ | FILE_MAP_WRITE | FILE_MAP_EXECUTE, 0,
    285                     0, size);
    286   if (!r) {
    287     UnmapViewOfFile(w);
    288     CloseHandle(map);
    289     return KIT_NOMEM;
    290   }
    291 
    292   tok = (ExecMemTokenWin*)malloc(sizeof(*tok));
    293   if (!tok) {
    294     UnmapViewOfFile(r);
    295     UnmapViewOfFile(w);
    296     CloseHandle(map);
    297     return KIT_NOMEM;
    298   }
    299   tok->mapping = map;
    300   tok->write_addr = w;
    301   tok->runtime_addr = r;
    302   tok->size = size;
    303 
    304   exec_dual_register_w(w, r, size);
    305 
    306   out->write = w;
    307   out->runtime = r;
    308   out->size = size;
    309   out->token = tok;
    310   return KIT_OK;
    311 }
    312 
    313 static KitStatus execmem_reserve_win(void* user, size_t size, int prot,
    314                                      KitExecMemRegion* out) {
    315   (void)user;
    316   if (!out || !size) return KIT_INVALID;
    317   if (prot & KIT_PROT_EXEC) return execmem_reserve_dual_win(size, out);
    318   return execmem_reserve_single_win(size, out);
    319 }
    320 
    321 static KitStatus execmem_protect_win(void* user, void* addr, size_t size,
    322                                      int prot) {
    323   DWORD old;
    324   (void)user;
    325   return VirtualProtect(addr, size, kit_to_win_prot(prot), &old) ? KIT_OK
    326                                                                  : KIT_ERR;
    327 }
    328 
    329 static void execmem_release_win(void* user, KitExecMemRegion* region) {
    330   (void)user;
    331   if (!region || !region->size) return;
    332   if (region->token) {
    333     ExecMemTokenWin* tok = (ExecMemTokenWin*)region->token;
    334     if (tok->runtime_addr && tok->runtime_addr != tok->write_addr) {
    335       exec_dual_unregister_w(tok->runtime_addr);
    336       UnmapViewOfFile(tok->runtime_addr);
    337     }
    338     if (tok->write_addr) UnmapViewOfFile(tok->write_addr);
    339     if (tok->mapping) CloseHandle(tok->mapping);
    340     free(tok);
    341   } else if (region->write) {
    342     VirtualFree(region->write, 0, MEM_RELEASE);
    343   }
    344   region->write = NULL;
    345   region->runtime = NULL;
    346   region->size = 0;
    347   region->token = NULL;
    348 }
    349 
    350 static void execmem_flush_icache_win(void* user, void* addr, size_t size) {
    351   (void)user;
    352   FlushInstructionCache(GetCurrentProcess(), addr, size);
    353 }
    354 
    355 static KitExecMem g_execmem_win;
    356 
    357 /* ============================================================
    358  *   Writer vtables: HANDLE-backed and stdio-backed
    359  * ============================================================ */
    360 
    361 typedef struct DriverHandleWriter {
    362   KitWriter base;
    363   KitHeap* heap;
    364   HANDLE h;
    365   KitStatus status;
    366   uint64_t pos;
    367   wchar_t* tmp_path;
    368   wchar_t* final_path;
    369 } DriverHandleWriter;
    370 
    371 static KitStatus hw_write(KitWriter* w, const void* data, size_t n) {
    372   DriverHandleWriter* fw = (DriverHandleWriter*)w;
    373   const unsigned char* p = (const unsigned char*)data;
    374   if (fw->status != KIT_OK) return fw->status;
    375   while (n > 0) {
    376     DWORD chunk = n > 0x40000000u ? 0x40000000u : (DWORD)n;
    377     DWORD wrote = 0;
    378     if (!WriteFile(fw->h, p, chunk, &wrote, NULL) || wrote == 0) {
    379       fw->status = KIT_IO;
    380       return KIT_IO;
    381     }
    382     p += wrote;
    383     n -= wrote;
    384     fw->pos += wrote;
    385   }
    386   return KIT_OK;
    387 }
    388 
    389 static KitStatus hw_seek(KitWriter* w, uint64_t off) {
    390   DriverHandleWriter* fw = (DriverHandleWriter*)w;
    391   LARGE_INTEGER li;
    392   if (fw->status != KIT_OK) return fw->status;
    393   li.QuadPart = (LONGLONG)off;
    394   if (!SetFilePointerEx(fw->h, li, NULL, FILE_BEGIN)) {
    395     fw->status = KIT_IO;
    396     return KIT_IO;
    397   }
    398   fw->pos = off;
    399   return KIT_OK;
    400 }
    401 
    402 static uint64_t hw_tell(KitWriter* w) { return ((DriverHandleWriter*)w)->pos; }
    403 static KitStatus hw_status(KitWriter* w) {
    404   return ((DriverHandleWriter*)w)->status;
    405 }
    406 static void hw_close(KitWriter* w) {
    407   DriverHandleWriter* fw = (DriverHandleWriter*)w;
    408   KitStatus st = fw->status;
    409   if (fw->h && fw->h != INVALID_HANDLE_VALUE) {
    410     if (!CloseHandle(fw->h) && st == KIT_OK) st = KIT_IO;
    411   }
    412   if (fw->tmp_path) {
    413     if (st == KIT_OK &&
    414         !MoveFileExW(fw->tmp_path, fw->final_path,
    415                      MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) {
    416       st = KIT_IO;
    417       fw->status = KIT_IO;
    418     }
    419     if (st != KIT_OK) DeleteFileW(fw->tmp_path);
    420     free(fw->tmp_path);
    421     free(fw->final_path);
    422   }
    423   fw->heap->free(fw->heap, fw, sizeof(*fw));
    424 }
    425 
    426 static KitWriter* driver_writer_handle(KitHeap* h, HANDLE fh) {
    427   DriverHandleWriter* fw = (DriverHandleWriter*)h->alloc(
    428       h, sizeof(*fw), _Alignof(DriverHandleWriter));
    429   if (!fw) return NULL;
    430   fw->base.write = hw_write;
    431   fw->base.seek = hw_seek;
    432   fw->base.tell = hw_tell;
    433   fw->base.status = hw_status;
    434   fw->base.close = hw_close;
    435   fw->heap = h;
    436   fw->h = fh;
    437   fw->status = KIT_OK;
    438   fw->pos = 0;
    439   fw->tmp_path = NULL;
    440   fw->final_path = NULL;
    441   return &fw->base;
    442 }
    443 
    444 typedef struct DriverStdioWriter {
    445   KitWriter base;
    446   KitHeap* heap;
    447   FILE* fp;
    448   KitStatus status;
    449 } DriverStdioWriter;
    450 
    451 static KitStatus stdio_w_write(KitWriter* w, const void* data, size_t n) {
    452   DriverStdioWriter* sw = (DriverStdioWriter*)w;
    453   if (n) {
    454     size_t got = fwrite(data, 1, n, sw->fp);
    455     if (got != n) {
    456       sw->status = KIT_IO;
    457       return KIT_IO;
    458     }
    459   }
    460   return KIT_OK;
    461 }
    462 static KitStatus stdio_w_seek(KitWriter* w, uint64_t off) {
    463   DriverStdioWriter* sw = (DriverStdioWriter*)w;
    464   return fseek(sw->fp, (long)off, SEEK_SET) == 0 ? KIT_OK : KIT_IO;
    465 }
    466 static uint64_t stdio_w_tell(KitWriter* w) {
    467   long t = ftell(((DriverStdioWriter*)w)->fp);
    468   return t < 0 ? 0u : (uint64_t)t;
    469 }
    470 static KitStatus stdio_w_status(KitWriter* w) {
    471   DriverStdioWriter* sw = (DriverStdioWriter*)w;
    472   if (sw->status != KIT_OK) return sw->status;
    473   return ferror(sw->fp) ? KIT_IO : KIT_OK;
    474 }
    475 static void stdio_w_close(KitWriter* w) {
    476   DriverStdioWriter* sw = (DriverStdioWriter*)w;
    477   fflush(sw->fp);
    478   sw->heap->free(sw->heap, sw, sizeof(*sw));
    479 }
    480 
    481 static KitWriter* driver_stdio_writer(DriverEnv* e, FILE* fp) {
    482   DriverStdioWriter* sw = (DriverStdioWriter*)e->heap->alloc(
    483       e->heap, sizeof(*sw), _Alignof(DriverStdioWriter));
    484   if (!sw) return NULL;
    485   sw->base.write = stdio_w_write;
    486   sw->base.seek = stdio_w_seek;
    487   sw->base.tell = stdio_w_tell;
    488   sw->base.status = stdio_w_status;
    489   sw->base.close = stdio_w_close;
    490   sw->heap = e->heap;
    491   sw->fp = fp;
    492   sw->status = KIT_OK;
    493   return &sw->base;
    494 }
    495 
    496 KitWriter* driver_stdout_writer(DriverEnv* e) {
    497   return driver_stdio_writer(e, stdout);
    498 }
    499 
    500 KitWriter* driver_stderr_writer(DriverEnv* e) {
    501   return driver_stdio_writer(e, stderr);
    502 }
    503 
    504 void driver_writer_abort(KitWriter* writer) {
    505   if (!writer) return;
    506   if (writer->close == hw_close) {
    507     ((DriverHandleWriter*)writer)->status = KIT_ERR;
    508   } else if (writer->close == stdio_w_close) {
    509     ((DriverStdioWriter*)writer)->status = KIT_ERR;
    510   }
    511 }
    512 
    513 const char* const* driver_environ(void) { return (const char* const*)_environ; }
    514 
    515 int driver_touch(const char* path) {
    516   wchar_t* wpath;
    517   HANDLE h;
    518   FILETIME ft;
    519   if (!path) return 1;
    520   wpath = widen(path);
    521   if (!wpath) return 1;
    522   h = CreateFileW(wpath, FILE_WRITE_ATTRIBUTES,
    523                   FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS,
    524                   FILE_ATTRIBUTE_NORMAL, NULL);
    525   free(wpath);
    526   if (h == INVALID_HANDLE_VALUE) return 1;
    527   GetSystemTimeAsFileTime(&ft);
    528   SetFileTime(h, NULL, &ft, &ft);
    529   CloseHandle(h);
    530   return 0;
    531 }
    532 
    533 /* ============================================================
    534  *   file_io (CreateFileW + ReadFile/WriteFile)
    535  * ============================================================ */
    536 
    537 static KitStatus win_read_all(void* user, const char* path, KitFileData* out) {
    538   DriverEnv* env = (DriverEnv*)user;
    539   wchar_t* wpath;
    540   HANDLE h;
    541   LARGE_INTEGER sz;
    542   size_t size;
    543   size_t got;
    544   void* buf;
    545 
    546   wpath = widen(path);
    547   if (!wpath) return KIT_NOT_FOUND;
    548   h = CreateFileW(wpath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
    549                   FILE_ATTRIBUTE_NORMAL, NULL);
    550   free(wpath);
    551   if (h == INVALID_HANDLE_VALUE) return KIT_NOT_FOUND;
    552   if (!GetFileSizeEx(h, &sz)) {
    553     CloseHandle(h);
    554     return KIT_IO;
    555   }
    556   size = (size_t)sz.QuadPart;
    557   buf = size ? env->heap->alloc(env->heap, size, 1) : NULL;
    558   if (size && !buf) {
    559     CloseHandle(h);
    560     return KIT_NOMEM;
    561   }
    562   got = 0;
    563   while (got < size) {
    564     DWORD chunk =
    565         (size - got) > 0x40000000u ? 0x40000000u : (DWORD)(size - got);
    566     DWORD n = 0;
    567     if (!ReadFile(h, (unsigned char*)buf + got, chunk, &n, NULL) || n == 0) {
    568       env->heap->free(env->heap, buf, size);
    569       CloseHandle(h);
    570       return KIT_IO;
    571     }
    572     got += n;
    573   }
    574   CloseHandle(h);
    575   out->data = (const uint8_t*)buf;
    576   out->size = size;
    577   out->token = buf;
    578   return KIT_OK;
    579 }
    580 
    581 static void win_release(void* user, KitFileData* d) {
    582   DriverEnv* env = (DriverEnv*)user;
    583   if (d->token) env->heap->free(env->heap, d->token, d->size);
    584   d->data = NULL;
    585   d->size = 0;
    586   d->token = NULL;
    587 }
    588 
    589 static wchar_t* win_wcsdup(const wchar_t* s) {
    590   size_t n;
    591   wchar_t* out;
    592   if (!s) return NULL;
    593   n = (size_t)lstrlenW(s) + 1u;
    594   out = (wchar_t*)malloc(n * sizeof(*out));
    595   if (out) memcpy(out, s, n * sizeof(*out));
    596   return out;
    597 }
    598 
    599 static int win_ascii_eq_ci(const char* a, const char* b) {
    600   while (*a && *b) {
    601     char ca = *a++;
    602     char cb = *b++;
    603     if (ca >= 'A' && ca <= 'Z') ca = (char)(ca - 'A' + 'a');
    604     if (cb >= 'A' && cb <= 'Z') cb = (char)(cb - 'A' + 'a');
    605     if (ca != cb) return 0;
    606   }
    607   return *a == '\0' && *b == '\0';
    608 }
    609 
    610 static int win_special_output_path(const char* path) {
    611   const char* p;
    612   if (!path || !*path) return 0;
    613   for (p = path; *p; ++p) {
    614     if (*p == '/' || *p == '\\') return 0;
    615   }
    616   return win_ascii_eq_ci(path, "nul") || win_ascii_eq_ci(path, "nul:");
    617 }
    618 
    619 static wchar_t* win_output_dir_wide(const char* path) {
    620   const char* last = NULL;
    621   const char* p;
    622   char* dir;
    623   wchar_t* wdir;
    624   size_t n;
    625   for (p = path; *p; ++p) {
    626     if (*p == '/' || *p == '\\') last = p;
    627   }
    628   if (!last) return widen(".");
    629   n = (size_t)(last - path);
    630   if (n == 0)
    631     n = 1u;
    632   else if (n == 2u && path[1] == ':')
    633     n = 3u;
    634   dir = (char*)malloc(n + 1u);
    635   if (!dir) return NULL;
    636   memcpy(dir, path, n);
    637   dir[n] = '\0';
    638   wdir = widen(dir);
    639   free(dir);
    640   return wdir;
    641 }
    642 
    643 static KitStatus win_open_writer(void* user, const char* path,
    644                                  KitWriter** out) {
    645   DriverEnv* env = (DriverEnv*)user;
    646   wchar_t* wpath = widen(path);
    647   wchar_t* wdir = NULL;
    648   wchar_t* wtmp_dup = NULL;
    649   wchar_t tmp_path[MAX_PATH];
    650   HANDLE h;
    651   KitWriter* w;
    652   int special = win_special_output_path(path);
    653   if (!wpath) return KIT_IO;
    654   if (special) {
    655     h = CreateFileW(wpath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS,
    656                     FILE_ATTRIBUTE_NORMAL, NULL);
    657   } else {
    658     wdir = win_output_dir_wide(path);
    659     if (!wdir || !GetTempFileNameW(wdir, L"kit", 0, tmp_path)) {
    660       free(wdir);
    661       free(wpath);
    662       return KIT_IO;
    663     }
    664     free(wdir);
    665     h = CreateFileW(tmp_path, GENERIC_WRITE, FILE_SHARE_READ, NULL,
    666                     CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    667     if (h == INVALID_HANDLE_VALUE) {
    668       DeleteFileW(tmp_path);
    669       free(wpath);
    670       return KIT_IO;
    671     }
    672   }
    673   if (h == INVALID_HANDLE_VALUE) {
    674     free(wpath);
    675     return KIT_IO;
    676   }
    677   w = driver_writer_handle(env->heap, h);
    678   if (!w) {
    679     CloseHandle(h);
    680     if (!special) DeleteFileW(tmp_path);
    681     free(wpath);
    682     return KIT_NOMEM;
    683   }
    684   if (!special) {
    685     DriverHandleWriter* hw = (DriverHandleWriter*)w;
    686     wtmp_dup = win_wcsdup(tmp_path);
    687     if (!wtmp_dup) {
    688       kit_writer_close(w);
    689       DeleteFileW(tmp_path);
    690       free(wpath);
    691       return KIT_NOMEM;
    692     }
    693     hw->tmp_path = wtmp_dup;
    694     hw->final_path = wpath;
    695   } else {
    696     free(wpath);
    697   }
    698   *out = w;
    699   return KIT_OK;
    700 }
    701 
    702 /* ============================================================
    703  *   Path helpers
    704  * ============================================================ */
    705 
    706 int driver_path_exists(const char* path) {
    707   WIN32_FILE_ATTRIBUTE_DATA fad;
    708   wchar_t* wpath;
    709   BOOL ok;
    710   if (!path) return 0;
    711   wpath = widen(path);
    712   if (!wpath) return 0;
    713   ok = GetFileAttributesExW(wpath, GetFileExInfoStandard, &fad);
    714   free(wpath);
    715   return ok ? 1 : 0;
    716 }
    717 
    718 /* Convert a FILETIME (100-ns ticks since 1601-01-01 UTC) to ns since the
    719  * Unix epoch (1970-01-01 UTC). 11644473600 sec is the gap. */
    720 static int64_t filetime_to_unix_ns(FILETIME ft) {
    721   uint64_t t = ((uint64_t)ft.dwHighDateTime << 32) | (uint64_t)ft.dwLowDateTime;
    722   /* Subtract Win-to-Unix epoch offset in 100-ns ticks. */
    723   static const uint64_t EPOCH_DIFF_100NS = 116444736000000000ull;
    724   if (t < EPOCH_DIFF_100NS) t = EPOCH_DIFF_100NS;
    725   return (int64_t)((t - EPOCH_DIFF_100NS) * 100ull);
    726 }
    727 
    728 int driver_path_mtime_ns(const char* path, int64_t* out) {
    729   WIN32_FILE_ATTRIBUTE_DATA fad;
    730   wchar_t* wpath;
    731   BOOL ok;
    732   if (!path || !out) return 1;
    733   wpath = widen(path);
    734   if (!wpath) return 1;
    735   ok = GetFileAttributesExW(wpath, GetFileExInfoStandard, &fad);
    736   free(wpath);
    737   if (!ok) return 1;
    738   *out = filetime_to_unix_ns(fad.ftLastWriteTime);
    739   return 0;
    740 }
    741 
    742 int driver_path_stat(const char* path, uint64_t* out_size,
    743                      uint64_t* out_mtime_ns, uint8_t* out_filetype) {
    744   WIN32_FILE_ATTRIBUTE_DATA fad;
    745   wchar_t* wpath;
    746   BOOL ok;
    747   DWORD err;
    748   if (!path) return 2;
    749   wpath = widen(path);
    750   if (!wpath) return 2;
    751   ok = GetFileAttributesExW(wpath, GetFileExInfoStandard, &fad);
    752   err = ok ? 0 : GetLastError();
    753   free(wpath);
    754   if (!ok) {
    755     return (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) ? 1 : 2;
    756   }
    757   *out_mtime_ns = (uint64_t)filetime_to_unix_ns(fad.ftLastWriteTime);
    758   if (fad.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
    759     *out_size = 0u;
    760     *out_filetype = 3; /* DIRECTORY */
    761   } else if (fad.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
    762     *out_size = ((uint64_t)fad.nFileSizeHigh << 32) | fad.nFileSizeLow;
    763     *out_filetype = 7; /* SYMBOLIC_LINK */
    764   } else {
    765     *out_size = ((uint64_t)fad.nFileSizeHigh << 32) | fad.nFileSizeLow;
    766     *out_filetype = 4; /* REGULAR_FILE */
    767   }
    768   return 0;
    769 }
    770 
    771 int driver_path_lstat(const char* path, uint64_t* out_size,
    772                       uint8_t* out_filetype, int* out_executable) {
    773   uint64_t mtime = 0;
    774   /* GetFileAttributesExW reports a reparse point without following it, so
    775    * driver_path_stat is already effectively no-follow on Windows; there is no
    776    * POSIX-style execute bit. */
    777   if (out_executable) *out_executable = 0;
    778   return driver_path_stat(path, out_size, &mtime, out_filetype);
    779 }
    780 
    781 typedef struct DriverDirEntryRec {
    782   char* name;
    783   size_t name_alloc;
    784   uint32_t name_len;
    785   uint64_t ino;
    786   uint64_t size;
    787   uint64_t mtime_ns;
    788   uint8_t filetype;
    789 } DriverDirEntryRec;
    790 
    791 struct DriverDirHandle {
    792   DriverEnv* env;
    793   DriverDirEntryRec* entries;
    794   size_t entries_alloc;
    795   uint64_t count;
    796 };
    797 
    798 DriverDirHandle* driver_open_dir(DriverEnv* env, const char* path) {
    799   char* pattern;
    800   wchar_t* wpattern;
    801   WIN32_FIND_DATAW fd;
    802   HANDLE h;
    803   DWORD last;
    804   DriverDirHandle* dh;
    805   uint64_t cap = 0;
    806   uint64_t count = 0;
    807 
    808   if (!env || !path) return NULL;
    809   pattern = driver_path_join(env, path, "*", NULL);
    810   if (!pattern) return NULL;
    811   wpattern = widen(pattern);
    812   driver_free(env, pattern, kit_slice_cstr(pattern).len + 1u);
    813   if (!wpattern) return NULL;
    814 
    815   h = FindFirstFileW(wpattern, &fd);
    816   free(wpattern);
    817   if (h == INVALID_HANDLE_VALUE) return NULL;
    818 
    819   dh = (DriverDirHandle*)env->heap->alloc(env->heap, sizeof(*dh),
    820                                           _Alignof(DriverDirHandle));
    821   if (!dh) {
    822     FindClose(h);
    823     return NULL;
    824   }
    825   memset(dh, 0, sizeof(*dh));
    826   dh->env = env;
    827 
    828   for (;;) {
    829     char* name;
    830     size_t name_len;
    831     DriverDirEntryRec* e;
    832 
    833     name = narrow(fd.cFileName);
    834     if (!name) goto fail;
    835     if (driver_streq(name, ".") || driver_streq(name, "..")) {
    836       free(name);
    837       goto loop_next;
    838     }
    839 
    840     name_len = kit_slice_cstr(name).len;
    841 
    842     /* grow entry array */
    843     if (count >= cap) {
    844       uint64_t new_cap = cap ? cap * 2u : 8u;
    845       size_t new_alloc = (size_t)new_cap * sizeof(DriverDirEntryRec);
    846       DriverDirEntryRec* nv = (DriverDirEntryRec*)env->heap->alloc(
    847           env->heap, new_alloc, _Alignof(DriverDirEntryRec));
    848       if (!nv) {
    849         free(name);
    850         goto fail;
    851       }
    852       if (dh->entries) {
    853         memcpy(nv, dh->entries, (size_t)count * sizeof(DriverDirEntryRec));
    854         env->heap->free(env->heap, dh->entries, dh->entries_alloc);
    855       }
    856       dh->entries = nv;
    857       dh->entries_alloc = new_alloc;
    858       cap = new_cap;
    859     }
    860 
    861     e = &dh->entries[count];
    862     memset(e, 0, sizeof(*e));
    863     e->name_alloc = name_len + 1u;
    864     e->name = (char*)env->heap->alloc(env->heap, e->name_alloc, 1u);
    865     if (!e->name) {
    866       free(name);
    867       goto fail;
    868     }
    869     memcpy(e->name, name, name_len + 1u);
    870     e->name_len = (uint32_t)name_len;
    871     free(name);
    872 
    873     e->size = ((uint64_t)fd.nFileSizeHigh << 32) | fd.nFileSizeLow;
    874     e->mtime_ns = (uint64_t)filetime_to_unix_ns(fd.ftLastWriteTime);
    875     if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
    876       e->filetype = 3;
    877     else if (fd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
    878       e->filetype = 7;
    879     else
    880       e->filetype = 4;
    881     ++count;
    882 
    883   loop_next:
    884     if (!FindNextFileW(h, &fd)) {
    885       last = GetLastError();
    886       if (last == ERROR_NO_MORE_FILES) break;
    887       goto fail;
    888     }
    889   }
    890 
    891   FindClose(h);
    892   dh->count = count;
    893   return dh;
    894 
    895 fail:
    896   FindClose(h);
    897   driver_close_dir(env, dh);
    898   return NULL;
    899 }
    900 
    901 int driver_read_dir_entry(DriverDirHandle* h, uint64_t index,
    902                           const char** out_name, uint32_t* out_name_len,
    903                           uint64_t* out_ino, uint64_t* out_size,
    904                           uint64_t* out_mtime_ns, uint8_t* out_filetype) {
    905   DriverDirEntryRec* e;
    906   if (!h || index >= h->count) return 1;
    907   e = &h->entries[index];
    908   *out_name = e->name;
    909   *out_name_len = e->name_len;
    910   *out_ino = e->ino;
    911   *out_size = e->size;
    912   *out_mtime_ns = e->mtime_ns;
    913   *out_filetype = e->filetype;
    914   return 0;
    915 }
    916 
    917 void driver_close_dir(DriverEnv* env, DriverDirHandle* h) {
    918   uint64_t i;
    919   if (!h) return;
    920   if (!env) env = h->env;
    921   for (i = 0; i < h->count; ++i) {
    922     DriverDirEntryRec* e = &h->entries[i];
    923     if (e->name) env->heap->free(env->heap, e->name, e->name_alloc);
    924   }
    925   if (h->entries) env->heap->free(env->heap, h->entries, h->entries_alloc);
    926   env->heap->free(env->heap, h, sizeof(*h));
    927 }
    928 
    929 int driver_mkdir_p(DriverEnv* env, const char* path) {
    930   size_t len;
    931   char* buf;
    932   size_t i;
    933   if (!path || !path[0]) return 1;
    934   len = kit_slice_cstr(path).len;
    935   buf = (char*)driver_alloc(env, len + 1);
    936   if (!buf) return 1;
    937   memcpy(buf, path, len + 1);
    938 
    939   /* Walk separators, accepting both '/' and '\\'. Skip the drive prefix
    940    * ("C:") and the leading separator(s) of a UNC path so we don't try to
    941    * CreateDirectory("\\\\server"). */
    942   for (i = 0; i <= len; ++i) {
    943     int at_end = (i == len);
    944     char ch = buf[i];
    945     int is_sep = (ch == '/' || ch == '\\');
    946     int do_create = at_end || is_sep;
    947     if (!do_create) continue;
    948     if (!at_end) buf[i] = '\0';
    949     /* Skip pure roots: "", ".", drive-letter-only ("C:"), and UNC roots
    950      * ("\\\\server" or "\\\\server\\share"). */
    951     if (buf[0] == '\0') {
    952       /* nothing yet */
    953     } else if (driver_streq(buf, ".")) {
    954       /* skip */
    955     } else if (i >= 2 && buf[1] == ':' && buf[2] == '\0') {
    956       /* "C:" — drive prefix only */
    957     } else {
    958       wchar_t* wpath = widen(buf);
    959       if (!wpath) {
    960         driver_free(env, buf, len + 1);
    961         return 1;
    962       }
    963       if (!CreateDirectoryW(wpath, NULL)) {
    964         DWORD err = GetLastError();
    965         if (err != ERROR_ALREADY_EXISTS) {
    966           free(wpath);
    967           driver_free(env, buf, len + 1);
    968           return 1;
    969         }
    970       }
    971       {
    972         WIN32_FILE_ATTRIBUTE_DATA fad;
    973         BOOL ok = GetFileAttributesExW(wpath, GetFileExInfoStandard, &fad);
    974         free(wpath);
    975         if (!ok || !(fad.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
    976           driver_free(env, buf, len + 1);
    977           return 1;
    978         }
    979       }
    980     }
    981     if (!at_end) buf[i] = ch;
    982   }
    983 
    984   driver_free(env, buf, len + 1);
    985   return 0;
    986 }
    987 
    988 int driver_mark_executable_output(const char* path) {
    989   /* Windows has no Unix +x bit; file extension governs executability and
    990    * NTFS ACLs are inherited from the parent directory. No-op success. */
    991   (void)path;
    992   return 0;
    993 }
    994 
    995 int driver_path_mode_get(const char* path, uint32_t* mode_out) {
    996   if (!path || !mode_out || !driver_path_exists(path)) return 1;
    997   *mode_out = 0;
    998   return 0;
    999 }
   1000 
   1001 int driver_path_mode_set(const char* path, uint32_t mode) {
   1002   (void)mode;
   1003   return path && driver_path_exists(path) ? 0 : 1;
   1004 }
   1005 
   1006 /* ---------------- self executable path ---------------- */
   1007 static int wide_dir_exists(const wchar_t* path) {
   1008   DWORD attrs = GetFileAttributesW(path);
   1009   return attrs != INVALID_FILE_ATTRIBUTES &&
   1010          (attrs & FILE_ATTRIBUTE_DIRECTORY) != 0;
   1011 }
   1012 
   1013 /* An installed Windows multicall name is a hard link, so
   1014  * GetModuleFileNameW reports the prefix alias rather than the distribution's
   1015  * bin/kit.exe name.  Enumerate the file's other NTFS names and select the one
   1016  * that owns a Kit support tree.  This is the hard-link analogue of POSIX
   1017  * realpath resolving an installed symlink to its distribution executable. */
   1018 static int wide_exe_has_support(const wchar_t* exe) {
   1019   size_t n, slash, suffix_len;
   1020   wchar_t* probe;
   1021   static const wchar_t dev_suffix[] = L"support\\rt\\include";
   1022   static const wchar_t pkg_suffix[] = L"..\\support\\rt\\include";
   1023   const wchar_t* suffix;
   1024   int packaged;
   1025   if (!exe || !*exe) return 0;
   1026   n = wcslen(exe);
   1027   slash = n;
   1028   while (slash > 0 && exe[slash - 1] != L'\\' && exe[slash - 1] != L'/')
   1029     --slash;
   1030   if (slash == 0) return 0;
   1031   for (packaged = 0; packaged < 2; ++packaged) {
   1032     suffix = packaged ? pkg_suffix : dev_suffix;
   1033     suffix_len = wcslen(suffix);
   1034     probe = (wchar_t*)malloc((slash + suffix_len + 1u) * sizeof(*probe));
   1035     if (!probe) return 0;
   1036     memcpy(probe, exe, slash * sizeof(*probe));
   1037     memcpy(probe + slash, suffix, (suffix_len + 1u) * sizeof(*probe));
   1038     if (wide_dir_exists(probe)) {
   1039       free(probe);
   1040       return 1;
   1041     }
   1042     free(probe);
   1043   }
   1044   return 0;
   1045 }
   1046 
   1047 static wchar_t* wide_find_distribution_hardlink(const wchar_t* invoked) {
   1048   wchar_t volume[MAX_PATH];
   1049   wchar_t* link_name = NULL;
   1050   wchar_t* selected = NULL;
   1051   DWORD cap = 256;
   1052   HANDLE find = INVALID_HANDLE_VALUE;
   1053   if (!invoked || !GetVolumePathNameW(invoked, volume, MAX_PATH)) return NULL;
   1054   for (;;) {
   1055     DWORD size = cap;
   1056     wchar_t* next = (wchar_t*)realloc(link_name, (size_t)cap * sizeof(*next));
   1057     if (!next) goto done;
   1058     link_name = next;
   1059     find = FindFirstFileNameW(invoked, 0, &size, link_name);
   1060     if (find != INVALID_HANDLE_VALUE) break;
   1061     if (GetLastError() != ERROR_MORE_DATA || size <= cap) goto done;
   1062     cap = size;
   1063   }
   1064   for (;;) {
   1065     size_t volume_len = wcslen(volume);
   1066     size_t name_off =
   1067         link_name[0] == L'\\' || link_name[0] == L'/' ? 1u : 0u;
   1068     size_t name_len = wcslen(link_name + name_off);
   1069     int need_sep = volume_len > 0 && volume[volume_len - 1] != L'\\' &&
   1070                    volume[volume_len - 1] != L'/';
   1071     size_t total = volume_len + (size_t)need_sep + name_len + 1u;
   1072     wchar_t* candidate = (wchar_t*)malloc(total * sizeof(*candidate));
   1073     if (!candidate) goto done;
   1074     memcpy(candidate, volume, volume_len * sizeof(*candidate));
   1075     if (need_sep) candidate[volume_len++] = L'\\';
   1076     memcpy(candidate + volume_len, link_name + name_off,
   1077            (name_len + 1u) * sizeof(*candidate));
   1078     if (wide_exe_has_support(candidate)) {
   1079       selected = candidate;
   1080       break;
   1081     }
   1082     free(candidate);
   1083     {
   1084       DWORD size = cap;
   1085       if (FindNextFileNameW(find, &size, link_name)) continue;
   1086       if (GetLastError() != ERROR_MORE_DATA || size <= cap) break;
   1087       {
   1088         wchar_t* next =
   1089             (wchar_t*)realloc(link_name, (size_t)size * sizeof(*next));
   1090         if (!next) goto done;
   1091         link_name = next;
   1092         cap = size;
   1093       }
   1094       size = cap;
   1095       if (!FindNextFileNameW(find, &size, link_name)) break;
   1096     }
   1097   }
   1098 done:
   1099   if (find != INVALID_HANDLE_VALUE) FindClose(find);
   1100   free(link_name);
   1101   return selected;
   1102 }
   1103 
   1104 /* GetModuleFileNameW(NULL) reports the path of the running image. A return
   1105  * equal to the buffer size means truncation (older Windows doesn't fail), so
   1106  * grow until the result fits, then narrow to UTF-8. */
   1107 int driver_self_exe_path(DriverEnv* env, char** out, size_t* out_size) {
   1108   DWORD cap = 256;
   1109   wchar_t* wbuf = NULL;
   1110   char* narrowed;
   1111   size_t size;
   1112   if (!env || !out || !out_size) return 1;
   1113   for (;;) {
   1114     wchar_t* nb = (wchar_t*)realloc(wbuf, (size_t)cap * sizeof(wchar_t));
   1115     DWORD n;
   1116     if (!nb) {
   1117       free(wbuf);
   1118       return 1;
   1119     }
   1120     wbuf = nb;
   1121     n = GetModuleFileNameW(NULL, wbuf, cap);
   1122     if (n == 0) {
   1123       free(wbuf);
   1124       return 1;
   1125     }
   1126     if (n < cap) break; /* fit: n excludes the terminator on success */
   1127     if (cap >= (1u << 20)) {
   1128       free(wbuf);
   1129       return 1;
   1130     }
   1131     cap *= 2;
   1132   }
   1133   {
   1134     wchar_t* dist = wide_find_distribution_hardlink(wbuf);
   1135     if (dist) {
   1136       free(wbuf);
   1137       wbuf = dist;
   1138     }
   1139   }
   1140   narrowed = narrow(wbuf); /* malloc'd UTF-8 */
   1141   free(wbuf);
   1142   if (!narrowed) return 1;
   1143   size = driver_strlen(narrowed) + 1u;
   1144   *out = (char*)driver_alloc(env, size);
   1145   if (!*out) {
   1146     free(narrowed);
   1147     return 1;
   1148   }
   1149   driver_memcpy(*out, narrowed, size);
   1150   *out_size = size;
   1151   free(narrowed);
   1152   return 0;
   1153 }
   1154 
   1155 /* ---------------- link helpers (install) ---------------- */
   1156 
   1157 #ifndef SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
   1158 #define SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE 0x2
   1159 #endif
   1160 
   1161 int driver_create_symlink(const char* target, const char* link_path) {
   1162   wchar_t* wtarget;
   1163   wchar_t* wlink;
   1164   BOOLEAN ok;
   1165   if (!target || !link_path) return 1;
   1166   wtarget = widen(target);
   1167   wlink = widen(link_path);
   1168   if (!wtarget || !wlink) {
   1169     free(wtarget);
   1170     free(wlink);
   1171     return 1;
   1172   }
   1173   /* Prefer the unprivileged (Developer Mode) flag; fall back without it for
   1174    * older hosts that reject the unknown flag. */
   1175   ok = CreateSymbolicLinkW(wlink, wtarget,
   1176                            SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE);
   1177   if (!ok) ok = CreateSymbolicLinkW(wlink, wtarget, 0);
   1178   free(wtarget);
   1179   free(wlink);
   1180   return ok ? 0 : 1;
   1181 }
   1182 
   1183 int driver_readlink(const char* path, char* buf, size_t cap) {
   1184   /* Reading a reparse point is materially more involved than POSIX
   1185    * readlink(2), and initramfs symlinks are a Linux artifact. The Windows
   1186    * build is best-effort and reports failure; the caller emits a clear
   1187    * diagnostic and skips the entry rather than archiving a bogus link. */
   1188   (void)path;
   1189   (void)buf;
   1190   (void)cap;
   1191   return 1;
   1192 }
   1193 
   1194 int driver_create_hardlink(const char* target, const char* link_path) {
   1195   wchar_t* wtarget;
   1196   wchar_t* wlink;
   1197   BOOL ok;
   1198   if (!target || !link_path) return 1;
   1199   wtarget = widen(target);
   1200   wlink = widen(link_path);
   1201   if (!wtarget || !wlink) {
   1202     free(wtarget);
   1203     free(wlink);
   1204     return 1;
   1205   }
   1206   ok = CreateHardLinkW(wlink, wtarget, NULL);
   1207   free(wtarget);
   1208   free(wlink);
   1209   return ok ? 0 : 1;
   1210 }
   1211 
   1212 int driver_remove_file(const char* path) {
   1213   wchar_t* wpath;
   1214   BOOL ok;
   1215   if (!path) return 1;
   1216   wpath = widen(path);
   1217   if (!wpath) return 1;
   1218   ok = DeleteFileW(wpath);
   1219   if (!ok) {
   1220     DWORD err = GetLastError();
   1221     if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) ok = TRUE;
   1222   }
   1223   free(wpath);
   1224   return ok ? 0 : 1;
   1225 }
   1226 
   1227 int driver_path_lexists(const char* path) {
   1228   wchar_t* wpath;
   1229   DWORD attr;
   1230   if (!path) return 0;
   1231   wpath = widen(path);
   1232   if (!wpath) return 0;
   1233   /* GetFileAttributesW does not traverse reparse points, so a dangling
   1234    * symlink reports its own attributes rather than failing. */
   1235   attr = GetFileAttributesW(wpath);
   1236   free(wpath);
   1237   return attr != INVALID_FILE_ATTRIBUTES;
   1238 }
   1239 
   1240 int driver_rename(const char* from, const char* to) {
   1241   wchar_t *wfrom, *wto;
   1242   BOOL ok;
   1243   if (!from || !to) return 1;
   1244   wfrom = widen(from);
   1245   wto = widen(to);
   1246   if (!wfrom || !wto) {
   1247     free(wfrom);
   1248     free(wto);
   1249     return 1;
   1250   }
   1251   ok = MoveFileExW(wfrom, wto, MOVEFILE_REPLACE_EXISTING);
   1252   free(wfrom);
   1253   free(wto);
   1254   return ok ? 0 : 1;
   1255 }
   1256 
   1257 static int win_remove_tree(const char* path) {
   1258   wchar_t* wpath = widen(path);
   1259   DWORD attr;
   1260   int rc = 0;
   1261   if (!wpath) return 1;
   1262   attr = GetFileAttributesW(wpath);
   1263   if (attr == INVALID_FILE_ATTRIBUTES) {
   1264     DWORD e = GetLastError();
   1265     free(wpath);
   1266     return (e == ERROR_FILE_NOT_FOUND || e == ERROR_PATH_NOT_FOUND) ? 0 : 1;
   1267   }
   1268   if ((attr & FILE_ATTRIBUTE_DIRECTORY) &&
   1269       !(attr & FILE_ATTRIBUTE_REPARSE_POINT)) {
   1270     char pattern[4096];
   1271     WIN32_FIND_DATAW fd;
   1272     HANDLE h;
   1273     wchar_t* wpattern;
   1274     snprintf(pattern, sizeof pattern, "%s\\*", path);
   1275     wpattern = widen(pattern);
   1276     if (!wpattern) {
   1277       free(wpath);
   1278       return 1;
   1279     }
   1280     h = FindFirstFileW(wpattern, &fd);
   1281     free(wpattern);
   1282     if (h != INVALID_HANDLE_VALUE) {
   1283       do {
   1284         char child[4096], nameu8[1024];
   1285         if (WideCharToMultiByte(CP_UTF8, 0, fd.cFileName, -1, nameu8,
   1286                                 (int)sizeof nameu8, NULL, NULL) <= 0) {
   1287           rc = 1;
   1288           continue;
   1289         }
   1290         if (nameu8[0] == '.' && (nameu8[1] == '\0' ||
   1291                                  (nameu8[1] == '.' && nameu8[2] == '\0')))
   1292           continue;
   1293         if ((size_t)snprintf(child, sizeof child, "%s\\%s", path, nameu8) >=
   1294             sizeof child) {
   1295           rc = 1;
   1296           continue;
   1297         }
   1298         if (win_remove_tree(child) != 0) rc = 1;
   1299       } while (FindNextFileW(h, &fd));
   1300       FindClose(h);
   1301     }
   1302     if (!RemoveDirectoryW(wpath)) rc = 1;
   1303   } else if (attr & FILE_ATTRIBUTE_DIRECTORY) {
   1304     if (!RemoveDirectoryW(wpath)) rc = 1; /* reparse point: remove link only */
   1305   } else {
   1306     if (!DeleteFileW(wpath)) rc = 1;
   1307   }
   1308   free(wpath);
   1309   return rc;
   1310 }
   1311 
   1312 int driver_remove_tree(const char* path) {
   1313   if (!path) return 1;
   1314   return win_remove_tree(path);
   1315 }
   1316 
   1317 int driver_kit_home(char* buf, size_t cap) {
   1318   const char* v;
   1319   int n = -1;
   1320   size_t len;
   1321   if (!buf || cap == 0) return 1;
   1322   if ((v = getenv("KIT_HOME")) && *v)
   1323     n = snprintf(buf, cap, "%s", v);
   1324   else if ((v = getenv("XDG_DATA_HOME")) && *v)
   1325     n = snprintf(buf, cap, "%s/kit", v);
   1326   else if ((v = getenv("LOCALAPPDATA")) && *v)
   1327     n = snprintf(buf, cap, "%s\\kit", v);
   1328   if (n < 0 || (size_t)n >= cap) return 1;
   1329   len = strlen(buf);
   1330   while (len > 1 && (buf[len - 1] == '/' || buf[len - 1] == '\\'))
   1331     buf[--len] = '\0';
   1332   return 0;
   1333 }
   1334 
   1335 static int fetch_hex_val(char c, unsigned* out) {
   1336   if (c >= '0' && c <= '9') {
   1337     *out = (unsigned)(c - '0');
   1338     return 0;
   1339   }
   1340   if (c >= 'a' && c <= 'f') {
   1341     *out = (unsigned)(c - 'a') + 10u;
   1342     return 0;
   1343   }
   1344   if (c >= 'A' && c <= 'F') {
   1345     *out = (unsigned)(c - 'A') + 10u;
   1346     return 0;
   1347   }
   1348   return 1;
   1349 }
   1350 
   1351 static char* fetch_decode_url_path(const char* s) {
   1352   char* out;
   1353   char* w;
   1354   size_t n;
   1355   if (!s) return NULL;
   1356   n = strlen(s);
   1357   out = (char*)malloc(n + 1u);
   1358   if (!out) return NULL;
   1359   w = out;
   1360   while (*s) {
   1361     if (*s == '%') {
   1362       unsigned hi, lo;
   1363       if (!s[1] || !s[2] || fetch_hex_val(s[1], &hi) ||
   1364           fetch_hex_val(s[2], &lo)) {
   1365         free(out);
   1366         return NULL;
   1367       }
   1368       if (((hi << 4) | lo) == 0u) {
   1369         free(out);
   1370         return NULL;
   1371       }
   1372       *w++ = (char)((hi << 4) | lo);
   1373       s += 3;
   1374     } else {
   1375       *w++ = *s++;
   1376     }
   1377   }
   1378   *w = '\0';
   1379   return out;
   1380 }
   1381 
   1382 static char* fetch_file_url_path(const char* url) {
   1383   const char* p;
   1384   char* decoded;
   1385   if (!url || strncmp(url, "file://", 7) != 0) return NULL;
   1386   p = url + 7;
   1387   if (strncmp(p, "localhost/", 10) == 0) {
   1388     p += 10;
   1389   } else if (p[0] == '/') {
   1390     ++p;
   1391   } else {
   1392     const char* slash = strchr(p, '/');
   1393     char* unc;
   1394     size_t host_len, rest_len;
   1395     if (!slash || slash == p) return NULL;
   1396     host_len = (size_t)(slash - p);
   1397     rest_len = strlen(slash + 1u);
   1398     unc = (char*)malloc(2u + host_len + 1u + rest_len + 1u);
   1399     if (!unc) return NULL;
   1400     unc[0] = '\\';
   1401     unc[1] = '\\';
   1402     memcpy(unc + 2u, p, host_len);
   1403     unc[2u + host_len] = '\\';
   1404     memcpy(unc + 2u + host_len + 1u, slash + 1u, rest_len + 1u);
   1405     decoded = fetch_decode_url_path(unc);
   1406     free(unc);
   1407     return decoded;
   1408   }
   1409   decoded = fetch_decode_url_path(p);
   1410   return decoded;
   1411 }
   1412 
   1413 static int fetch_copy_file(const char* src, const char* dest) {
   1414   uint8_t buf[32768];
   1415   wchar_t *wsrc, *wdest;
   1416   HANDLE in_h, out_h;
   1417   int rc = 1;
   1418   wsrc = widen(src);
   1419   wdest = widen(dest);
   1420   if (!wsrc || !wdest) {
   1421     free(wsrc);
   1422     free(wdest);
   1423     return 1;
   1424   }
   1425   in_h = CreateFileW(wsrc, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
   1426                      FILE_ATTRIBUTE_NORMAL, NULL);
   1427   free(wsrc);
   1428   if (in_h == INVALID_HANDLE_VALUE) {
   1429     free(wdest);
   1430     return 1;
   1431   }
   1432   out_h = CreateFileW(wdest, GENERIC_WRITE, FILE_SHARE_READ, NULL,
   1433                       CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
   1434   free(wdest);
   1435   if (out_h == INVALID_HANDLE_VALUE) {
   1436     CloseHandle(in_h);
   1437     return 1;
   1438   }
   1439   for (;;) {
   1440     DWORD got = 0;
   1441     if (!ReadFile(in_h, buf, sizeof buf, &got, NULL)) break;
   1442     if (got == 0u) {
   1443       rc = 0;
   1444       break;
   1445     }
   1446     {
   1447       DWORD off = 0;
   1448       while (off < got) {
   1449         DWORD wrote = 0;
   1450         if (!WriteFile(out_h, buf + off, got - off, &wrote, NULL) ||
   1451             wrote == 0u)
   1452           goto out;
   1453         off += wrote;
   1454       }
   1455     }
   1456   }
   1457 out:
   1458   if (!CloseHandle(out_h)) rc = 1;
   1459   CloseHandle(in_h);
   1460   return rc;
   1461 }
   1462 
   1463 int driver_fetch_url(const char* url, const char* dest) {
   1464   wchar_t *wurl, *wdest;
   1465   intptr_t rc;
   1466   char* file_path;
   1467   if (!url || !dest) return 1;
   1468   if (strncmp(url, "file://", 7) == 0) {
   1469     int copy_rc;
   1470     file_path = fetch_file_url_path(url);
   1471     if (!file_path) return 1;
   1472     copy_rc = fetch_copy_file(file_path, dest);
   1473     free(file_path);
   1474     return copy_rc;
   1475   }
   1476   wurl = widen(url);
   1477   wdest = widen(dest);
   1478   if (!wurl || !wdest) {
   1479     free(wurl);
   1480     free(wdest);
   1481     return 1;
   1482   }
   1483   /* curl.exe ships with Windows 10+; the URL is a distinct argv element (no
   1484    * shell), so a hostile mirror URL cannot inject a command. */
   1485   rc = _wspawnlp(_P_WAIT, L"curl", L"curl", L"-fsSL", L"-o", wdest, L"--", wurl,
   1486                  (wchar_t*)NULL);
   1487   free(wurl);
   1488   free(wdest);
   1489   return rc == 0 ? 0 : 1;
   1490 }
   1491 
   1492 static int driver_walk_regular_files_at(DriverEnv* env, const char* dir,
   1493                                         const char* rel, DriverWalkFileFn cb,
   1494                                         void* user) {
   1495   char* pattern;
   1496   wchar_t* wpattern;
   1497   WIN32_FIND_DATAW fd;
   1498   HANDLE h;
   1499   DWORD last;
   1500   int rc = 1;
   1501 
   1502   pattern = driver_path_join(env, dir, "*", NULL);
   1503   if (!pattern) return 1;
   1504   wpattern = widen(pattern);
   1505   driver_free(env, pattern, kit_slice_cstr(pattern).len + 1u);
   1506   if (!wpattern) return 1;
   1507 
   1508   h = FindFirstFileW(wpattern, &fd);
   1509   free(wpattern);
   1510   if (h == INVALID_HANDLE_VALUE) {
   1511     last = GetLastError();
   1512     return last == ERROR_FILE_NOT_FOUND ? 0 : 1;
   1513   }
   1514 
   1515   for (;;) {
   1516     char* name = narrow(fd.cFileName);
   1517     char* child = NULL;
   1518     char* child_rel = NULL;
   1519     int child_rc = 0;
   1520     if (!name) goto loop_fail;
   1521     if (driver_streq(name, ".") || driver_streq(name, "..")) {
   1522       free(name);
   1523       goto loop_next;
   1524     }
   1525     child = driver_path_join(env, dir, name, NULL);
   1526     child_rel = rel && rel[0] ? driver_path_join(env, rel, name, NULL)
   1527                               : driver_path_join(env, "", name, NULL);
   1528     if (!child || !child_rel) goto loop_fail;
   1529     if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
   1530       if ((fd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
   1531         child_rc = 1;
   1532       } else {
   1533         child_rc =
   1534             driver_walk_regular_files_at(env, child, child_rel, cb, user);
   1535       }
   1536     } else {
   1537       child_rc = cb(user, child, child_rel, 0);
   1538     }
   1539     if (child_rel)
   1540       driver_free(env, child_rel, kit_slice_cstr(child_rel).len + 1u);
   1541     if (child) driver_free(env, child, kit_slice_cstr(child).len + 1u);
   1542     free(name);
   1543     if (child_rc) goto out;
   1544 
   1545   loop_next:
   1546     if (!FindNextFileW(h, &fd)) break;
   1547     continue;
   1548 
   1549   loop_fail:
   1550     if (child_rel)
   1551       driver_free(env, child_rel, kit_slice_cstr(child_rel).len + 1u);
   1552     if (child) driver_free(env, child, kit_slice_cstr(child).len + 1u);
   1553     if (name) free(name);
   1554     goto out;
   1555   }
   1556 
   1557   last = GetLastError();
   1558   rc = last == ERROR_NO_MORE_FILES ? 0 : 1;
   1559 
   1560 out:
   1561   FindClose(h);
   1562   return rc;
   1563 }
   1564 
   1565 int driver_walk_regular_files(DriverEnv* env, const char* root,
   1566                               DriverWalkFileFn cb, void* user) {
   1567   if (!env || !root || !root[0] || !cb) return 1;
   1568   return driver_walk_regular_files_at(env, root, "", cb, user);
   1569 }
   1570 
   1571 /* ============================================================
   1572  *   Time
   1573  * ============================================================ */
   1574 
   1575 uint64_t driver_now_ns(void) {
   1576   /* QueryPerformanceCounter is monotonic across cores on every supported
   1577    * Windows version since Vista. */
   1578   LARGE_INTEGER freq;
   1579   LARGE_INTEGER ctr;
   1580   if (!QueryPerformanceFrequency(&freq) || freq.QuadPart <= 0) return 0;
   1581   if (!QueryPerformanceCounter(&ctr)) return 0;
   1582   /* Avoid 128-bit multiply: split ticks into integer-seconds and remainder. */
   1583   {
   1584     int64_t sec = ctr.QuadPart / freq.QuadPart;
   1585     int64_t rem = ctr.QuadPart % freq.QuadPart;
   1586     return (uint64_t)sec * 1000000000ull +
   1587            (uint64_t)((rem * 1000000000) / freq.QuadPart);
   1588   }
   1589 }
   1590 
   1591 int driver_random_bytes(uint8_t* out, size_t n) {
   1592   /* rand_s draws from the OS CSPRNG (RtlGenRandom) without linking bcrypt. */
   1593   size_t off = 0;
   1594   if (!out) return 1;
   1595   while (off < n) {
   1596     unsigned int v;
   1597     size_t chunk;
   1598     if (rand_s(&v) != 0) return 1;
   1599     chunk = n - off < sizeof v ? n - off : sizeof v;
   1600     memcpy(out + off, &v, chunk);
   1601     off += chunk;
   1602   }
   1603   return 0;
   1604 }
   1605 
   1606 /* ============================================================
   1607  *   load helpers
   1608  * ============================================================ */
   1609 
   1610 /* driver_load_bytes / driver_release_bytes are OS-neutral; see env/common.c. */
   1611 
   1612 /* ============================================================
   1613  *   stdin / edit_temp / read_line
   1614  * ============================================================ */
   1615 
   1616 int driver_read_stdin(DriverEnv* e, uint8_t** out_data, size_t* out_size) {
   1617   size_t cap = 4096;
   1618   size_t len = 0;
   1619   uint8_t* buf = e->heap->alloc(e->heap, cap, 1);
   1620   HANDLE h = GetStdHandle(STD_INPUT_HANDLE);
   1621   if (!buf) return 0;
   1622   for (;;) {
   1623     DWORD n;
   1624     if (len == cap) {
   1625       size_t newcap = cap * 2;
   1626       uint8_t* nb = e->heap->realloc(e->heap, buf, cap, newcap, 1);
   1627       if (!nb) {
   1628         e->heap->free(e->heap, buf, cap);
   1629         return 0;
   1630       }
   1631       buf = nb;
   1632       cap = newcap;
   1633     }
   1634     if (!ReadFile(h, buf + len, (DWORD)(cap - len), &n, NULL)) {
   1635       if (GetLastError() == ERROR_BROKEN_PIPE) break; /* EOF on pipe */
   1636       e->heap->free(e->heap, buf, cap);
   1637       return 0;
   1638     }
   1639     if (n == 0) break;
   1640     len += n;
   1641   }
   1642   if (len < cap) {
   1643     uint8_t* shrunk = len ? e->heap->realloc(e->heap, buf, cap, len, 1) : NULL;
   1644     if (len && !shrunk) {
   1645       *out_data = buf;
   1646       *out_size = cap;
   1647       return 1;
   1648     }
   1649     if (!len) {
   1650       e->heap->free(e->heap, buf, cap);
   1651       buf = NULL;
   1652     } else {
   1653       buf = shrunk;
   1654     }
   1655   }
   1656   *out_data = buf;
   1657   *out_size = len;
   1658   return 1;
   1659 }
   1660 
   1661 static int driver_write_handle_all(HANDLE h, const uint8_t* data, size_t n) {
   1662   size_t off = 0;
   1663   while (off < n) {
   1664     DWORD chunk = (n - off) > 0x40000000u ? 0x40000000u : (DWORD)(n - off);
   1665     DWORD wr = 0;
   1666     if (!WriteFile(h, data + off, chunk, &wr, NULL) || wr == 0) return 0;
   1667     off += wr;
   1668   }
   1669   return 1;
   1670 }
   1671 
   1672 int driver_edit_temp(DriverEnv* e, const char* suffix, const uint8_t* initial,
   1673                      size_t initial_size, uint8_t** out_data,
   1674                      size_t* out_size) {
   1675   /* Windows temp-file dance:
   1676    *   - GetTempPathW gives us %TMP%/%TEMP%/%USERPROFILE% with trailing '\\'.
   1677    *   - GetTempFileNameW makes a unique "<dir>\\cfXXXX.tmp" path. We ignore
   1678    *     the ".tmp" and append our requested suffix below by renaming.
   1679    *   - We rename to "<base><suffix>" so the editor sees the right extension.
   1680    *   - Editor is launched via system() so shell quoting / PATH lookup is
   1681    *     handled by cmd.exe.
   1682    */
   1683   wchar_t tmp_dir[MAX_PATH + 1];
   1684   wchar_t tmp_path[MAX_PATH + 1];
   1685   DWORD got;
   1686   HANDLE h = INVALID_HANDLE_VALUE;
   1687   wchar_t* wsuffix = NULL;
   1688   wchar_t final_path[MAX_PATH + 64];
   1689   size_t final_len;
   1690   int ok = 0;
   1691   KitFileData fd_data;
   1692   const char* editor;
   1693   char* cmd = NULL;
   1694   int rc;
   1695   size_t cmd_cap;
   1696   char utf8_path[MAX_PATH * 4 + 1];
   1697   int utf8_len;
   1698 
   1699   if (!out_data || !out_size) return 0;
   1700   *out_data = NULL;
   1701   *out_size = 0;
   1702 
   1703   got = GetTempPathW(MAX_PATH + 1, tmp_dir);
   1704   if (got == 0 || got > MAX_PATH) return 0;
   1705   if (GetTempFileNameW(tmp_dir, L"cf", 0, tmp_path) == 0) return 0;
   1706 
   1707   /* Build the final path = tmp_path with ".tmp" stripped + requested suffix.
   1708    * GetTempFileNameW already created the file at tmp_path; we MoveFileEx to
   1709    * rename it to final_path. */
   1710   {
   1711     size_t tplen = wcslen(tmp_path);
   1712     /* Strip the ".tmp" extension GetTempFileNameW appends. */
   1713     if (tplen >= 4 && tmp_path[tplen - 4] == L'.') tplen -= 4;
   1714     if (tplen >= sizeof(final_path) / sizeof(wchar_t) - 16) {
   1715       DeleteFileW(tmp_path);
   1716       return 0;
   1717     }
   1718     memcpy(final_path, tmp_path, tplen * sizeof(wchar_t));
   1719     final_path[tplen] = L'\0';
   1720     final_len = tplen;
   1721     if (suffix && *suffix) {
   1722       wsuffix = widen(suffix);
   1723       if (!wsuffix) {
   1724         DeleteFileW(tmp_path);
   1725         return 0;
   1726       }
   1727       {
   1728         size_t sl = wcslen(wsuffix);
   1729         if (final_len + sl + 1 >= sizeof(final_path) / sizeof(wchar_t)) {
   1730           free(wsuffix);
   1731           DeleteFileW(tmp_path);
   1732           return 0;
   1733         }
   1734         memcpy(final_path + final_len, wsuffix, sl * sizeof(wchar_t));
   1735         final_len += sl;
   1736         final_path[final_len] = L'\0';
   1737       }
   1738       free(wsuffix);
   1739     }
   1740     if (!MoveFileExW(tmp_path, final_path, MOVEFILE_REPLACE_EXISTING)) {
   1741       DeleteFileW(tmp_path);
   1742       return 0;
   1743     }
   1744   }
   1745 
   1746   /* Open the renamed file and write initial contents. */
   1747   h = CreateFileW(final_path, GENERIC_WRITE, FILE_SHARE_READ, NULL,
   1748                   TRUNCATE_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
   1749   if (h == INVALID_HANDLE_VALUE) goto out;
   1750   if (initial_size &&
   1751       !driver_write_handle_all(h, initial ? initial : (const uint8_t*)"",
   1752                                initial_size))
   1753     goto out;
   1754   CloseHandle(h);
   1755   h = INVALID_HANDLE_VALUE;
   1756 
   1757   /* Convert final_path back to UTF-8 for the editor command. */
   1758   utf8_len = WideCharToMultiByte(CP_UTF8, 0, final_path, -1, utf8_path,
   1759                                  (int)sizeof(utf8_path), NULL, NULL);
   1760   if (utf8_len <= 0) goto out;
   1761 
   1762   editor = getenv("VISUAL");
   1763   if (!editor || !*editor) editor = getenv("EDITOR");
   1764   if (!editor || !*editor) editor = "notepad";
   1765   cmd_cap = strlen(editor) + (size_t)utf8_len + 16;
   1766   cmd = (char*)malloc(cmd_cap);
   1767   if (!cmd) goto out;
   1768   /* Wrap the entire command in extra quotes so cmd.exe /S doesn't strip
   1769    * matching outer quotes when the editor path itself has spaces. */
   1770   rc = snprintf(cmd, cmd_cap, "\"\"%s\" \"%s\"\"", editor, utf8_path);
   1771   if (rc < 0 || (size_t)rc >= cmd_cap) goto out;
   1772   if (system(cmd) != 0) goto out;
   1773 
   1774   fd_data.data = NULL;
   1775   fd_data.size = 0;
   1776   fd_data.token = NULL;
   1777   if (win_read_all(e, utf8_path, &fd_data) != KIT_OK) goto out;
   1778   *out_data = (uint8_t*)fd_data.data;
   1779   *out_size = fd_data.size;
   1780   ok = 1;
   1781 
   1782 out:
   1783   if (h != INVALID_HANDLE_VALUE) CloseHandle(h);
   1784   if (cmd) free(cmd);
   1785   DeleteFileW(final_path);
   1786   return ok;
   1787 }
   1788 
   1789 int driver_read_line(char* buf, size_t cap) {
   1790   size_t len = 0;
   1791   if (!buf || cap < 2) return -1;
   1792   for (;;) {
   1793     int c = fgetc(stdin);
   1794     if (c == EOF) {
   1795       buf[len] = '\0';
   1796       if (ferror(stdin)) return -1;
   1797       if (len == 0) return 0;
   1798       return (int)len;
   1799     }
   1800     if (c == '\n') {
   1801       /* Strip a trailing CR if present (CRLF line ending). */
   1802       if (len > 0 && buf[len - 1] == '\r') --len;
   1803       buf[len] = '\0';
   1804       return (int)len;
   1805     }
   1806     if (len + 1 < cap) buf[len++] = (char)c;
   1807   }
   1808 }
   1809 
   1810 static int win_line_history_add(DriverEnv* env, DriverLineHistory* h,
   1811                                 const char* line, size_t len) {
   1812   char** ni;
   1813   size_t* ns;
   1814   char* copy;
   1815   uint32_t nc;
   1816   if (!env || !h || !line || len == 0) return 0;
   1817   if (h->count == h->cap) {
   1818     size_t old_items = (size_t)h->cap * sizeof(*h->items);
   1819     size_t new_items;
   1820     size_t old_sizes = (size_t)h->cap * sizeof(*h->sizes);
   1821     size_t new_sizes;
   1822     nc = h->cap ? h->cap * 2u : 32u;
   1823     new_items = (size_t)nc * sizeof(*h->items);
   1824     new_sizes = (size_t)nc * sizeof(*h->sizes);
   1825     ni = (char**)env->heap->realloc(env->heap, h->items, old_items, new_items,
   1826                                     _Alignof(char*));
   1827     if (!ni) return 1;
   1828     h->items = ni;
   1829     ns = (size_t*)env->heap->realloc(env->heap, h->sizes, old_sizes, new_sizes,
   1830                                      _Alignof(size_t));
   1831     if (!ns) return 1;
   1832     h->sizes = ns;
   1833     h->cap = nc;
   1834   }
   1835   copy = (char*)driver_alloc(env, len + 1u);
   1836   if (!copy) return 1;
   1837   memcpy(copy, line, len);
   1838   copy[len] = '\0';
   1839   h->items[h->count] = copy;
   1840   h->sizes[h->count] = len + 1u;
   1841   h->count++;
   1842   return 0;
   1843 }
   1844 
   1845 int driver_read_line_edit(DriverEnv* env, const char* prompt, char* buf,
   1846                           size_t cap, DriverLineHistory* hist,
   1847                           DriverLineCompleteFn complete, void* complete_user,
   1848                           const char* edit_suffix) {
   1849   int n;
   1850   (void)complete;
   1851   (void)complete_user;
   1852   /* This host reads in cooked mode, so there is no keystroke interception
   1853    * for a Ctrl-G inline-edit binding; the `edit` REPL command remains the
   1854    * editor entry point here. */
   1855   (void)edit_suffix;
   1856   if (prompt && *prompt) {
   1857     fputs(prompt, stdout);
   1858     fflush(stdout);
   1859   }
   1860   n = driver_read_line(buf, cap);
   1861   if (n > 0 && hist) (void)win_line_history_add(env, hist, buf, (size_t)n);
   1862   return n;
   1863 }
   1864 
   1865 /* ============================================================
   1866  *   dlsym via GetProcAddress over loaded modules
   1867  * ============================================================ */
   1868 
   1869 /* Snapshot the loaded modules at first call and remember them. We don't
   1870  * track DLL load/unload events; that's a reasonable trade-off for a JIT
   1871  * (the relevant DLLs -- ucrt, kernel32, the kit binary -- are loaded
   1872  * before the JIT extern resolver runs). */
   1873 static HMODULE g_dlsym_modules[256];
   1874 static DWORD g_dlsym_count;
   1875 static SRWLOCK g_dlsym_lock = SRWLOCK_INIT;
   1876 static int g_dlsym_inited;
   1877 
   1878 static void dlsym_init_once(void) {
   1879   HANDLE proc;
   1880   DWORD need = 0;
   1881   AcquireSRWLockExclusive(&g_dlsym_lock);
   1882   if (g_dlsym_inited) {
   1883     ReleaseSRWLockExclusive(&g_dlsym_lock);
   1884     return;
   1885   }
   1886   proc = GetCurrentProcess();
   1887   if (EnumProcessModules(proc, g_dlsym_modules, sizeof(g_dlsym_modules),
   1888                          &need)) {
   1889     DWORD have = need / (DWORD)sizeof(HMODULE);
   1890     g_dlsym_count = have > (DWORD)(sizeof(g_dlsym_modules) / sizeof(HMODULE))
   1891                         ? (DWORD)(sizeof(g_dlsym_modules) / sizeof(HMODULE))
   1892                         : have;
   1893   } else {
   1894     /* Fall back to the executable module and the CRT/kernel essentials. */
   1895     g_dlsym_modules[0] = GetModuleHandleW(NULL);
   1896     g_dlsym_modules[1] = GetModuleHandleW(L"kernel32.dll");
   1897     g_dlsym_modules[2] = GetModuleHandleW(L"msvcrt.dll");
   1898     g_dlsym_modules[3] = GetModuleHandleW(L"ucrtbase.dll");
   1899     g_dlsym_count = 4;
   1900   }
   1901   g_dlsym_inited = 1;
   1902   ReleaseSRWLockExclusive(&g_dlsym_lock);
   1903 }
   1904 
   1905 static void* win_dlsym(const char* name) {
   1906   DWORD i;
   1907   if (!name) return NULL;
   1908   dlsym_init_once();
   1909   /* Try the source-level name; if that misses and there's a leading '_'
   1910    * (Mach-O-mangled), try without it. */
   1911   AcquireSRWLockShared(&g_dlsym_lock);
   1912   for (i = 0; i < g_dlsym_count; ++i) {
   1913     void* p;
   1914     if (!g_dlsym_modules[i]) continue;
   1915     p = (void*)GetProcAddress(g_dlsym_modules[i], name);
   1916     if (p) {
   1917       ReleaseSRWLockShared(&g_dlsym_lock);
   1918       return p;
   1919     }
   1920   }
   1921   if (name[0] == '_' && name[1] != '\0') {
   1922     for (i = 0; i < g_dlsym_count; ++i) {
   1923       void* p;
   1924       if (!g_dlsym_modules[i]) continue;
   1925       p = (void*)GetProcAddress(g_dlsym_modules[i], name + 1);
   1926       if (p) {
   1927         ReleaseSRWLockShared(&g_dlsym_lock);
   1928         return p;
   1929       }
   1930     }
   1931   }
   1932   ReleaseSRWLockShared(&g_dlsym_lock);
   1933   return NULL;
   1934 }
   1935 
   1936 static int win_name_eq(KitSlice s, const char* lit, size_t litlen) {
   1937   return s.len == litlen && memcmp(s.s, lit, litlen) == 0;
   1938 }
   1939 
   1940 /* libucrt-static stdio entry points handed to JIT'd code.
   1941  *
   1942  * With kit's UCRT profile (__USE_MINGW_ANSI_STDIO=0), mingw's <stdio.h>
   1943  * resolves the printf/scanf family to out-of-line wrappers that live ONLY in
   1944  * libucrt.a — ucrtbase.dll exports just the __stdio_common_v* cores (and
   1945  * __local_stdio_* helpers the wrappers call). An AOT `kit cc` link pulls the
   1946  * wrappers from libucrt.a; the in-process JIT (`kit run`) resolves externs via
   1947  * dlsym over loaded DLLs (win_dlsym), which can't see them, so a printf-using
   1948  * JIT program fails with an undefined `printf`. kit.exe statically links
   1949  * libucrt.a, so taking each wrapper's address here pulls its definition into
   1950  * our own image and lets us hand the JIT our copy. Functions that ARE
   1951  * ucrtbase.dll exports (puts, fputs, fflush, putchar, __stdio_common_v*,
   1952  * __acrt_iob_func, …) resolve through win_dlsym above and never reach this
   1953  * table. The __local_stdio_*_options helpers' storage only ever holds the
   1954  * default (0) flags, so sharing one instance between the host and the JIT'd
   1955  * program is benign. */
   1956 void* driver_dlsym_resolver(void* user, KitSlice name_s) {
   1957   void* p;
   1958   (void)user;
   1959   if (!name_s.s || name_s.len == 0) return NULL;
   1960   p = win_dlsym(name_s.s);
   1961   if (p) return p;
   1962   /* Match by source name; #fn stringizes it and sizeof-1 is its length. */
   1963 #define KIT_STATIC_STDIO(fn) \
   1964   if (win_name_eq(name_s, #fn, sizeof(#fn) - 1u)) return (void*)(uintptr_t)&fn
   1965   KIT_STATIC_STDIO(__local_stdio_printf_options);
   1966   KIT_STATIC_STDIO(__local_stdio_scanf_options);
   1967   KIT_STATIC_STDIO(printf);
   1968   KIT_STATIC_STDIO(fprintf);
   1969   KIT_STATIC_STDIO(sprintf);
   1970   KIT_STATIC_STDIO(snprintf);
   1971   KIT_STATIC_STDIO(_snprintf);
   1972   KIT_STATIC_STDIO(vprintf);
   1973   KIT_STATIC_STDIO(vfprintf);
   1974   KIT_STATIC_STDIO(vsprintf);
   1975   KIT_STATIC_STDIO(vsnprintf);
   1976   KIT_STATIC_STDIO(_vsnprintf);
   1977   KIT_STATIC_STDIO(scanf);
   1978   KIT_STATIC_STDIO(fscanf);
   1979   KIT_STATIC_STDIO(sscanf);
   1980   KIT_STATIC_STDIO(_snscanf);
   1981   KIT_STATIC_STDIO(vscanf);
   1982   KIT_STATIC_STDIO(vfscanf);
   1983   KIT_STATIC_STDIO(vsscanf);
   1984   KIT_STATIC_STDIO(_scprintf);
   1985   KIT_STATIC_STDIO(_vscprintf);
   1986 #undef KIT_STATIC_STDIO
   1987   return NULL;
   1988 }
   1989 
   1990 /* ============================================================
   1991  *   SIGINT shim via SetConsoleCtrlHandler
   1992  * ============================================================ */
   1993 
   1994 static void (*g_ctrlc_cb)(void*);
   1995 static void* g_ctrlc_cb_user;
   1996 
   1997 static BOOL WINAPI ctrlc_trampoline(DWORD type) {
   1998   if (type == CTRL_C_EVENT || type == CTRL_BREAK_EVENT) {
   1999     if (g_ctrlc_cb) g_ctrlc_cb(g_ctrlc_cb_user);
   2000     return TRUE; /* handled */
   2001   }
   2002   return FALSE;
   2003 }
   2004 
   2005 int driver_install_sigint(void (*cb)(void*), void* user) {
   2006   g_ctrlc_cb = cb;
   2007   g_ctrlc_cb_user = user;
   2008   return SetConsoleCtrlHandler(ctrlc_trampoline, TRUE) ? 0 : 1;
   2009 }
   2010 
   2011 void driver_restore_sigint(void) {
   2012   SetConsoleCtrlHandler(ctrlc_trampoline, FALSE);
   2013   g_ctrlc_cb = NULL;
   2014   g_ctrlc_cb_user = NULL;
   2015 }
   2016 
   2017 /* No fault-guard on Windows yet (the POSIX path uses sigaction + sigsetjmp; a
   2018  * vectored-exception-handler port is a follow-up). Run the entry directly so
   2019  * `kit run` still executes the program; on_crash never fires. */
   2020 int driver_run_with_crash_guard(DriverEnv* env, KitArchKind arch,
   2021                                 DriverRunEntryFn entry, int argc, char** argv,
   2022                                 int* ret_out, DriverRunCrashFn on_crash,
   2023                                 void* user) {
   2024   (void)env;
   2025   (void)arch;
   2026   (void)on_crash;
   2027   (void)user;
   2028   *ret_out = entry(argc, argv);
   2029   return 0;
   2030 }
   2031 
   2032 /* ============================================================
   2033  *   Win32 CONTEXT <-> KitUnwindFrame marshalling
   2034  * ============================================================ */
   2035 
   2036 /* Inline per-arch marshalling: the Win32 dbg path is short enough that a
   2037  * separate uctx_*_windows.c isn't worth the extra TU. Only the host arch
   2038  * matters; cross-arch debug isn't a Win32 concern (the worker runs in our
   2039  * own process). */
   2040 
   2041 #if defined(_M_X64) || defined(__x86_64__)
   2042 static void ctx_to_frame(const CONTEXT* c, KitUnwindFrame* f) {
   2043   memset(f, 0, sizeof(*f));
   2044   f->pc = (uint64_t)c->Rip;
   2045   f->cfa = (uint64_t)c->Rsp;
   2046   /* SysV DWARF mapping: rax=0, rdx=1, rcx=2, rbx=3, rsi=4, rdi=5, rbp=6,
   2047    * rsp=7, r8..r15=8..15. We use the same mapping so kit's DWARF reader
   2048    * can interpret these directly. */
   2049   f->regs[0] = c->Rax;
   2050   f->regs[1] = c->Rdx;
   2051   f->regs[2] = c->Rcx;
   2052   f->regs[3] = c->Rbx;
   2053   f->regs[4] = c->Rsi;
   2054   f->regs[5] = c->Rdi;
   2055   f->regs[6] = c->Rbp;
   2056   f->regs[7] = c->Rsp;
   2057   f->regs[8] = c->R8;
   2058   f->regs[9] = c->R9;
   2059   f->regs[10] = c->R10;
   2060   f->regs[11] = c->R11;
   2061   f->regs[12] = c->R12;
   2062   f->regs[13] = c->R13;
   2063   f->regs[14] = c->R14;
   2064   f->regs[15] = c->R15;
   2065 }
   2066 
   2067 static void frame_to_ctx(const KitUnwindFrame* f, CONTEXT* c) {
   2068   c->Rip = f->pc;
   2069   c->Rax = f->regs[0];
   2070   c->Rdx = f->regs[1];
   2071   c->Rcx = f->regs[2];
   2072   c->Rbx = f->regs[3];
   2073   c->Rsi = f->regs[4];
   2074   c->Rdi = f->regs[5];
   2075   c->Rbp = f->regs[6];
   2076   c->Rsp = f->regs[7];
   2077   c->R8 = f->regs[8];
   2078   c->R9 = f->regs[9];
   2079   c->R10 = f->regs[10];
   2080   c->R11 = f->regs[11];
   2081   c->R12 = f->regs[12];
   2082   c->R13 = f->regs[13];
   2083   c->R14 = f->regs[14];
   2084   c->R15 = f->regs[15];
   2085 }
   2086 #elif defined(_M_ARM64) || defined(__aarch64__)
   2087 static void ctx_to_frame(const CONTEXT* c, KitUnwindFrame* f) {
   2088   unsigned i;
   2089   memset(f, 0, sizeof(*f));
   2090   f->pc = (uint64_t)c->Pc;
   2091   f->cfa = (uint64_t)c->Sp;
   2092   for (i = 0; i < 31; ++i) f->regs[i] = c->X[i];
   2093   f->regs[31] = c->Sp;
   2094 }
   2095 
   2096 static void frame_to_ctx(const KitUnwindFrame* f, CONTEXT* c) {
   2097   unsigned i;
   2098   c->Pc = f->pc;
   2099   for (i = 0; i < 31; ++i) c->X[i] = f->regs[i];
   2100   c->Sp = f->regs[31];
   2101 }
   2102 #else
   2103 static void ctx_to_frame(const CONTEXT* c, KitUnwindFrame* f) {
   2104   (void)c;
   2105   memset(f, 0, sizeof(*f));
   2106 }
   2107 static void frame_to_ctx(const KitUnwindFrame* f, CONTEXT* c) {
   2108   (void)f;
   2109   (void)c;
   2110 }
   2111 #endif
   2112 
   2113 /* Map a Win32 exception code to the POSIX-style signo the dbg session
   2114  * expects. Anything we don't recognize falls through as a generic SEGV. */
   2115 static int exception_code_to_signo(DWORD code) {
   2116   switch (code) {
   2117     case EXCEPTION_BREAKPOINT:
   2118     case EXCEPTION_SINGLE_STEP:
   2119       return 5; /* SIGTRAP */
   2120     case EXCEPTION_ILLEGAL_INSTRUCTION:
   2121     case EXCEPTION_PRIV_INSTRUCTION:
   2122       return 4; /* SIGILL */
   2123     case EXCEPTION_INT_DIVIDE_BY_ZERO:
   2124     case EXCEPTION_INT_OVERFLOW:
   2125     case EXCEPTION_FLT_DIVIDE_BY_ZERO:
   2126     case EXCEPTION_FLT_OVERFLOW:
   2127     case EXCEPTION_FLT_UNDERFLOW:
   2128     case EXCEPTION_FLT_INVALID_OPERATION:
   2129     case EXCEPTION_FLT_DENORMAL_OPERAND:
   2130     case EXCEPTION_FLT_INEXACT_RESULT:
   2131     case EXCEPTION_FLT_STACK_CHECK:
   2132       return 8; /* SIGFPE */
   2133     case EXCEPTION_DATATYPE_MISALIGNMENT:
   2134       return 7; /* SIGBUS */
   2135     case EXCEPTION_ACCESS_VIOLATION:
   2136     case EXCEPTION_IN_PAGE_ERROR:
   2137     case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
   2138     case EXCEPTION_STACK_OVERFLOW:
   2139     default:
   2140       return 11; /* SIGSEGV */
   2141   }
   2142 }
   2143 
   2144 /* ============================================================
   2145  *   dbg_os: threads, events, exceptions, guarded_copy
   2146  * ============================================================ */
   2147 
   2148 static KitDbgSignalOps g_dbg_ops;
   2149 static int g_dbg_ops_set;
   2150 static void* g_dbg_session;
   2151 static DWORD g_dbg_worker_tid;
   2152 static int g_dbg_worker_tid_valid;
   2153 static PVOID g_veh_cookie;
   2154 
   2155 /* Guarded-copy landing for the VEH path. dbg_guarded_copy_win sets
   2156  * g_guard_armed around the copy; if the VEH fires while it's set the exception
   2157  * is "translated" into a longjmp via the VEH's ability to rewrite the resume
   2158  * context. This is the single mechanism for every compiler (no SEH __try). */
   2159 static __thread int g_guard_armed;
   2160 static __thread jmp_buf g_guard_buf;
   2161 
   2162 static int win_dbg_caller_is_worker(void) {
   2163   return g_dbg_worker_tid_valid && GetCurrentThreadId() == g_dbg_worker_tid;
   2164 }
   2165 
   2166 /* --- thread shim --- */
   2167 
   2168 typedef struct DbgWinThread {
   2169   HANDLE handle;
   2170   DWORD tid;
   2171   void (*fn)(void*);
   2172   void* arg;
   2173 } DbgWinThread;
   2174 
   2175 static unsigned __stdcall dbg_thread_trampoline(void* p) {
   2176   DbgWinThread* t = (DbgWinThread*)p;
   2177   t->fn(t->arg);
   2178   return 0;
   2179 }
   2180 
   2181 static KitStatus dbg_thread_start_win(void* user, void (*fn)(void*), void* arg,
   2182                                       void** thread_out) {
   2183   DbgWinThread* t;
   2184   uintptr_t h;
   2185   (void)user;
   2186   t = (DbgWinThread*)malloc(sizeof(*t));
   2187   if (!t) return KIT_NOMEM;
   2188   t->fn = fn;
   2189   t->arg = arg;
   2190   /* _beginthreadex sets up the CRT TLS for the new thread; pure CreateThread
   2191    * leaves the CRT in an undefined state for code that calls printf/malloc. */
   2192   h = _beginthreadex(NULL, 0, dbg_thread_trampoline, t, 0, (unsigned*)&t->tid);
   2193   if (h == 0) {
   2194     free(t);
   2195     return KIT_ERR;
   2196   }
   2197   t->handle = (HANDLE)h;
   2198   g_dbg_worker_tid = t->tid;
   2199   g_dbg_worker_tid_valid = 1;
   2200   *thread_out = t;
   2201   return KIT_OK;
   2202 }
   2203 
   2204 static void dbg_thread_join_win(void* user, void* thread) {
   2205   DbgWinThread* t = (DbgWinThread*)thread;
   2206   (void)user;
   2207   if (!t) return;
   2208   WaitForSingleObject(t->handle, INFINITE);
   2209   CloseHandle(t->handle);
   2210   g_dbg_worker_tid_valid = 0;
   2211   free(t);
   2212 }
   2213 
   2214 /* Interrupt the worker by suspending it, snapshotting its CONTEXT,
   2215  * calling on_fault on the caller thread (the REPL), and writing back any
   2216  * register edits. This differs from POSIX (where the signal handler runs
   2217  * the on_fault on the worker), but the observable contract -- "the worker
   2218  * is stopped while on_fault runs and resumes with the edited frame" --
   2219  * matches. Locking quirks of SuspendThread aren't a concern here: the
   2220  * worker is, by construction, running JIT'd user code that holds no host
   2221  * locks. */
   2222 static KitStatus dbg_thread_interrupt_win(void* user, void* thread) {
   2223   DbgWinThread* t = (DbgWinThread*)thread;
   2224   CONTEXT ctx;
   2225   KitUnwindFrame frame;
   2226   KitStatus rc;
   2227   (void)user;
   2228   if (!t || !g_dbg_ops_set || !g_dbg_ops.on_fault) return KIT_INVALID;
   2229   if (SuspendThread(t->handle) == (DWORD)-1) return KIT_ERR;
   2230   memset(&ctx, 0, sizeof(ctx));
   2231   ctx.ContextFlags = CONTEXT_FULL;
   2232   if (!GetThreadContext(t->handle, &ctx)) {
   2233     ResumeThread(t->handle);
   2234     return KIT_ERR;
   2235   }
   2236   ctx_to_frame(&ctx, &frame);
   2237   rc = g_dbg_ops.on_fault(g_dbg_session, DBG_WIN_INTERRUPT_SIGNO, &frame);
   2238   if (rc == KIT_OK) {
   2239     frame_to_ctx(&frame, &ctx);
   2240     SetThreadContext(t->handle, &ctx);
   2241   }
   2242   ResumeThread(t->handle);
   2243   return rc;
   2244 }
   2245 
   2246 /* --- event shim --- */
   2247 
   2248 static KitStatus dbg_event_new_win(void* user, void** event_out) {
   2249   HANDLE h;
   2250   (void)user;
   2251   /* Manual-reset so a signaler that races ahead of the waiter doesn't lose
   2252    * the wake-up; the dbg core explicitly resets via event_reset. */
   2253   h = CreateEventW(NULL, TRUE, FALSE, NULL);
   2254   if (!h) return KIT_ERR;
   2255   *event_out = (void*)h;
   2256   return KIT_OK;
   2257 }
   2258 
   2259 static void dbg_event_free_win(void* user, void* ev) {
   2260   (void)user;
   2261   if (ev) CloseHandle((HANDLE)ev);
   2262 }
   2263 
   2264 static KitStatus dbg_event_wait_win(void* user, void* ev) {
   2265   (void)user;
   2266   if (WaitForSingleObject((HANDLE)ev, INFINITE) != WAIT_OBJECT_0)
   2267     return KIT_ERR;
   2268   /* Auto-reset semantics expected by dbg_event_wait on POSIX (it clears
   2269    * the flag after consuming). Mirror that here so the contract holds. */
   2270   ResetEvent((HANDLE)ev);
   2271   return KIT_OK;
   2272 }
   2273 
   2274 static KitStatus dbg_event_signal_win(void* user, void* ev) {
   2275   (void)user;
   2276   return SetEvent((HANDLE)ev) ? KIT_OK : KIT_ERR;
   2277 }
   2278 
   2279 static KitStatus dbg_event_reset_win(void* user, void* ev) {
   2280   (void)user;
   2281   return ResetEvent((HANDLE)ev) ? KIT_OK : KIT_ERR;
   2282 }
   2283 
   2284 /* --- vectored exception handler --- */
   2285 
   2286 /* Recovery thunk: for a fault inside a guarded_copy (g_guard_armed) or any
   2287  * other natural fault raised in a guarded region, hand control back to the
   2288  * longjmp target by rewriting the resume context. */
   2289 static LONG WINAPI dbg_veh(EXCEPTION_POINTERS* ep) {
   2290   DWORD code = ep->ExceptionRecord->ExceptionCode;
   2291   KitUnwindFrame frame;
   2292   KitStatus rc;
   2293   int signo;
   2294 
   2295   /* Filter out C++-style exceptions / debug strings we don't care about. */
   2296   if (code == 0x406D1388u /* MS_VC_EXCEPTION (SetThreadName) */ ||
   2297       code == 0xE06D7363u /* CXX EH */ || code == DBG_PRINTEXCEPTION_C ||
   2298       code == DBG_PRINTEXCEPTION_WIDE_C)
   2299     return EXCEPTION_CONTINUE_SEARCH;
   2300 
   2301   /* SEGV/BUS during a guarded_copy: bail out without involving the
   2302    * session. The VEH backstop only fires if SEH/__try wasn't compiled in
   2303    * for the function that armed the guard. */
   2304   if (g_guard_armed &&
   2305       (code == EXCEPTION_ACCESS_VIOLATION || code == EXCEPTION_IN_PAGE_ERROR ||
   2306        code == EXCEPTION_DATATYPE_MISALIGNMENT)) {
   2307     g_guard_armed = 0;
   2308     longjmp(g_guard_buf, 1);
   2309     /* not reached */
   2310     return EXCEPTION_CONTINUE_EXECUTION;
   2311   }
   2312 
   2313   if (!win_dbg_caller_is_worker() || !g_dbg_ops_set || !g_dbg_ops.on_fault)
   2314     return EXCEPTION_CONTINUE_SEARCH;
   2315 
   2316   signo = exception_code_to_signo(code);
   2317   ctx_to_frame(ep->ContextRecord, &frame);
   2318   rc = g_dbg_ops.on_fault(g_dbg_session, signo, &frame);
   2319   if (rc != KIT_OK) return EXCEPTION_CONTINUE_SEARCH;
   2320   frame_to_ctx(&frame, ep->ContextRecord);
   2321   return EXCEPTION_CONTINUE_EXECUTION;
   2322 }
   2323 
   2324 static KitStatus dbg_signals_install_win(void* user, const KitDbgSignalOps* ops,
   2325                                          void* session) {
   2326   (void)user;
   2327   if (g_veh_cookie) return KIT_ERR;
   2328   g_dbg_ops = *ops;
   2329   g_dbg_ops_set = 1;
   2330   g_dbg_session = session;
   2331   /* First==1 ensures we run before any other VEH and before the system's
   2332    * default last-chance handler. */
   2333   g_veh_cookie = AddVectoredExceptionHandler(1, dbg_veh);
   2334   if (!g_veh_cookie) {
   2335     memset(&g_dbg_ops, 0, sizeof(g_dbg_ops));
   2336     g_dbg_ops_set = 0;
   2337     g_dbg_session = NULL;
   2338     return KIT_ERR;
   2339   }
   2340   return KIT_OK;
   2341 }
   2342 
   2343 static void dbg_signals_uninstall_win(void* user) {
   2344   (void)user;
   2345   if (g_veh_cookie) {
   2346     RemoveVectoredExceptionHandler(g_veh_cookie);
   2347     g_veh_cookie = NULL;
   2348   }
   2349   memset(&g_dbg_ops, 0, sizeof(g_dbg_ops));
   2350   g_dbg_ops_set = 0;
   2351   g_dbg_session = NULL;
   2352 }
   2353 
   2354 /* --- W^X transitions --- */
   2355 
   2356 static size_t page_floor(size_t v, size_t pg) { return v & ~(pg - 1); }
   2357 static size_t page_ceil(size_t v, size_t pg) {
   2358   return (v + pg - 1) & ~(pg - 1);
   2359 }
   2360 
   2361 static KitStatus dbg_code_write_begin_win(void* user, void* runtime_addr,
   2362                                           size_t n, void** write_out) {
   2363   size_t pg;
   2364   uintptr_t a;
   2365   uintptr_t base;
   2366   size_t span;
   2367   DWORD old;
   2368   (void)user;
   2369   if (!runtime_addr || !n || !write_out) return KIT_INVALID;
   2370   /* Dual-mapped reservation: write through the alias, no protect flip. */
   2371   if (exec_dual_lookup_w(runtime_addr, n, write_out) == 0) return KIT_OK;
   2372   /* Single-mapping fallback: transient PAGE_EXECUTE_READWRITE. */
   2373   pg = driver_host_page_size_win();
   2374   a = (uintptr_t)runtime_addr;
   2375   base = page_floor(a, pg);
   2376   span = page_ceil((a - base) + n, pg);
   2377   if (!VirtualProtect((void*)base, span, PAGE_EXECUTE_READWRITE, &old))
   2378     return KIT_ERR;
   2379   *write_out = runtime_addr;
   2380   return KIT_OK;
   2381 }
   2382 
   2383 static void dbg_code_write_end_win(void* user, void* runtime_addr, size_t n) {
   2384   void* w;
   2385   size_t pg;
   2386   uintptr_t a;
   2387   uintptr_t base;
   2388   size_t span;
   2389   DWORD old;
   2390   (void)user;
   2391   if (exec_dual_lookup_w(runtime_addr, n, &w) == 0)
   2392     return; /* nothing to flip */
   2393   pg = driver_host_page_size_win();
   2394   a = (uintptr_t)runtime_addr;
   2395   base = page_floor(a, pg);
   2396   span = page_ceil((a - base) + n, pg);
   2397   VirtualProtect((void*)base, span, PAGE_EXECUTE_READ, &old);
   2398 }
   2399 
   2400 static void dbg_flush_icache_win(void* user, void* runtime_addr, size_t n) {
   2401   (void)user;
   2402   FlushInstructionCache(GetCurrentProcess(), runtime_addr, n);
   2403 }
   2404 
   2405 /* --- guarded copy via the vectored exception handler --- */
   2406 
   2407 static KitStatus dbg_guarded_copy_win(void* user, void* dst, const void* src,
   2408                                       size_t n) {
   2409   (void)user;
   2410   /* Catch an access fault during the copy without SEH __try/__except: the
   2411    * vectored handler dbg_veh (installed by dbg_signals_install_win) sees the
   2412    * thread-local g_guard_armed and longjmps back through g_guard_buf on a
   2413    * fault. This is one path for every compiler — kit's own frontend, gcc, and
   2414    * clang alike — so it needs no -fseh-exceptions / -fms-extensions and no
   2415    * __try language extension. */
   2416   if (setjmp(g_guard_buf) != 0) {
   2417     g_guard_armed = 0;
   2418     return KIT_ERR;
   2419   }
   2420   g_guard_armed = 1;
   2421   memcpy(dst, src, n);
   2422   g_guard_armed = 0;
   2423   return KIT_OK;
   2424 }
   2425 
   2426 /* --- call_with_catch / thread_abort (longjmp-based) --- */
   2427 
   2428 static __thread jmp_buf g_dbg_abort_buf;
   2429 
   2430 static int dbg_call_with_catch_win(void* user, void (*fn)(void*), void* arg) {
   2431   (void)user;
   2432   if (setjmp(g_dbg_abort_buf) == 0) {
   2433     fn(arg);
   2434     return 0;
   2435   }
   2436   return 1;
   2437 }
   2438 
   2439 static void dbg_thread_abort_win(void* user) {
   2440   (void)user;
   2441   longjmp(g_dbg_abort_buf, 1);
   2442 }
   2443 
   2444 static KitDbgOs g_dbg_os_win = {
   2445     .thread_start = dbg_thread_start_win,
   2446     .thread_join = dbg_thread_join_win,
   2447     .thread_interrupt = dbg_thread_interrupt_win,
   2448     .event_new = dbg_event_new_win,
   2449     .event_free = dbg_event_free_win,
   2450     .event_wait = dbg_event_wait_win,
   2451     .event_signal = dbg_event_signal_win,
   2452     .event_reset = dbg_event_reset_win,
   2453     .signals_install = dbg_signals_install_win,
   2454     .signals_uninstall = dbg_signals_uninstall_win,
   2455     .interrupt_signo = DBG_WIN_INTERRUPT_SIGNO,
   2456     .trap_signo = 5,
   2457     .code_write_begin = dbg_code_write_begin_win,
   2458     .code_write_end = dbg_code_write_end_win,
   2459     .flush_icache = dbg_flush_icache_win,
   2460     .guarded_copy = dbg_guarded_copy_win,
   2461     .call_with_catch = dbg_call_with_catch_win,
   2462     .thread_abort = dbg_thread_abort_win,
   2463     .user = NULL,
   2464 };
   2465 
   2466 /* JIT thread-local access resolves to in-image storage (single-threaded JIT:
   2467  * the in-image TLS data is the single instance — see src/link/link_jit.c), so
   2468  * no host-provided per-thread TLS vtable is needed. */
   2469 
   2470 /* ============================================================
   2471  *   host target
   2472  * ============================================================ */
   2473 
   2474 static KitArchKind host_arch_self_win(void) {
   2475 #if defined(_M_X64) || defined(__x86_64__)
   2476   return KIT_ARCH_X86_64;
   2477 #elif defined(_M_ARM64) || defined(__aarch64__)
   2478   return KIT_ARCH_ARM_64;
   2479 #elif defined(_M_IX86) || defined(__i386__)
   2480   return KIT_ARCH_X86_32;
   2481 #elif defined(_M_ARM) || defined(__arm__)
   2482   return KIT_ARCH_ARM_32;
   2483 #else
   2484   return KIT_ARCH_X86_64;
   2485 #endif
   2486 }
   2487 
   2488 KitTargetSpec driver_host_target(void) {
   2489   KitTargetSpec t = {0};
   2490   t.arch = host_arch_self_win();
   2491   t.os = KIT_OS_WINDOWS;
   2492   t.obj = KIT_OBJ_COFF;
   2493   t.ptr_size = (uint8_t)sizeof(void*);
   2494   t.ptr_align = (uint8_t)sizeof(void*);
   2495   t.big_endian = 0;
   2496   t.pic = driver_default_pic(t.obj, t.os);
   2497   t.code_model = KIT_CM_DEFAULT;
   2498   return t;
   2499 }
   2500 
   2501 /* No host probe on Windows: the MinGW sysroot is supplied via --sysroot or the
   2502  * KIT_SYSROOT env var (handled in the cc driver), not auto-discovered from a
   2503  * fixed install path. */
   2504 int driver_default_hosted_dirs(DriverEnv* env, KitTargetSpec target,
   2505                                DriverHostedDirs* out) {
   2506   (void)env;
   2507   (void)target;
   2508   (void)out;
   2509   return 1;
   2510 }
   2511 
   2512 /* ============================================================
   2513  *   env wiring
   2514  * ============================================================ */
   2515 
   2516 static char g_cache_dir_win[4096];
   2517 
   2518 void driver_env_init(DriverEnv* e) {
   2519   e->heap = &g_heap_libc;
   2520   e->diag = &g_diag_stderr;
   2521   e->file_io.read_all = win_read_all;
   2522   e->file_io.release = win_release;
   2523   e->file_io.open_writer = win_open_writer;
   2524   e->file_io.user = e;
   2525 
   2526   g_execmem_win.page_size = driver_host_page_size_win();
   2527   g_execmem_win.reserve = execmem_reserve_win;
   2528   g_execmem_win.protect = execmem_protect_win;
   2529   g_execmem_win.release = execmem_release_win;
   2530   g_execmem_win.flush_icache = execmem_flush_icache_win;
   2531   g_execmem_win.user = NULL;
   2532   e->execmem = &g_execmem_win;
   2533 
   2534   e->dbg_os = &g_dbg_os_win;
   2535   /* Opt-in compile metrics (KIT_METRICS): one process-wide profiler backs both
   2536    * the heap counters and libkit's scope timers. NULL when unset -- the metrics
   2537    * hot path is then a single pointer check. */
   2538   e->profiler = driver_metrics_profiler();
   2539 
   2540   {
   2541     /* XDG_CACHE_HOME wins if set (cross-platform tooling convention),
   2542      * otherwise fall back to %LOCALAPPDATA%\\kit, otherwise a
   2543      * build-tree-local path. */
   2544     const char* xdg = getenv("XDG_CACHE_HOME");
   2545     const char* lap = getenv("LOCALAPPDATA");
   2546     if (xdg && *xdg) {
   2547       snprintf(g_cache_dir_win, sizeof(g_cache_dir_win), "%s/kit", xdg);
   2548     } else if (lap && *lap) {
   2549       snprintf(g_cache_dir_win, sizeof(g_cache_dir_win), "%s\\kit", lap);
   2550     } else {
   2551       snprintf(g_cache_dir_win, sizeof(g_cache_dir_win), "build\\kit-cache");
   2552     }
   2553     g_cache_dir_win[sizeof(g_cache_dir_win) - 1] = '\0';
   2554     e->cache_dir = g_cache_dir_win;
   2555   }
   2556 
   2557   {
   2558     const char* sde = getenv("SOURCE_DATE_EPOCH");
   2559     if (sde && *sde) {
   2560       char* endp = NULL;
   2561       long long v = strtoll(sde, &endp, 10);
   2562       e->now = (endp != sde && v >= 0) ? (int64_t)v : (int64_t)-1;
   2563     } else {
   2564       time_t t = time(NULL);
   2565       e->now = (t == (time_t)-1) ? (int64_t)-1 : (int64_t)t;
   2566     }
   2567   }
   2568 }
   2569 
   2570 void driver_env_fini(DriverEnv* e) { (void)e; }
   2571 
   2572 KitContext driver_env_to_context(const DriverEnv* e) {
   2573   KitContext c;
   2574   c.heap = e->heap;
   2575   c.file_io = &e->file_io;
   2576   c.diag = e->diag;
   2577   c.profiler = e->profiler;
   2578   c.now = e->now;
   2579   return c;
   2580 }
   2581 
   2582 KitJitHost driver_env_to_jit_host(const DriverEnv* e) {
   2583   KitJitHost h;
   2584   h.execmem = e->execmem;
   2585   return h;
   2586 }
   2587 
   2588 KitDbgHost driver_env_to_dbg_host(const DriverEnv* e) {
   2589   KitDbgHost h;
   2590   h.os = e->dbg_os;
   2591   return h;
   2592 }