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