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 }