{"name":"std/fs.nu","source":"// stdlib/std/fs.nu — non-fatal filesystem API\n//\n// Wraps the bare `nurl_file_*` runtime calls in the `! T IoErr` discipline\n// shared by the rest of the stdlib. Errors are classified via the runtime\n// helper `nurl_errno_kind`, which maps the libc errno of the most recent\n// failing call to one of the `IoErr` variants.\n//\n// The handle-based runtime calls (`nurl_file_open`, `nurl_file_write`,\n// `nurl_file_close`) are still available raw; this module only adds the\n// safe one-shot variants used by application code.\n//\n//   ( read_file path )           → ! String IoErr   owned String, actual byte length\n//   ( write_file path content )  → ! v IoErr        truncates / creates\n//   ( write_file_private path content ) → ! v IoErr  replaces `path` atomically\n//                                                    with an owner-only (0600) file —\n//                                                    for secrets (tokens, keys)\n//   ( append_file path content ) → ! v IoErr        opens \"a\"; creates if missing\n//   ( file_exists path )         → b\n//   ( file_size path )           → ! i IoErr        bytes; NotFound if stat fails\n//   ( file_delete path )         → ! v IoErr        succeeds even when missing\n//                                                    (matches Rust's `remove_file`?\n//                                                    no — `remove` returns 0 only when\n//                                                    the entry was actually unlinked,\n//                                                    so we surface ENOENT as NotFound)\n//   ( set_permissions path mode ) → ! v IoErr       chmod(2); mode is the octal\n//                                                    value as a plain integer\n//                                                    (0o600 = 384)\n//   ( dir_create path )          → ! v IoErr        mkdir 0755; AlreadyExists if exists\n//   ( dir_create_all path )      → ! v IoErr        mkdir -p: every missing parent;\n//                                                    an existing directory is not an error\n//   ( dir_remove_all path )      → ! v IoErr        recursive rm -rf of a directory tree\n//                                                    (also unlinks a plain file / symlink)\n//\n// Streaming reads — for inputs too large to slurp with read_file:\n//   ( file_open path )           → ! File IoErr     open a file for binary reading\n//   ( file_read_chunk f n )      → ! ( Vec u ) IoErr  up to n bytes; empty Vec at EOF\n//   ( file_eof f )               → b                T once the stream is drained\n//   ( file_close f )             → v                close now (optional: the\n//                                                    last owner closes it)\n//\n// Examples:\n//   : ! String IoErr r ( read_file `data.txt` )\n//   ?? r {\n//     T s → ( nurl_print ( string_data s ) )\n//     F e → ( nurl_eprintln ( io_err_msg # IoErr e ) )\n//   }\n//\n// Ownership: read_file returns an OWNED `String`; the caller is\n// responsible for `string_free`-ing it. write_file/append_file BORROW the content string —\n// callers may pass `( string_data s )` from an owned String without\n// transferring ownership.\n\n$ `stdlib/core/marker.nu`\n$ `stdlib/core/string.nu`\n$ `stdlib/core/io.nu`\n$ `stdlib/core/vec.nu`\n$ `stdlib/core/errors.nu`\n$ `stdlib/std/path.nu`\n$ `stdlib/core/posix.nu`\n$ `stdlib/core/rcbox.nu`\n\n// errno-kind → IoErr enum value. Order matches `nurl_errno_kind` in\n// runtime.c (NotFound=0, PermissionDenied=1, AlreadyExists=2,\n// Interrupted=3, UnexpectedEof=4, WriteFailed=5, ReadFailed=6, Other=7).\n@ _io_err_of_kind i k → IoErr {\n    ? == k 0 { ^ @ IoErr { NotFound } } {}\n    ? == k 1 { ^ @ IoErr { PermissionDenied } } {}\n    ? == k 2 { ^ @ IoErr { AlreadyExists } } {}\n    ? == k 3 { ^ @ IoErr { Interrupted } } {}\n    ? == k 4 { ^ @ IoErr { UnexpectedEof } } {}\n    ? == k 5 { ^ @ IoErr { WriteFailed } } {}\n    ? == k 6 { ^ @ IoErr { ReadFailed } } {}\n    ^ @ IoErr { Other }\n}\n\n// Text and binary reads share one EOF/error policy. Adopt the Vec control\n// block without copying; String retains the actual byte length, including NULs.\n@ read_file s path → !String IoErr {\n    ?? ( read_file_bytes path ) {\n        T bytes → { ^ @ !String IoErr { T @ String { . bytes ctl } } }\n        F e → { ^ @ !String IoErr { F e } }\n    }\n}\n\n// Always opens the file in binary mode (`wb`/`ab`). On POSIX this is a\n// no-op; on Windows the C runtime would otherwise translate every `\\n`\n// in the payload to `\\r\\n` under the text-mode `w`/`a` flag, which\n// corrupts UTF-8 multibyte sequences and shifts byte offsets emitted by\n// downstream parsers (notably nurlc — a `→` arrow sitting next to a\n// translated newline appears at the wrong column and is rejected as\n// \"expected type\"). Cross-platform NURL programs writing to disk almost\n// always want LF preserved exactly as written, so binary is the right\n// default. Callers that need CRLF must produce it explicitly in the\n// payload.\n// Non-fatal write: opens `path` in binary mode (`wb` truncate or `ab`\n// append) and writes `content`'s NUL-terminated bytes. Returns 0 on\n// success or -1 on any failure (open, partial write, or close); the\n// classification routes through `nurl_errno_kind` like every other\n// `IoErr` boundary. The body is pure NURL — `fopen` / `fwrite` /\n// `fclose` are declared in `nurlc.nu`'s preamble and reach libc directly.\n// Out of file descriptors (EMFILE / ENFILE): unreachable Rc cycles may be\n// holding some — collect them and say whether to try once more\n// (stdlib/runtime_core.c, docs/MEMORY.md §7.7).\n& `c` @ nurl_cc_reclaim_fds → i32\n\n// `fopen`, retried once when the process had run out of descriptors and\n// collecting unreachable cycles freed some.\n@ __fs_fopen s path s mode → s {\n    : s fp ( fopen path mode )\n    ? == 0 # i fp { ? != 0 ( nurl_cc_reclaim_fds ) { ^ ( fopen path mode ) } {} } {}\n    ^ fp\n}\n\n@ __write_file_pure s path s content s mode → i {\n    ? || == # i path 0 || == # i content 0 == # i mode 0 { ^ -1 } {}\n    : s fp ( __fs_fopen path mode )\n    ? == # i fp 0 { ^ -1 } {}\n    : i n ( nurl_str_len content )\n    ? > n 0 {\n        : i got ( fwrite content 1 n fp )\n        ? != got n {\n            : i32 _cr ( fclose fp )\n            ^ -1\n        } {}\n    } {}\n    : i32 cr ( fclose fp )\n    ? != cr # i32 0 { ^ -1 } {}\n    ^ 0\n}\n\n@ write_file s path s content → !v IoErr {\n    : i rc ( __write_file_pure path content `wb` )\n    ? == rc 0 {\n        ^ @ !v IoErr { T 0 }\n    } {}\n    : IoErr e ( _io_err_of_kind ( errno_kind ) )\n    ^ @ !v IoErr { F e }\n}\n\n@ append_file s path s content → !v IoErr {\n    : i rc ( __write_file_pure path content `ab` )\n    ? == rc 0 {\n        ^ @ !v IoErr { T 0 }\n    } {}\n    : IoErr e ( _io_err_of_kind ( errno_kind ) )\n    ^ @ !v IoErr { F e }\n}\n\n@ file_exists s path → b {\n    ^ != 0 ( nurl_file_exists path )\n}\n\n// Byte length of a regular file. Implementation: open in binary mode,\n// seek to end, query position, close. Avoids the `stat(2)` path\n// entirely because `struct stat`'s `st_size` field offset varies by\n// platform (Linux x86_64 = 48, macOS = 96, mingw layout different\n// again) and porting that offset table buys nothing — SEEK_END is a\n// universal POSIX value (2) honoured by every libc stdio we target.\n// Directories and other non-regular files yield the same \"fopen\n// succeeded\" outcome as on POSIX (ftell on a directory is\n// implementation-defined; the previous stat-based path returned the\n// directory's allocation size 4096 — a behavioural difference NURL\n// callers should not be relying on).\n@ __file_size_pure s path → i {\n    ? == # i path 0 { ^ -1 } {}\n    : s fp ( __fs_fopen path `rb` )\n    ? == # i fp 0 { ^ -1 } {}\n    : i32 sr ( fseek fp 0 # i32 2 )\n    ? != sr # i32 0 {\n        : i32 _u ( fclose fp )\n        ^ -1\n    } {}\n    : i sz ( ftell fp )\n    : i32 _cr ( fclose fp )\n    ^ sz\n}\n\n@ file_size s path → !i IoErr {\n    : i n ( __file_size_pure path )\n    ? < n 0 {\n        : IoErr e ( _io_err_of_kind ( errno_kind ) )\n        ^ @ !i IoErr { F e }\n    } {}\n    ^ @ !i IoErr { T n }\n}\n\n// Best-effort delete. `nurl_file_del` (libc `remove`) doesn't surface\n// errno reliably across platforms, so callers wanting to distinguish\n// \"did not exist\" from \"still there\" should call `file_exists` first.\n// Delete a directory entry.\n//\n// It does NOT pre-check with `file_exists`, and that is the point: that\n// check is `access(2)`, which FOLLOWS a symlink, so a link whose target\n// is gone answered \"not there\" and was never unlinked. Every `rm -r`\n// over a tree containing a broken symlink then failed at the directory\n// itself, with ENOTEMPTY, having reported the wrong reason twice. The\n// syscall's own return value knows better than a probe before it.\n@ file_delete s path → !v IoErr {\n    ? == ( nurl_file_del_rc path ) 0 { ^ @ !v IoErr { T 0 } } {}\n    : IoErr e ( _io_err_of_kind ( errno_kind ) )\n    ^ @ !v IoErr { F e }\n}\n\n@ dir_create s path → !v IoErr {\n    : i rc ( nurl_dir_create path )\n    ? == rc 0 {\n        ^ @ !v IoErr { T 0 }\n    } {}\n    : IoErr e ( _io_err_of_kind ( errno_kind ) )\n    ^ @ !v IoErr { F e }\n}\n\n& `c` @ chmod in s path i32 mode → i32\n\n// POSIX chmod(2). `mode` is the permission word as a plain integer, so\n// the familiar octal literals are written out in decimal: 0o600 = 384,\n// 0o644 = 420, 0o700 = 448, 0o755 = 493.\n//\n// This is what a program writes a private key with. write_file creates\n// at 0666 & ~umask, which on a default umask leaves a key readable by\n// every account on the host; there is no create-with-mode variant here\n// because fopen has no such argument, so the sequence is write then\n// tighten. The window between the two is why the caller should also be\n// creating the file in a directory that is not world-readable.\n//\n// On Windows the CRT honours only the owner-write bit and ignores the\n// rest — treat a success there as \"read-only flag set\", not as POSIX\n// permissions.\n@ set_permissions s path i mode → !v IoErr {\n    ? == # i path 0 { ^ @ !v IoErr { F @ IoErr { NotFound } } } {}\n    : i32 rc ( chmod path # i32 mode )\n    ? == # i rc 0 {\n        ^ @ !v IoErr { T 0 }\n    } {}\n    : IoErr e ( _io_err_of_kind ( errno_kind ) )\n    ^ @ !v IoErr { F e }\n}\n\n// ── stat(2) ─────────────────────────────────────────────────────────\n//\n// Everything a listing, a copy that preserves metadata, or a `find`\n// predicate needs to know about an entry, in one call.\n//\n// `struct stat` is not a portable layout, so the runtime reads it and\n// hands back sixteen int64 slots in an order the whole toolchain agrees\n// on (`nurl_stat_into`, stdlib/runtime_core.c). This module mirrors\n// those slots as `FileStat` and never touches the C struct itself.\n//\n//   ( fs_stat path )    → !FileStat IoErr   follows symlinks\n//   ( fs_lstat path )   → !FileStat IoErr   the link itself\n//   ( fs_fstat fd )     → !FileStat IoErr   an already-open descriptor\n//\n// A field the platform does not carry reads 0 rather than a made-up\n// value: Windows has no st_blocks and no user database, and the\n// unikernel has neither plus no nanosecond timestamps.\n\n& `c` @ nurl_stat_into in s path inout FileStat out → i\n\n& `c` @ nurl_lstat_into in s path inout FileStat out → i\n\n& `c` @ nurl_fstat_into i fd inout FileStat out → i\n\n& `c` @ nurl_stat_kind i mode → i\n\n& `c` @ nurl_utimes_set in s path i atime i ansec i mtime i mnsec i follow → i\n\n& `c` @ nurl_user_name i uid → sink s\n\n& `c` @ nurl_group_name i gid → sink s\n\n: FileStat {\n    i mode  // raw st_mode: permission bits AND type bits\n    i size\n    i mtime  // seconds since the epoch\n    i mtime_ns\n    i uid\n    i gid\n    i nlink\n    i ino\n    i dev\n    i rdev\n    i blocks  // 512-byte units, 0 where unsupported\n    i blksize\n    i atime\n    i atime_ns\n    i ctime\n    i ctime_ns\n}\n\n// What `stat_kind` answers. The S_IF* bits are not portable values, so\n// the runtime classifies and these name the answer.\n: i FS_KIND_UNKNOWN 0\n: i FS_KIND_FILE 1\n: i FS_KIND_DIR 2\n: i FS_KIND_SYMLINK 3\n: i FS_KIND_CHARDEV 4\n: i FS_KIND_BLOCKDEV 5\n: i FS_KIND_FIFO 6\n: i FS_KIND_SOCKET 7\n\n// A zeroed FileStat for the runtime to fill through `inout` (its 16\n// words in the order the runtime writes them).\n@ __fs_stat_zero → FileStat {\n    ^ @ FileStat { 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 }\n}\n\n@ __fs_stat_at s path i follow → !FileStat IoErr {\n    ? == # i path 0 { ^ @ !FileStat IoErr { F @ IoErr { NotFound } } } {}\n    : ~ FileStat st ( __fs_stat_zero )\n    : i rc ? != follow 0 ( nurl_stat_into path st ) ( nurl_lstat_into path st )\n    ? != rc 0 {\n        : IoErr e ( _io_err_of_kind ( errno_kind ) )\n        ^ @ !FileStat IoErr { F e }\n    } {}\n    ^ @ !FileStat IoErr { T st }\n}\n\n// Metadata for `path`, following a final symlink.\n@ fs_stat s path → !FileStat IoErr { ^ ( __fs_stat_at path 1 ) }\n\n// Metadata for the symlink itself. This is the one to use when walking\n// a tree: `fs_stat` on a dangling link is an error, and on a link to a\n// directory it says \"directory\", which is how a recursive delete follows\n// a link out of the tree it was given.\n@ fs_lstat s path → !FileStat IoErr { ^ ( __fs_stat_at path 0 ) }\n\n@ fs_fstat i fd → !FileStat IoErr {\n    : ~ FileStat st ( __fs_stat_zero )\n    : i rc ( nurl_fstat_into fd st )\n    ? != rc 0 {\n        : IoErr e ( _io_err_of_kind ( errno_kind ) )\n        ^ @ !FileStat IoErr { F e }\n    } {}\n    ^ @ !FileStat IoErr { T st }\n}\n\n// ── Filesystem statistics ───────────────────────────────────────────\n//\n// How big is the filesystem holding this path, and how much of it is\n// free — `df`'s question, and a writer's before it starts.\n//\n// Sizes are in `frsize` units (the fragment size), which is what POSIX\n// specifies and what makes `blocks * frsize` the byte total. A platform\n// with no statvfs answers an error rather than zeroes: an empty disk and\n// an unanswerable question are different, and a caller that treats the\n// second as the first refuses to write for no reason.\n\n& `c` @ nurl_statfs_into in s path inout FsStat out → i\n\n: FsStat {\n    i bsize  // preferred I/O size\n    i frsize  // fragment size — the unit `blocks` counts\n    i blocks\n    i bfree  // free to root\n    i bavail  // free to an unprivileged writer\n    i files  // inodes\n    i ffree\n    i favail\n    i flag\n    i namemax\n}\n\n@ fs_statfs s path → !FsStat IoErr {\n    ? == # i path 0 { ^ @ !FsStat IoErr { F @ IoErr { NotFound } } } {}\n    : ~ FsStat st @ FsStat { 0 0 0 0 0 0 0 0 0 0 }\n    ? != 0 ( nurl_statfs_into path st ) {\n        : IoErr e ( _io_err_of_kind ( errno_kind ) )\n        ^ @ !FsStat IoErr { F e }\n    } {}\n    ^ @ !FsStat IoErr { T st }\n}\n\n// ── Interpreting a FileStat ─────────────────────────────────────────\n\n@ stat_kind FileStat st → i { ^ ( nurl_stat_kind . st mode ) }\n\n@ stat_is_file FileStat st → b { ^ == ( stat_kind st ) FS_KIND_FILE }\n\n@ stat_is_dir FileStat st → b { ^ == ( stat_kind st ) FS_KIND_DIR }\n\n@ stat_is_symlink FileStat st → b { ^ == ( stat_kind st ) FS_KIND_SYMLINK }\n\n// The permission bits alone — 0777 plus setuid / setgid / sticky (07777).\n@ stat_mode_bits FileStat st → i { ^ & . st mode 4095 }\n\n@ __fs_rwx String out i bits i setid i setid_char → v {\n    ( string_push_char out ? != 0 & bits 4 114 45 )\n    ( string_push_char out ? != 0 & bits 2 119 45 )\n    ? != setid 0 {\n        ( string_push_char out ? != 0 & bits 1 setid_char - setid_char 32 )\n    } {\n        ( string_push_char out ? != 0 & bits 1 120 45 )\n    }\n}\n\n// The ten-character listing form: `drwxr-xr-x`, `-rw-r--r--`,\n// `lrwxrwxrwx`. setuid / setgid / sticky replace the matching execute\n// character with `s` / `s` / `t`, upper-cased when the execute bit\n// itself is clear — the convention every `ls` follows.\n@ stat_mode_string FileStat st → String {\n    : String out ( string_with_cap 12 )\n    : i k ( stat_kind st )\n    ( string_push_char out ? == k FS_KIND_DIR 100\n    ? == k FS_KIND_SYMLINK 108\n    ? == k FS_KIND_CHARDEV 99\n    ? == k FS_KIND_BLOCKDEV 98\n    ? == k FS_KIND_FIFO 112\n    ? == k FS_KIND_SOCKET 115 45 )\n    : i m ( stat_mode_bits st )\n    ( __fs_rwx out & >> m 6 7 & m 2048 115 )\n    ( __fs_rwx out & >> m 3 7 & m 1024 115 )\n    ( __fs_rwx out & m 7 & m 512 116 )\n    ^ out\n}\n\n// ── Timestamps ──────────────────────────────────────────────────────\n\n// Set access + modification times. Nanoseconds are honoured where the\n// platform has utimensat(2). `follow` = F acts on a symlink itself.\n@ fs_set_times s path i atime i atime_ns i mtime i mtime_ns b follow → !v IoErr {\n    : i rc ( nurl_utimes_set path atime atime_ns mtime mtime_ns ? follow 1 0 )\n    ? == rc 0 { ^ @ !v IoErr { T 0 } } {}\n    : IoErr e ( _io_err_of_kind ( errno_kind ) )\n    ^ @ !v IoErr { F e }\n}\n\n// ── The user database ───────────────────────────────────────────────\n//\n// The name behind a uid / gid, or None where the platform has no user\n// database (Windows, the unikernel) or the id is unknown. A caller that\n// gets None prints the number, which is what `ls -l` does on an NFS\n// mount whose ids do not resolve.\n\n@ fs_user_name i uid → ?String {\n    : s raw ( nurl_user_name uid )\n    ? == # i raw 0 { ^ @ ?String { F # String 0 } } {}\n    ^ @ ?String { T ( string_adopt raw ) }\n}\n\n@ fs_group_name i gid → ?String {\n    : s raw ( nurl_group_name gid )\n    ? == # i raw 0 { ^ @ ?String { F # String 0 } } {}\n    ^ @ ?String { T ( string_adopt raw ) }\n}\n\n// POSIX symlink(2) — `target` is what the link points TO (stored\n// verbatim in the symlink), `linkpath` is the new symlink entry to\n// create. Returns 0 on success, -1 on failure with errno set.\n// Windows is NOT supported here; CreateSymbolicLinkW needs a privilege\n// (`SeCreateSymbolicLinkPrivilege`) most accounts lack — Win32 users\n// of nurlpkg should fall back to copying.\n& `c` @ symlink in s target in s linkpath → i32\n\n& `c` @ nurl_hard_link s target s linkpath → i32\n\n// A hard link: `linkpath` names the same file `target` does (link(2);\n// CreateHardLinkA on Windows).\n@ fs_link s target s linkpath → !v IoErr {\n    : i32 rc ( nurl_hard_link target linkpath )\n    ? == rc 0 {\n        ^ @ !v IoErr { T 0 }\n    } {}\n    : IoErr e ( _io_err_of_kind ( errno_kind ) )\n    ^ @ !v IoErr { F e }\n}\n\n// Create a symbolic link at `linkpath` pointing to `target`. Returns\n// IoErr {AlreadyExists} if the entry already exists (errno = EEXIST),\n// {PermissionDenied} on EACCES, {Other} for everything else.\n@ fs_symlink s target s linkpath → !v IoErr {\n    : i32 rc ( symlink target linkpath )\n    ? == rc 0 {\n        ^ @ !v IoErr { T 0 }\n    } {}\n    : IoErr e ( _io_err_of_kind ( errno_kind ) )\n    ^ @ !v IoErr { F e }\n}\n\n// POSIX readlink(2) — writes the symlink's target into `buf` (NOT\n// NUL-terminated) and returns the byte count, or -1 with errno set\n// (EINVAL when `path` is not a symlink). `bufsiz` caps the write.\n& `c` @ readlink in s path *u buf i bufsiz → i\n\n// Read the target a symbolic link points to, as an owned String.\n// Returns IoErr {Other} when `path` is not a symlink (errno = EINVAL),\n// {NotFound} when it doesn't exist, etc. The target is captured into a\n// zero-filled PATH_MAX buffer so the returned String is always NUL-\n// terminated; targets longer than 4095 bytes (above the Linux symlink\n// ceiling) are truncated.\n@ fs_readlink s path → !String IoErr {\n    : i cap 4096\n    : ( Vec u ) buf ( vec_zeroed [u] + cap 1 )\n    : i n ( readlink path # *u ( vec_data [u] buf ) cap )\n    ? < n 0 {\n        : IoErr e ( _io_err_of_kind ( errno_kind ) )\n        ^ @ !String IoErr { F e }\n    } {}\n    ^ @ !String IoErr { T ( string_from_bytes # *u ( vec_data [u] buf ) n ) }\n}\n\n// Remove an empty directory. Returns NotFound when missing, Other when\n// the directory is non-empty (errno = ENOTEMPTY) — the errno-kind table\n// folds the latter into Other rather than introducing a new variant.\n@ dir_remove s path → !v IoErr {\n    : i rc ( nurl_dir_remove path )\n    ? == rc 0 {\n        ^ @ !v IoErr { T 0 }\n    } {}\n    : IoErr e ( _io_err_of_kind ( errno_kind ) )\n    ^ @ !v IoErr { F e }\n}\n\n// POSIX dir-list helpers — pure-NURL drivers over opendir/readdir/closedir.\n// PURIFY §13 batch 3 (2026-05-24): replaced the C-side iterator with a\n// loop in NURL that filters \".\" / \"..\" and copies each name into an\n// owned String. The `d_name` field offset within `struct dirent` varies\n// by platform (Linux glibc = 19, macOS = 21), so a 1-line C accessor\n// (`nurl_dirent_name` in runtime.c) bridges it without porting the\n// offset table into NURL.\n@ __dir_list_pure_posix s path → !( Vec String ) IoErr {\n    : s d ( opendir path )\n    ? == # i d 0 {\n        : IoErr e ( _io_err_of_kind ( errno_kind ) )\n        ^ @ !( Vec String ) IoErr { F e }\n    } {}\n    : ( Vec String ) out ( vec_new [String] )\n    : ~ b going T\n    ~ going {\n        : s de ( readdir d )\n        ? == # i de 0 {\n            = going F\n        } {\n            : s name ( nurl_dirent_name de )\n            ? != # i name 0 {\n                : i c0 ( nurl_str_get name 0 )\n                : i n ( nurl_str_len name )\n                : ~ b skip F\n                ? == c0 46 {\n                    ? == n 1 { = skip T } {\n                        ? & == n 2 == ( nurl_str_at name n 1 ) 46 { = skip T } {}\n                    }\n                } {}\n                ? ! skip {\n                    ( vec_push [String] out ( string_from name ) )\n                } {}\n            } {}\n        }\n    }\n    : i32 _c ( closedir d )\n    ^ @ !( Vec String ) IoErr { T out }\n}\n\n// List directory entries (excluding \".\" and \"..\") as owned Strings.\n// The returned Vec is owned: free the elements first via vec_free_with\n// + string_free, then drop the Vec itself. Order is platform-defined\n// (POSIX returns the on-disk order; Windows returns FindFirstFile order).\n// Callers that want a stable order should sort_by cmp_string afterwards.\n@ dir_list s path → !( Vec String ) IoErr {\n    // POSIX: drive opendir/readdir/closedir in pure NURL via\n    // `__dir_list_pure_posix`. Win32 / WASI route through the runtime's\n    // `nurl_dir_list_*` opaque-handle trio (FindFirstFile state cache).\n    // The predicate is a CAPABILITY question answered by the runtime, not\n    // a constant lookup standing in for one. `posix_const \\`O_RDONLY\\` != -1`\n    // used to be the test, and it stopped meaning \"POSIX dirent works\n    // here\" the moment `O_RDONLY` was published on Windows too — where\n    // it is needed, because the flag values differ from POSIX and a\n    // caller cannot spell them as literals. Every Windows directory\n    // listing then took this branch into runtime_core.c's own ENOSYS\n    // stubs and reported an i/o error.\n    ? ( posix_have_dirent ) {\n        ^ ( __dir_list_pure_posix path )\n    } {}\n    : i h ( nurl_dir_list_open path )\n    ? == h 0 {\n        : IoErr e ( _io_err_of_kind ( errno_kind ) )\n        ^ @ !( Vec String ) IoErr { F e }\n    } {}\n    : ( Vec String ) out ( vec_new [String] )\n    : ~ b going T\n    ~ going {\n        : s raw ( nurl_dir_list_next h )\n        : i p # i raw\n        ? == p 0 {\n            = going F\n        } {\n            ( vec_push [String] out ( string_adopt raw ) )\n        }\n    }\n    ( nurl_dir_list_close h )\n    ^ @ !( Vec String ) IoErr { T out }\n}\n\n// ── Binary I/O ──────────────────────────────────────────────────────\n//\n// Read and write opaque byte buffers (`( Vec u )`). NUL bytes are kept\n// verbatim — these are the right primitives for images, archives, msgpack\n// and any other non-text payload. For UTF-8 text use read_file/write_file.\n//\n// Memory model: read_file_bytes returns an owned Vec[u], dropped by its\n// owner. The buffer has a spare NUL terminator while Vec length retains\n// every input byte.\n// write_file_bytes BORROWS its byte buffer.\n\n// Buffered stdio bridge: retries interrupted reads, distinguishes I/O errors\n// from EOF, and preserves a prefix already read before an interruption.\n& `c` @ nurl_stream_read *u stream *u dst i room → i\n\n& `c` @ fileno *u stream → i32\n\n// File size is only a capacity hint from the opened regular file. Pipes,\n// procfs, growing files and short reads all use the same EOF-driven reader.\n@ read_file_bytes s path → !( Vec u ) IoErr {\n    ? == # i path 0 { ^ @ !( Vec u ) IoErr { F @ IoErr { Other } } } {}\n    : s f # s ( __fs_fopen path `rb` )\n    ? == # i f 0 {\n        : IoErr e ( _io_err_of_kind ( errno_kind ) )\n        ^ @ !( Vec u ) IoErr { F e }\n    } {}\n    : ~ i hint 4096\n    ?? ( fs_fstat # i ( fileno f ) ) {\n        T st → { ? & ( stat_is_file st ) > . st size 0 { = hint . st size } {} }\n        F _ → {}\n    }\n    : !( Vec u ) IoErr result ( read_to_end \\ * u dst i room → i { ^ ( nurl_stream_read f dst room ) } hint )\n    : i32 closed ( fclose f )\n    : IoErr close_error ( _io_err_of_kind ( errno_kind ) )\n    ?? result {\n        F e → { ^ @ !( Vec u ) IoErr { F e } }\n        T bytes → {\n            ? != closed # i32 0 {\n                ^ @ !( Vec u ) IoErr { F close_error }\n            } {}\n            ^ @ !( Vec u ) IoErr { T bytes }\n        }\n    }\n}\n\n// All of `v` to the open file descriptor `fd` (1 = standard output) — a\n// short write is continued — with nothing added: how many bytes went, or\n// -1 when a write failed.\n@ fd_write_all i fd ( Vec u ) v → i { ^ ( __fd_write_all fd v ) }\n\nunsafe @ __fd_write_all i fd ( Vec u ) v → i {\n    : i n ( vec_len [u] v )\n    : *u p ( vec_data [u] v )\n    : ~ i off 0\n    ~ < off n {\n        : i k ( write # i32 fd # *u + # i p off - n off )\n        ? <= k 0 { ^ -1 } {}\n        = off + off k\n    }\n    ^ off\n}\n\n@ write_file_bytes s path ( Vec u ) v → !v IoErr {\n    ^ ( __write_bytes path v `wb` )\n}\n\n@ append_file_bytes s path ( Vec u ) v → !v IoErr {\n    ^ ( __write_bytes path v `ab` )\n}\n\n@ __write_bytes s path ( Vec u ) v s mode → !v IoErr {\n    : s f # s ( __fs_fopen path mode )\n    ? == # i f 0 {\n        : IoErr e ( _io_err_of_kind ( errno_kind ) )\n        ^ @ !v IoErr { F e }\n    } {}\n    : i n ( vec_len [u] v )\n    ? > n 0 {\n        : *u data ( vec_data [u] v )\n        : i got ( fwrite # s data 1 n f )\n        ? != got n {\n            : i32 _ ( fclose f )\n            : IoErr e ( _io_err_of_kind ( errno_kind ) )\n            ^ @ !v IoErr { F e }\n        } {}\n    } {}\n    : i32 rc ( fclose f )\n    ? != rc # i32 0 {\n        : IoErr e ( _io_err_of_kind ( errno_kind ) )\n        ^ @ !v IoErr { F e }\n    } {}\n    ^ @ !v IoErr { T 0 }\n}\n\n// ── Recursive directory operations ──────────────────────────────────\n//\n// dir_create_all is mkdir -p; dir_remove_all is rm -rf. Both compose the\n// single-level primitives above. dir_remove_all classifies every entry\n// with `nurl_path_type`, which uses lstat — a symbolic link is unlinked,\n// never followed, so the walk can never escape the tree it was handed.\n\n// libc unlink(2): removes a file or symlink entry; 0 on success, -1 with\n// errno set. Used for non-directory entries during recursive removal —\n// nurl_file_del discards its return code, and file_delete's stat-based\n// existence probe would mis-report a dangling symlink as NotFound.\n& `c` @ unlink in s path → i32\n\n// Runtime filesystem helpers (resolved from runtime.o; libc is always\n// linked). nurl_path_type: 0 missing, 1 file, 2 dir, 3 symlink, 4 other.\n& `c` @ nurl_path_type in s path → i\n\n// The same classification FOLLOWING symlinks (stat, not lstat), so a link\n// to a directory answers 2 and can be descended. glob(3) resolves links in\n// intermediate path components, so the glob helpers below use this one;\n// dir_remove_all deliberately keeps the unlinked answer above.\n& `c` @ nurl_path_type_follow in s path → i\n\n// ── nurl_file_* via libc stdio ─────────────────────────────────────\n// fopen / fclose / fputs / fwrite / fputc / fread / feof come from\n// the nurlc preamble (globally declared). Handles are *v throughout.\n\n// fopen wrapper. Returns NULL handle on failure (NURL callers check\n// via `# i h == 0`).\n@ nurl_file_open s path s mode → *v {\n    : s h # s ( __fs_fopen path mode )\n    ^ # *v h\n}\n\n@ nurl_file_close * v h → v {\n    ? != 0 # i h { : i32 _ ( fclose # s h ) } {}\n}\n\n// fputs the NUL-terminated `str` to `h`. NULL-safe on both args.\n@ nurl_file_write * v h s str → v {\n    ? & != 0 # i h != 0 # i str {\n        : i32 _ ( fputs str # s h )\n    } {}\n}\n\n// fwrite `len` bytes from `p` to `h`. NULL-safe; len<=0 is a no-op.\n@ nurl_file_write_range * v h s p i len → v {\n    ? & & != 0 # i h != 0 # i p > len 0 {\n        : i _ ( fwrite p 1 len # s h )\n    } {}\n}\n\n// fputc one byte. NULL-safe.\n@ nurl_file_write_byte * v h i c → v {\n    ? != 0 # i h { : i32 _ ( fputc # i32 c # s h ) } {}\n}\n\n@ nurl_file_eof * v h → i {\n    ? == # i h 0 { ^ 1 } {}\n    ^ ? != 0 # i ( feof # s h ) 1 0\n}\n\n// `file_read_chunk` below calls fread(buf, 1, n, h) directly into a\n// Vec[u]'s data buffer; ferror gates the read-error path.\n& `c` @ ferror *u h → i32\n\n// ── probe + mutation ───────────────────────────────────────────────\n// access(2) / remove(3) / mkdir(2) / rmdir(2) wrappers. `access`\n// uses F_OK (0) for \"does this path exist at all\". `mkdir` mode is\n// 0755 (decimal 493).\n\n& `c` @ remove in s path → i32\n\n& `c` @ mkdir in s path i32 mode → i32\n\n& `c` @ rmdir in s path → i32\n\n@ nurl_file_exists s path → i {\n    ? == # i path 0 { ^ 0 } {}\n    : i32 rc ( access path # i32 0 )\n    ^ ? == # i rc 0 1 0\n}\n\n@ nurl_file_del s path → v {\n    ? != 0 # i path { : i32 _ ( remove path ) } {}\n}\n\n// remove(2)'s own answer: 0 on success, -1 with errno set. The `v`\n// spelling above predates the C-runtime purge and is kept because the\n// compiler's preamble still declares that symbol; new code wants the\n// status, so `file_delete` can report WHY a delete failed instead of\n// guessing.\n@ nurl_file_del_rc s path → i {\n    ? == 0 # i path { ^ -1 } {}\n    : i32 rc ( remove path )\n    ^ ? == # i rc 0 0 -1\n}\n\n// 0777, not 0755: mkdir(2) subtracts the process umask, and that is\n// where the answer belongs. Hard-coding 0755 silently overrode a umask\n// of 002 — the group-writable setup every shared build directory uses —\n// and produced directories a colleague could not write into.\n@ nurl_dir_create s path → i {\n    ? == # i path 0 { ^ -1 } {}\n    : i32 rc ( mkdir path # i32 511 )\n    ^ ? == # i rc 0 0 -1\n}\n\n@ nurl_dir_remove s path → i {\n    ? == # i path 0 { ^ -1 } {}\n    : i32 rc ( rmdir path )\n    ^ ? == # i rc 0 0 -1\n}\n\n// mkdir one path component, treating an already-existing directory as\n// success — the whole point of mkdir -p. errno-kind 2 is AlreadyExists.\n@ __dir_create_step s p → !v IoErr {\n    : i rc ( nurl_dir_create p )\n    ? == rc 0 { ^ @ !v IoErr { T 0 } } {}\n    : i k ( errno_kind )\n    ? == k 2 { ^ @ !v IoErr { T 0 } } {}\n    // mkdir can refuse an EXISTING directory with something other than\n    // EEXIST: Windows answers \"C:\" and other roots with EACCES, and\n    // dir_create_all walks an absolute path's every prefix — so without\n    // this, creating ANY absolute path on Windows aborted at its first\n    // component. If the path opens as a directory, it exists; that is\n    // what mkdir -p semantics promise, whatever errno the refusal wore.\n    : i dh ( nurl_dir_list_open p )\n    ? != dh 0 {\n        ( nurl_dir_list_close dh )\n        ^ @ !v IoErr { T 0 }\n    } {}\n    ^ @ !v IoErr { F ( _io_err_of_kind k ) }\n}\n\n// Create `path` and every missing parent directory. An existing leaf or\n// parent is not an error.\n//\n// Only the FINAL component decides the verdict: an intermediate prefix\n// that can neither be created nor opened is not fatal on its own,\n// because a genuinely missing parent makes the next step fail anyway.\n// That is what makes this work under a capability filesystem — on WASI\n// an absolute path's outer prefixes (\"/\", \"/tmp\") live outside every\n// preopen, so mkdir answers ENOENT and opendir cannot prove they exist,\n// even though the leaf's parent is perfectly reachable. Aborting at the\n// first refusal meant `dir_create_all` could not create ANY absolute\n// path on wasm. The first intermediate error is kept and reported if the\n// final step fails too, since it is the one closer to the real cause\n// (an EACCES ancestor beats the leaf's derived ENOENT).\n@ dir_create_all s path → !v IoErr {\n    : i n ( nurl_str_len path )\n    ? == n 0 { ^ @ !v IoErr { F @ IoErr { NotFound } } } {}\n    : *u pb # *u path\n    : String prefix ( string_new )\n    : ~ b have_first F\n    : ~ IoErr first @ IoErr { Other }\n    : ~ i idx 0\n    ~ < idx n {\n        : i c & # i . pb idx 255\n        ? | == c 47 == c 92 {\n            // A separator: create everything accumulated so far. Skip a\n            // leading or doubled separator (empty prefix → nothing yet).\n            ? > ( string_len prefix ) 0 {\n                ?? ( __dir_create_step ( string_data prefix ) ) {\n                    T → {}\n                    F e → { ? have_first {} { = first e = have_first T } }\n                }\n            } {}\n        } {}\n        ( string_push_char prefix c )\n        = idx + idx 1\n    }\n    // Final component (the path itself, with no trailing separator).\n    : !v IoErr last ( __dir_create_step ( string_data prefix ) )\n    ?? last {\n        T → { ^ @ !v IoErr { T 0 } }\n        F e → { ^ @ !v IoErr { F ? have_first first e } }\n    }\n}\n\n// unlink a non-directory entry, mapping errno to IoErr.\n@ __unlink_entry s p → !v IoErr {\n    : i32 rc ( unlink p )\n    ? == rc 0 { ^ @ !v IoErr { T 0 } } {}\n    ^ @ !v IoErr { F ( _io_err_of_kind ( errno_kind ) ) }\n}\n\n// Recursively remove a directory and everything beneath it. Handed a\n// plain file or a symlink, it unlinks that entry directly (rm -rf\n// parity). Returns NotFound when `path` does not exist. On the first\n// failure the walk stops and surfaces that error — entries already\n// removed stay removed (best-effort, like rm -rf).\n@ dir_remove_all s path → !v IoErr {\n    : i t ( nurl_path_type path )\n    ? == t 0 { ^ @ !v IoErr { F @ IoErr { NotFound } } } {}\n    ? != t 2 { ^ ( __unlink_entry path ) } {}\n    : !( Vec String ) IoErr lst ( dir_list path )\n    ?? lst {\n        F e → { ^ @ !v IoErr { F e } }\n        T entries → {\n            : i cnt ( vec_len [String] entries )\n            : ~ i idx 0\n            ~ < idx cnt {\n                ?? ( vec_get [String] entries idx ) {\n                    T name → {\n                        : String full ( path_join path ( string_data name ) )\n                        : s fp ( string_data full )\n                        : !v IoErr r ? == 2 ( nurl_path_type fp ) { ( dir_remove_all fp ) } { ( __unlink_entry fp ) }\n                        ?? r {\n                            T → {}\n                            F e → {\n                                ^ @ !v IoErr { F e }\n                            }\n                        }\n                    }\n                    F → {}\n                }\n                = idx + idx 1\n            }\n        }\n    }\n    // The directory is empty now — remove the directory itself.\n    ^ ( dir_remove path )\n}\n\n// ── Handle-based streaming reads ────────────────────────────────────\n//\n// file_open + file_read_chunk read a file in fixed-size pieces, so an\n// input far larger than RAM is processed without ever being fully\n// resident. For line-oriented streaming use stdlib/std/bufio.nu, which\n// adds a refill buffer and a memchr line scanner on top of the same\n// FILE* handle; this layer is the raw binary primitive.\n//\n// File is a handle on a libc FILE* in an rcbox (stdlib/core/rcbox.nu):\n// every copy is the same open file, and the last owner closes it —\n// file_close closes it now (a later operation on any copy is an error,\n// a later close a no-op). file_open opens \"rb\". `@ File { ( shared_null [FileImpl] ) }` is the\n// null File (no file).\n\n: FileImpl { * v fp }  // the FILE*, 0 once closed\n\n% Drop FileImpl {\n    @ drop FileImpl f → v {\n        ? != 0 # i . f fp { ( nurl_file_close . f fp ) } {}\n    }\n}\n\n: File { ( Shared FileImpl ) ctl }\n\n// Its drop closes something the outside world sees (stdlib/core/marker.nu).\n% Resource File {}\n\n@ File_share File h → File { ^ @ File { ( shared_clone [FileImpl] . h ctl ) } }\n\n@ File_drop sink File h → v {}\n\n// The FILE* behind `f`, lent (0 for a null or closed File).\n@ file_raw File f → s {\n    ? == 0 # i . f ctl { ^ # s 0 } {}\n    ^ # s . ( shared_ptr [FileImpl] . f ctl ) fp\n}\n\n// Whether `f` still has its stream: F for a null File and after\n// file_close (on any copy).\n@ file_is_open File f → b { ^ != 0 # i ( file_raw f ) }\n\n// The null File: no stream (file_is_open F; every operation on it is an\n// error, closing it a no-op). A placeholder for a File opened later.\n@ file_none → File { ^ @ File { ( shared_null [FileImpl] ) } }\n\n// Take over an open FILE* (0 gives the null File): the File's last owner\n// closes it.\n@ file_from_raw s fp → File {\n    ? == 0 # i fp { ^ @ File { ( shared_null [FileImpl] ) } } {}\n    ^ @ File { ( shared_new [FileImpl] @ FileImpl { # *v fp } ) }\n}\n\n@ file_open s path → !File IoErr {\n    : *v h ( nurl_file_open path `rb` )\n    ? == 0 # i h {\n        ^ @ !File IoErr { F ( _io_err_of_kind ( errno_kind ) ) }\n    } {}\n    : s rawp # s h\n    ^ @ !File IoErr { T ( file_from_raw rawp ) }\n}\n\n// Read up to `n` bytes from the handle into an owned Vec[u]. A returned\n// Vec shorter than `n` (down to empty) signals end of file — pair with\n// file_eof for an explicit check. The caller owns the Vec (vec_free [u]).\n@ file_read_chunk File f i n → !( Vec u ) IoErr {\n    ? <= n 0 { ^ @ !( Vec u ) IoErr { T ( vec_new [u] ) } } {}\n    : s hp ( file_raw f )\n    ? == 0 # i hp {\n        ^ @ !( Vec u ) IoErr { F @ IoErr { Other } }\n    } {}\n    : ( Vec u ) out ( vec_with_cap [u] n )\n    : *u dst ( vec_data [u] out )\n    : i got ( nurl_stream_read hp dst n )\n    ? < got 0 {\n        : IoErr e ( _io_err_of_kind ( errno_kind ) )\n        ^ @ !( Vec u ) IoErr { F e }\n    } {}\n    : b _ok ( vec_set_len [u] out got )\n    ^ @ !( Vec u ) IoErr { T out }\n}\n\n@ file_eof File f → b {\n    : s hp ( file_raw f )\n    ^ != 0 ( nurl_file_eof # *v hp )\n}\n\n// Close the file now (optional: the last owner closes it). Every copy of\n// `f` sees it closed.\n@ file_close File f → v {\n    ? == 0 # i . f ctl { ^ } {}\n    : *FileImpl p ( shared_ptr [FileImpl] . f ctl )\n    ? != 0 # i . p fp {\n        ( nurl_file_close . p fp )\n        = . p fp # *v 0\n    } {}\n}\n\n// ── Handle-based streaming writes + seeking ─────────────────────────\n//\n// The write-side dual of file_open/file_read_chunk: create (or append\n// to) a file and emit it in pieces, so an output far larger than RAM —\n// a model download, a log, an export — is written without ever being\n// fully resident. The same File handle then seeks and tells, which is\n// what a resumed download (HTTP Range) or a random-access reader needs.\n//\n//   ( file_create path )          → !File IoErr    \"wb\": truncate + write\n//   ( file_append path )          → !File IoErr    \"ab\": append\n//   ( file_write_chunk f v )      → !v IoErr       binary-safe, all-or-error\n//   ( file_flush f )              → !v IoErr       push libc buffers to the OS\n//   ( file_seek f off whence )    → !i IoErr       → new absolute offset\n//   ( file_tell f )               → !i IoErr\n//   ( file_read_at f off n )      → !( Vec u ) IoErr   seek SET + read\n//\n// Whence values (universal POSIX): FS_SEEK_SET / FS_SEEK_CUR / FS_SEEK_END.\n// Handles from file_open seek too — mixed read/write handles are not\n// offered (libc requires an intervening seek between r/w on \"r+\" streams;\n// open one handle per direction instead).\n\n: i FS_SEEK_SET 0\n: i FS_SEEK_CUR 1\n: i FS_SEEK_END 2\n\n& `c` @ fflush *u h → i32\n\n@ __file_open_mode s path s mode → !File IoErr {\n    : *v h ( nurl_file_open path mode )\n    ? == 0 # i h {\n        ^ @ !File IoErr { F ( _io_err_of_kind ( errno_kind ) ) }\n    } {}\n    : s rawp # s h\n    ^ @ !File IoErr { T ( file_from_raw rawp ) }\n}\n\n// Create (truncate) `path` for writing. Binary mode — see the CRLF\n// rationale on __write_file_pure.\n@ file_create s path → !File IoErr {\n    ^ ( __file_open_mode path `wb` )\n}\n\n// Open `path` for appending; created if absent. Every write lands at\n// the end regardless of seeks (POSIX \"a\" semantics).\n@ file_append s path → !File IoErr {\n    ^ ( __file_open_mode path `ab` )\n}\n\n// Write the whole Vec (binary-safe: NULs included). A short write is\n// an error, never silent truncation.\n@ file_write_chunk File f ( Vec u ) data → !v IoErr {\n    : s hp ( file_raw f )\n    ? == 0 # i hp { ^ @ !v IoErr { F @ IoErr { Other } } } {}\n    : i n ( vec_len [u] data )\n    ? <= n 0 { ^ @ !v IoErr { T 0 } } {}\n    : i wrote ( fwrite # s ( vec_data [u] data ) 1 n hp )\n    ? == wrote n { ^ @ !v IoErr { T 0 } } {}\n    ^ @ !v IoErr { F ( _io_err_of_kind ( errno_kind ) ) }\n}\n\n@ file_flush File f → !v IoErr {\n    : s hp ( file_raw f )\n    ? == 0 # i hp { ^ @ !v IoErr { F @ IoErr { Other } } } {}\n    ? == 0 # i ( fflush hp ) { ^ @ !v IoErr { T 0 } } {}\n    ^ @ !v IoErr { F ( _io_err_of_kind ( errno_kind ) ) }\n}\n\n// Runtime durability barriers (see nurl_file_sync / nurl_dir_sync in\n// stdlib/runtime_core.c — one shim because the call is fsync(2) on unix\n// and _commit on Windows). Both return 0 on success, -1 on failure.\n& `c` @ nurl_file_sync *v h → i\n\n& `c` @ nurl_dir_sync s path → i\n\n& `c` @ nurl_file_truncate s path i len → i\n\n// Flush AND fsync: return only once the bytes are on the storage device.\n//\n// file_flush pushes libc's buffer into the kernel, which survives the\n// process dying but not the machine dying — the page cache is still\n// volatile. Anything that has to survive power loss in a known state\n// (a write-ahead log before acknowledging a write, a data file before a\n// manifest is allowed to name it) needs this instead. It costs a device\n// round-trip, so it is a deliberate call, not the default on every write.\n@ file_sync File f → !v IoErr {\n    : s hp ( file_raw f )\n    ? == 0 # i hp { ^ @ !v IoErr { F @ IoErr { Other } } } {}\n    ? == 0 ( nurl_file_sync # *v hp ) { ^ @ !v IoErr { T 0 } } {}\n    ^ @ !v IoErr { F ( _io_err_of_kind ( errno_kind ) ) }\n}\n\n// Cut `path` down to `len` bytes (or extend it with zeros). The\n// operation an append-only file cannot do without: after a crash,\n// recovery keeps the records up to the last intact one and the file has\n// to END there — otherwise the torn bytes stay at the tail forever and\n// every later append hides behind them. Not available on WASI.\n@ file_truncate s path i len → !v IoErr {\n    ? == 0 ( nurl_file_truncate path len ) { ^ @ !v IoErr { T 0 } } {}\n    ^ @ !v IoErr { F ( _io_err_of_kind ( errno_kind ) ) }\n}\n\n// fsync a directory — what makes a rename durable. A file's contents and\n// the directory entry naming it are separate writes: fsync the file, and\n// a crash can still leave the bytes on disk with no name reaching them.\n// Call this on the parent directory after an atomic publish-by-rename.\n// A no-op on Windows and WASI, which expose no directory handle.\n@ dir_sync s path → !v IoErr {\n    ? == 0 ( nurl_dir_sync path ) { ^ @ !v IoErr { T 0 } } {}\n    ^ @ !v IoErr { F ( _io_err_of_kind ( errno_kind ) ) }\n}\n\n// Seek to `off` relative to `whence`; returns the new ABSOLUTE offset\n// (so `( file_seek f 0 FS_SEEK_END )` doubles as \"how big is this\").\n@ file_seek File f i off i whence → !i IoErr {\n    : s hp ( file_raw f )\n    ? == 0 # i hp { ^ @ !i IoErr { F @ IoErr { Other } } } {}\n    ? == 0 # i ( fseek hp off # i32 whence ) {} {\n        ^ @ !i IoErr { F ( _io_err_of_kind ( errno_kind ) ) }\n    }\n    : i posn ( ftell hp )\n    ? >= posn 0 { ^ @ !i IoErr { T posn } } {}\n    ^ @ !i IoErr { F ( _io_err_of_kind ( errno_kind ) ) }\n}\n\n@ file_tell File f → !i IoErr {\n    : s hp ( file_raw f )\n    ? == 0 # i hp { ^ @ !i IoErr { F @ IoErr { Other } } } {}\n    : i posn ( ftell hp )\n    ? >= posn 0 { ^ @ !i IoErr { T posn } } {}\n    ^ @ !i IoErr { F ( _io_err_of_kind ( errno_kind ) ) }\n}\n\n// Random-access read: up to `n` bytes at absolute offset `off`.\n@ file_read_at File f i off i n → !( Vec u ) IoErr {\n    : !i IoErr sr ( file_seek f off FS_SEEK_SET )\n    ?? sr {\n        T _ → {}\n        F e → { ^ @ !( Vec u ) IoErr { F e } }\n    }\n    ^ ( file_read_chunk f n )\n}\n\n// ── Rename / copy / tempfile (B6) ──────────────────────────────────\n\n// The runtime maps POSIX rename and Windows replace-in-place to one atomic\n// publication boundary. It never unlinks the old destination before success.\n& `c` @ nurl_fs_rename s oldp s newp → i32\n\n& `c` @ nurl_fs_tempdir *u template → i32\n\n// libc mkstemp(3): the template's trailing \"XXXXXX\" is replaced in\n// place with a unique suffix; creates the file 0600 and returns an\n// open fd (or -1). We close the fd and hand back the path — callers\n// write through the normal write_file* helpers.\n& `c` @ mkstemp *u template → i32\n\n// Rename / move `from` to `to`, atomically replacing an existing file on\n// the same filesystem. Cross-device and platform sharing restrictions fail\n// without removing `to`; callers may retain or remove their staged source.\n@ fs_rename s from s to → !v IoErr {\n    ? == # i32 0 ( nurl_fs_rename from to ) { ^ @ !v IoErr { T 0 } } {}\n    ^ @ !v IoErr { F ( _io_err_of_kind ( errno_kind ) ) }\n}\n\n// Copy `src` to `dst` (truncating / creating `dst`). Streams in 64 KiB\n// chunks through libc stdio so arbitrarily large files copy in bounded\n// memory — never slurps the whole file. Does NOT preserve mode/mtime\n// (a plain content copy); layer chmod on top if needed.\n@ fs_copy_file s src s dst → !v IoErr {\n    : s rf # s ( __fs_fopen src `rb` )\n    ? == # i rf 0 { ^ @ !v IoErr { F ( _io_err_of_kind ( errno_kind ) ) } } {}\n    : s wf # s ( __fs_fopen dst `wb` )\n    ? == # i wf 0 {\n        : IoErr e ( _io_err_of_kind ( errno_kind ) )\n        : i32 _rc ( fclose rf )\n        ^ @ !v IoErr { F e }\n    } {}\n    : i chunk 65536\n    : ( Vec u ) buf ( vec_with_cap [u] chunk )\n    : *u dp ( vec_data [u] buf )\n    : ~ b ok T\n    : ~ IoErr error @ IoErr { ReadFailed }\n    : ~ b going T\n    ~ & going ok {\n        : i got ( fread # s dp 1 chunk rf )\n        ? != # i32 0 ( ferror rf ) { = ok F } {}\n        ? & ok > got 0 {\n            : i put ( fwrite # s dp 1 got wf )\n            ? != put got { = ok F = error @ IoErr { WriteFailed } } {}\n        } {}\n        ? < got chunk { = going F } {}\n    }\n    // fclose flushes buffered writes; success from fwrite alone does not\n    // mean a complete copy (e.g. a full device can fail only at close).\n    : i32 wc ( fclose wf )\n    ? & ok != wc # i32 0 { = ok F = error @ IoErr { WriteFailed } } {}\n    : i32 rc ( fclose rf )\n    ? & ok != rc # i32 0 { = ok F = error @ IoErr { ReadFailed } } {}\n    ? ok { ^ @ !v IoErr { T 0 } }\n    { ^ @ !v IoErr { F error } }\n}\n\n// Create a fresh, uniquely-named empty file under `dir` whose name\n// begins with `prefix`, and return its path (owned String). `dir` \"\"\n// defaults to \".\". The file exists (0600) and is empty on return; the\n// caller writes through write_file / append_file. Uses mkstemp, so the\n// name cannot collide with a concurrent caller.\n@ fs_tempfile s dir s prefix → !String IoErr {\n    : s d ? == 0 ( nurl_str_len dir ) `.` dir\n    : String tmpl ( string_with_cap + + ( nurl_str_len d ) ( nurl_str_len prefix ) 10 )\n    ( string_push_str tmpl d )\n    // No second separator after a root `dir`: `//tmp.XXXXXX` is a\n    // different path on some systems and an unopenable one on a\n    // filesystem driver that walks components itself.\n    ? ! ( string_ends_with tmpl `/` ) { ( string_push_char tmpl 47 ) } {}\n    ( string_push_str tmpl prefix )\n    ( string_push_str tmpl `XXXXXX` )\n    : i32 fd ( mkstemp ( string_data tmpl ) )\n    ? < # i fd 0 {\n        ^ @ !String IoErr { F ( _io_err_of_kind ( errno_kind ) ) }\n    } {}\n    : i _c ( close # i fd )\n    ^ @ !String IoErr { T tmpl }  // mkstemp filled XXXXXX in place\n}\n\n// Replace `path` with `content` as a file only its owner can read, for\n// secrets (a registry token, a private key). The content goes into a fresh\n// mkstemp file beside `path` — created 0600, so there is no moment when it\n// is readable by others, as there is between write_file and a following\n// chmod — and is then renamed over `path` (atomic on POSIX; MoveFileEx\n// REPLACE_EXISTING on Windows, where the file takes the directory's ACL —\n// under the user profile, owner-only). A reader sees the old file or the\n// new one, never a torn write. On failure the staged file is removed and\n// `path` is untouched.\n@ write_file_private s path s content → !v IoErr {\n    : String dir ( path_dirname path )\n    : String base ( path_basename path )\n    ( string_push_str base `.` )\n    : String tmp ?? ( fs_tempfile ( string_data dir ) ( string_data base ) ) {\n        T t → t\n        F e → { ^ @ !v IoErr { F e } }\n    }\n    ?? ( write_file ( string_data tmp ) content ) {\n        T _ → {}\n        F e → {\n            : !v IoErr _d ( file_delete ( string_data tmp ) )\n            ^ @ !v IoErr { F e }\n        }\n    }\n    ?? ( fs_rename ( string_data tmp ) path ) {\n        T _ → {}\n        F e → {\n            : !v IoErr _d2 ( file_delete ( string_data tmp ) )\n            ^ @ !v IoErr { F e }\n        }\n    }\n    ^ @ !v IoErr { T 0 }\n}\n\n// Exclusively create a uniquely named directory under `dir` (\"\" means\n// \".\"). POSIX permissions are 0700; Windows applies the parent's ACL.\n// The owned path is returned only after creation succeeds. No temporary\n// file is unlinked to make room, so another process cannot claim the path\n// between allocation and mkdir. Remove the tree with dir_remove_all.\n@ fs_tempdir s dir s prefix → !String IoErr {\n    : s d ? == 0 ( nurl_str_len dir ) `.` dir\n    : String tmpl ( string_with_cap + + ( nurl_str_len d ) ( nurl_str_len prefix ) 10 )\n    ( string_push_str tmpl d )\n    ? ! ( string_ends_with tmpl `/` ) { ( string_push_char tmpl 47 ) } {}\n    ( string_push_str tmpl prefix )\n    ( string_push_str tmpl `XXXXXX` )\n    ? != # i32 0 ( nurl_fs_tempdir ( string_data tmpl ) ) {\n        : IoErr e ( _io_err_of_kind ( errno_kind ) )\n        ^ @ !String IoErr { F e }\n    } {}\n    ^ @ !String IoErr { T tmpl }\n}\n\n// ── Glob (B7) ──────────────────────────────────────────────────────\n//\n// fs_glob expands a shell-style path pattern against the filesystem:\n//   *        any run of characters within one path segment (not '/')\n//   ?        exactly one character (not '/')\n//   [abc]    a character class; [a-z] ranges; [!...] / [^...] negation\n//   **       (as a whole segment) zero or more directory levels\n//\n// A leading-dot name is matched only by a pattern segment that itself\n// starts with '.' (the usual \"glob hides dotfiles\" rule). The pattern\n// may be absolute (\"/etc/*.conf\") or relative (\"src/**/*.nu\"); relative\n// results keep the pattern's own leading form. Returns an OWNED\n// Vec[String] of matching paths, unsorted (sort_by cmp_string for a\n// stable order). A pattern with no metacharacters yields the path iff\n// it exists.\n\n// Match one path SEGMENT `name` against glob segment `pat` (no '/').\n@ __fnmatch_seg s pat s name → b {\n    : i pn ( nurl_str_len pat )\n    : i nn ( nurl_str_len name )\n    // Dotfile rule: '*'/'?'/'[' never match a leading '.'.\n    ? & > nn 0 == ( nurl_str_at name nn 0 ) 46 {\n        ? & > pn 0 != ( nurl_str_at pat pn 0 ) 46 { ^ F } {}\n    } {}\n    ^ ( __fnmatch_at pat 0 pn name 0 nn )\n}\n\n// ── Matching a name against a pattern ───────────────────────────────\n//\n// The matcher `fs_glob` drives, on its own — because the question \"does\n// this name match `*.log`?\" comes up without a directory to walk, and\n// re-implementing `*` / `?` / `[a-z]` per caller is how two of them end\n// up disagreeing about `[!x]`.\n//\n// Two rules, and the difference matters:\n//\n//   ( fs_match `*.log` name )       fnmatch(3): `*` matches ANY name,\n//                                   including one starting with a dot.\n//                                   This is `find -name`'s rule.\n//   ( fs_match_glob `*.log` name )  the shell's rule: `*`, `?` and `[`\n//                                   never match a leading `.`, so a\n//                                   pattern must spell the dot to see a\n//                                   hidden file. This is what `fs_glob`\n//                                   uses per path segment.\n//\n// Neither treats `/` specially — they match one NAME, not a path. A\n// pattern with no metacharacter is a plain equality test.\n\n@ fs_match s pattern s name → b {\n    ^ ( __fnmatch_at pattern 0 ( nurl_str_len pattern ) name 0 ( nurl_str_len name ) )\n}\n\n@ fs_match_glob s pattern s name → b {\n    ^ ( __fnmatch_seg pattern name )\n}\n\n// Recursive matcher over pat[pi..pn) vs name[ni..nn).\n@ __fnmatch_at s pat i pi i pn s name i ni i nn → b {\n    : ~ i p pi\n    : ~ i k ni\n    ~ < p pn {\n        : i pc ( nurl_str_at pat pn p )\n        ? == pc 42 {\n            // '*' — collapse runs, then try every split (no '/' allowed).\n            ~ & < p pn == ( nurl_str_at pat pn p ) 42 { = p + p 1 }\n            ? >= p pn { ^ T } {}  // trailing '*' matches the rest\n            : ~ i j k\n            ~ <= j nn {\n                ? ( __fnmatch_at pat p pn name j nn ) { ^ T } {}\n                = j + j 1\n            }\n            ^ F\n        } {\n            ? >= k nn { ^ F } {}\n            : i nc ( nurl_str_at name nn k )\n            ? == pc 63 {\n                = p + p 1 = k + k 1\n            } {\n                ? == pc 91 {\n                    : i pe ( __fnmatch_class pat + p 1 pn nc )\n                    ? < pe 0 { ^ F } {}  // no match / malformed\n                    = p pe = k + k 1\n                } {\n                    ? == pc nc { = p + p 1 = k + k 1 } { ^ F }\n                }\n            }\n        }\n    }\n    ^ == k nn\n}\n\n// Match a '[...]' class beginning at pat[start] (just past '['), against\n// byte `c`. Returns the index just past the closing ']' on a match, or\n// -1 on no-match / malformed (missing ']').\n@ __fnmatch_class s pat i start i pn i c → i {\n    : ~ i p start\n    : ~ b neg F\n    ? & < p pn | == ( nurl_str_at pat pn p ) 33 == ( nurl_str_at pat pn p ) 94 {\n        = neg T = p + p 1\n    } {}\n    : ~ b hit F\n    : ~ i first p\n    ~ & < p pn ! & > p first == ( nurl_str_at pat pn p ) 93 {\n        : i lo ( nurl_str_at pat pn p )\n        // range \"a-z\" when a '-' with a following non-']' char\n        ? & & < + p 2 pn == ( nurl_str_at pat pn + p 1 ) 45 != ( nurl_str_at pat pn + p 2 ) 93 {\n            : i hi ( nurl_str_at pat pn + p 2 )\n            ? & >= c lo <= c hi { = hit T } {}\n            = p + p 3\n        } {\n            ? == c lo { = hit T } {}\n            = p + p 1\n        }\n    }\n    ? & < p pn == ( nurl_str_at pat pn p ) 93 {} { ^ -1 }  // unterminated class\n    : b matched ? neg ! hit hit\n    ? matched { ^ + p 1 } { ^ -1 }\n}\n\n// True iff `seg` contains a glob metacharacter (* ? [).\n@ __glob_has_meta s seg → b {\n    : i n ( nurl_str_len seg )\n    : ~ i k 0\n    ~ < k n {\n        : i c ( nurl_str_at seg n k )\n        ? | | == c 42 == c 63 == c 91 { ^ T } {}\n        = k + k 1\n    }\n    ^ F\n}\n\n// Join `base` + '/' + `name` (base \"\" → just name; base \"/\" → \"/name\").\n@ _glob_join s base s name → String {\n    : i bn ( nurl_str_len base )\n    : String out ( string_with_cap + + bn ( nurl_str_len name ) 2 )\n    ? > bn 0 {\n        ( string_push_str out base )\n        ? != ( nurl_str_at base bn - bn 1 ) 47 { ( string_push_char out 47 ) } {}\n    } {}\n    ( string_push_str out name )\n    ^ out\n}\n\n// Append every entry of directory `base` whose name matches `seg` to\n// `acc`, as joined paths. `base` \"\" means the current directory. When\n// `dirs_only` is true, keep only entries that are themselves\n// directories (used for intermediate path segments).\n@ __glob_expand_seg ( Vec String ) acc s base s seg b dirs_only → v {\n    : s listdir ? == 0 ( nurl_str_len base ) `.` base\n    : !( Vec String ) IoErr lr ( dir_list listdir )\n    ?? lr {\n        T entries → {\n            : i ne ( vec_len [String] entries )\n            : ~ i k 0\n            ~ < k ne {\n                : ?String eo ( vec_get [String] entries k )\n                ?? eo {\n                    T name → {\n                        ? ( __fnmatch_seg seg ( string_data name ) ) {\n                            : String full ( _glob_join base ( string_data name ) )\n                            ? dirs_only {\n                                ? == 2 ( nurl_path_type_follow ( string_data full ) )\n                                { ( vec_push [String] acc full ) }\n                                {}\n                            } {\n                                ( vec_push [String] acc full )\n                            }\n                        } {}\n                    }\n                    F _ → {}\n                }\n                = k + k 1\n            }\n        }\n        F _ → {}  // unreadable dir → no matches from it\n    }\n}\n\n// Collect `base` itself plus every descendant directory of `base`\n// (recursive), for '**'. Pushes owned paths into `acc`.\n@ __glob_walk_dirs ( Vec String ) acc s base → v {\n    : s self ? == 0 ( nurl_str_len base ) `.` base\n    ( vec_push [String] acc ( string_from base ) )\n    : !( Vec String ) IoErr lr ( dir_list self )\n    ?? lr {\n        T entries → {\n            : i ne ( vec_len [String] entries )\n            : ~ i k 0\n            ~ < k ne {\n                : ?String eo ( vec_get [String] entries k )\n                ?? eo {\n                    T name → {\n                        // '**' does not descend into dotfile dirs.\n                        ? != ( nurl_str_get ( string_data name ) 0 ) 46 {\n                            : String full ( _glob_join base ( string_data name ) )\n                            ? == 2 ( nurl_path_type_follow ( string_data full ) )\n                            { ( __glob_walk_dirs acc ( string_data full ) ) } {}\n                        } {}\n                    }\n                    F _ → {}\n                }\n                = k + k 1\n            }\n        }\n        F _ → {}\n    }\n}\n\n// Split `pattern` into '/'-separated segments (owned Strings). A leading\n// '/' yields a leading empty segment so the driver can seed an absolute\n// base. Trailing/duplicate slashes collapse to empty segments which the\n// driver skips.\n@ __glob_split s pattern → ( Vec String ) {\n    : ( Vec String ) out ( vec_new [String] )\n    : i n ( nurl_str_len pattern )\n    : ~ i start 0\n    : ~ i k 0\n    ~ <= k n {\n        ? | == k n == ( nurl_str_at pattern n k ) 47 {\n            : String seg ( string_with_cap + - k start 1 )\n            : ~ i j start\n            ~ < j k { ( string_push_char seg ( nurl_str_at pattern n j ) ) = j + j 1 }\n            ( vec_push [String] out seg )\n            = start + k 1\n        } {}\n        = k + k 1\n    }\n    ^ out\n}\n\n@ fs_glob s pattern → !( Vec String ) IoErr {\n    : ( Vec String ) segs ( __glob_split pattern )\n    : i nseg ( vec_len [String] segs )\n    // Frontier of currently-matched base paths. Seed: \"\" (relative) or\n    // \"/\" when the pattern is absolute (first segment empty).\n    : ~ ( Vec String ) frontier ( vec_new [String] )\n    : ~ i first_seg 0\n    : b absolute ? > nseg 0 {\n        : ?String s0 ( vec_get [String] segs 0 )\n        ?? s0 { T s → == 0 ( nurl_str_len ( string_data s ) ) F _ → F }\n    } F\n    ? absolute {\n        ( vec_push [String] frontier ( string_from `/` ) )\n        = first_seg 1\n    } {\n        ( vec_push [String] frontier ( string_from `` ) )\n    }\n    : ~ i si first_seg\n    ~ < si nseg {\n        : ?String seo ( vec_get [String] segs si )\n        : s seg ?? seo { T s → ( string_data s ) F _ → `` }\n        : b is_last == si - nseg 1\n        // skip empty segments (collapsed slashes)\n        ? == 0 ( nurl_str_len seg ) { = si + si 1 } {\n            : ( Vec String ) next ( vec_new [String] )\n            : i nf ( vec_len [String] frontier )\n            : ~ i fi 0\n            ~ < fi nf {\n                : ?String bo ( vec_get [String] frontier fi )\n                : s base ?? bo { T b → ( string_data b ) F _ → `` }\n                ? & == ( nurl_str_get seg 0 ) 42 & == ( nurl_str_get seg 1 ) 42 == ( nurl_str_len seg ) 2 {\n                    // '**' — base + all descendant dirs\n                    ( __glob_walk_dirs next base )\n                } {\n                    ? ( __glob_has_meta seg ) {\n                        ( __glob_expand_seg next base seg ! is_last )\n                    } {\n                        // literal segment: extend if the path exists.\n                        // The two tests ask different questions, so they\n                        // use different classifiers. A FINAL segment only\n                        // has to exist as an entry — unlinked, so a\n                        // dangling symlink still matches, as glob(3) has\n                        // it. An INTERMEDIATE segment has to be something\n                        // we can descend, which follows links.\n                        : String full ( _glob_join base seg )\n                        : i ty ( nurl_path_type ( string_data full ) )\n                        : i tyf ( nurl_path_type_follow ( string_data full ) )\n                        ? | & is_last > ty 0 & ! is_last == tyf 2\n                        { ( vec_push [String] next full ) }\n                        {}\n                    }\n                }\n                = fi + fi 1\n            }\n            = frontier next\n            = si + si 1\n        }\n    }\n    ^ @ !( Vec String ) IoErr { T frontier }\n}\n","bytes":62735}