// node:fs and node:fs/promises over the kernel's syscalls. Sync forms block on the // mailbox. Callback forms are async syscalls whose results run as poll-phase loop // callbacks (ctx.submit), which is where libuv delivers them, and fs.promises settles // from those same callbacks. Each composite operation (readFile, realpath, ...) is // written once against an `Io` and runs sync or async depending on which Io it gets. import type { DirentRecord, FsResult, OpenFlags, StatRecord, SyscallSpec, WatchEvent } from "../shared/protocol"; import { uvError } from "../shared/nodeerr"; import { capture, runInFrame } from "./async_hooks"; import { Buffer } from "./buffer"; import { invalidArgType } from "./errors"; import { EventEmitter, type EventEmitterConstructor } from "./events"; import type { RequireContext } from "./modules"; import { matchesGlob } from "./path"; import type { Timeout } from "./timers"; import { refusedClass, unsupported } from "./refuse"; import { join } from "./resolver"; import { StdinStream } from "./stdin"; import { Readable, Writable } from "./stream"; import { nextTick } from "./ticks"; type Op = keyof SyscallSpec; type Args = SyscallSpec[K]["args"]; type Ret = SyscallSpec[K]["ret"]; type Done = (err: Error | null, val?: T) => void; type PathLike = string | URL | Uint8Array; /** One syscall, reported under the Node syscall name and path (and dest) the guest * called with. */ interface Io { (op: K, args: Args, syscall: string, path: string | undefined, done: Done>, dest?: string): void; } // Linux values: process.platform is "linux", so these are what a package expects. export const constants = Object.freeze({ UV_FS_SYMLINK_DIR: 1, UV_FS_SYMLINK_JUNCTION: 2, O_RDONLY: 0, O_WRONLY: 1, O_RDWR: 2, UV_DIRENT_UNKNOWN: 0, UV_DIRENT_FILE: 1, UV_DIRENT_DIR: 2, UV_DIRENT_LINK: 3, UV_DIRENT_FIFO: 4, UV_DIRENT_SOCKET: 5, UV_DIRENT_CHAR: 6, UV_DIRENT_BLOCK: 7, S_IFMT: 0o170000, S_IFREG: 0o100000, S_IFDIR: 0o40000, S_IFCHR: 0o20000, S_IFBLK: 0o60000, S_IFIFO: 0o10000, S_IFLNK: 0o120000, S_IFSOCK: 0o140000, O_CREAT: 0o100, O_EXCL: 0o200, UV_FS_O_FILEMAP: 0, O_NOCTTY: 0o400, O_TRUNC: 0o1000, O_APPEND: 0o2000, O_DIRECTORY: 0o200000, O_NOATIME: 0o1000000, O_NOFOLLOW: 0o400000, O_SYNC: 0o4010000, O_DSYNC: 0o10000, O_DIRECT: 0o40000, O_NONBLOCK: 0o4000, S_IRWXU: 0o700, S_IRUSR: 0o400, S_IWUSR: 0o200, S_IXUSR: 0o100, S_IRWXG: 0o70, S_IRGRP: 0o40, S_IWGRP: 0o20, S_IXGRP: 0o10, S_IRWXO: 0o7, S_IROTH: 0o4, S_IWOTH: 0o2, S_IXOTH: 0o1, F_OK: 0, R_OK: 4, W_OK: 2, X_OK: 1, UV_FS_COPYFILE_EXCL: 1, COPYFILE_EXCL: 1, UV_FS_COPYFILE_FICLONE: 2, COPYFILE_FICLONE: 2, UV_FS_COPYFILE_FICLONE_FORCE: 4, COPYFILE_FICLONE_FORCE: 4, }); const FLAG_STRINGS: Record = (() => { const f = (read: boolean, write: boolean, create: boolean, trunc: boolean, append: boolean, excl: boolean): OpenFlags => ({ read, write, create, trunc, append, excl }); const table: Record = { "r": f(true, false, false, false, false, false), "r+": f(true, true, false, false, false, false), "w": f(false, true, true, true, false, false), "wx": f(false, true, true, true, false, true), "w+": f(true, true, true, true, false, false), "wx+": f(true, true, true, true, false, true), "a": f(false, true, true, false, true, false), "ax": f(false, true, true, false, true, true), "a+": f(true, true, true, false, true, false), "ax+": f(true, true, true, false, true, true), }; Object.assign(table, { rs: table["r"], "rs+": table["r+"], sr: table["r"], "sr+": table["r+"], xw: table["wx"], "xw+": table["wx+"], xa: table["ax"], "xa+": table["ax+"], as: table["a"], sa: table["a"], "as+": table["a+"], "sa+": table["a+"] }); return table; })(); function toPathString(p: unknown, name = "path"): string { if (typeof p === "string") return p; if (p instanceof URL) { if (p.protocol !== "file:") throw Object.assign(new TypeError("The URL must be of scheme file"), { code: "ERR_INVALID_URL_SCHEME" }); return decodeURIComponent(p.pathname); } if (p instanceof Uint8Array) return Buffer.from(p).toString(); throw invalidArgType(name, "of type string or an instance of Buffer or URL", p); } function parseFlags(flags: unknown, fallback: string): OpenFlags { if (flags === undefined || flags === null) return FLAG_STRINGS[fallback]!; if (typeof flags === "number") { const acc = flags & 3; return { read: acc === constants.O_RDONLY || acc === constants.O_RDWR, write: acc === constants.O_WRONLY || acc === constants.O_RDWR, create: (flags & constants.O_CREAT) !== 0, trunc: (flags & constants.O_TRUNC) !== 0, append: (flags & constants.O_APPEND) !== 0, excl: (flags & constants.O_EXCL) !== 0, }; } const parsed = typeof flags === "string" ? FLAG_STRINGS[flags] : undefined; if (!parsed) { throw Object.assign(new TypeError(`The argument 'flags' is invalid. Received ${typeof flags === "string" ? `'${flags}'` : String(flags)}`), { code: "ERR_INVALID_ARG_VALUE" }); } return parsed; } function callbackArg unknown>(cb: unknown): T { if (typeof cb !== "function") throw invalidArgType("cb", "of type function", cb); return cb as T; } function encodingOf(options: unknown): string | undefined { if (typeof options === "string") return options; if (options && typeof options === "object") return (options as { encoding?: string | null }).encoding ?? undefined; return undefined; } // ---- Stats and Dirent ---- // Fields the VFS does not track. One device, one link per name, root-owned, and // 512-byte blocks rounded up from the size, as a 4 KiB-block filesystem reports them. const STAT_DEV = 1; const STAT_NLINK = 1; const STAT_UID = 0; const STAT_GID = 0; const STAT_RDEV = 0; const STAT_BLKSIZE = 4096; class StatsBase { declare mode: number | bigint; #type(): number { return Number(this.mode) & constants.S_IFMT; } isDirectory(): boolean { return this.#type() === constants.S_IFDIR; } isFile(): boolean { return this.#type() === constants.S_IFREG; } isBlockDevice(): boolean { return this.#type() === constants.S_IFBLK; } isCharacterDevice(): boolean { return this.#type() === constants.S_IFCHR; } isSymbolicLink(): boolean { return this.#type() === constants.S_IFLNK; } isFIFO(): boolean { return this.#type() === constants.S_IFIFO; } isSocket(): boolean { return this.#type() === constants.S_IFSOCK; } } const DATE_FIELDS = ["atime", "mtime", "ctime", "birthtime"] as const; /** Node's fs.Stats: the numeric fields are own properties in Node's order; the Date * forms are prototype accessors computed from the *Ms fields on first read. */ export class Stats extends StatsBase { declare mode: number; declare dev: number; declare nlink: number; declare uid: number; declare gid: number; declare rdev: number; declare blksize: number; declare ino: number; declare size: number; declare blocks: number; declare atimeMs: number; declare mtimeMs: number; declare ctimeMs: number; declare birthtimeMs: number; declare atime: Date; declare mtime: Date; declare ctime: Date; declare birthtime: Date; constructor(dev: number, mode: number, nlink: number, uid: number, gid: number, rdev: number, blksize: number, ino: number, size: number, blocks: number, atimeMs: number, mtimeMs: number, ctimeMs: number, birthtimeMs: number) { super(); Object.assign(this, { dev, mode, nlink, uid, gid, rdev, blksize, ino, size, blocks, atimeMs, mtimeMs, ctimeMs, birthtimeMs }); } } /** Node's BigIntStats, what `{ bigint: true }` answers: every numeric field a bigint, * each time also in nanoseconds (*Ns), its *Ms the whole milliseconds of that. */ class BigIntStats extends StatsBase { declare mode: bigint; declare atimeMs: bigint; declare mtimeMs: bigint; declare ctimeMs: bigint; declare birthtimeMs: bigint; declare atime: Date; declare mtime: Date; declare ctime: Date; declare birthtime: Date; constructor(fields: Record) { super(); Object.assign(this, fields); } } for (const Class of [Stats, BigIntStats]) { for (const field of DATE_FIELDS) { const cache = new WeakMap(); Object.defineProperty(Class.prototype, field, { configurable: true, enumerable: false, get(this: Record) { let date = cache.get(this); if (!date) cache.set(this, (date = new Date(Math.round(Number(this[`${field}Ms`]))))); return date; }, set(this: object, value: unknown) { Object.defineProperty(this, field, { value, writable: true, enumerable: true, configurable: true }); }, }); } } function toStats(r: StatRecord): Stats { const t = r.mtimeMs; // the VFS tracks one time; atime, ctime and birthtime report it return new Stats(STAT_DEV, r.mode, STAT_NLINK, STAT_UID, STAT_GID, STAT_RDEV, STAT_BLKSIZE, r.ino, r.size, Math.ceil(r.size / 512), t, t, t, t); } function toBigIntStats(r: StatRecord): BigIntStats { const ns = BigInt(Math.round(r.mtimeMs * 1e6)); const ms = ns / 1_000_000n; const n = (v: number): bigint => BigInt(v); return new BigIntStats({ dev: n(STAT_DEV), mode: n(r.mode), nlink: n(STAT_NLINK), uid: n(STAT_UID), gid: n(STAT_GID), rdev: n(STAT_RDEV), blksize: n(STAT_BLKSIZE), ino: n(r.ino), size: n(r.size), blocks: n(Math.ceil(r.size / 512)), atimeMs: ms, mtimeMs: ms, ctimeMs: ms, birthtimeMs: ms, atimeNs: ns, mtimeNs: ns, ctimeNs: ns, birthtimeNs: ns, }); } type AnyStats = Stats | BigIntStats; const bigintOf = (options: unknown): boolean => Boolean(options && typeof options === "object" && (options as { bigint?: unknown }).bigint); const statsAs = (bigint: boolean) => (r: StatRecord): AnyStats => (bigint ? toBigIntStats(r) : toStats(r)); const kType = Symbol("type"); export class Dirent { declare name: string; declare parentPath: string; declare [kType]: number; constructor(name: string, type: number, parentPath: string) { this.name = name; this.parentPath = parentPath; this[kType] = type; } isDirectory(): boolean { return this[kType] === constants.UV_DIRENT_DIR; } isFile(): boolean { return this[kType] === constants.UV_DIRENT_FILE; } isBlockDevice(): boolean { return this[kType] === constants.UV_DIRENT_BLOCK; } isCharacterDevice(): boolean { return this[kType] === constants.UV_DIRENT_CHAR; } isSymbolicLink(): boolean { return this[kType] === constants.UV_DIRENT_LINK; } isFIFO(): boolean { return this[kType] === constants.UV_DIRENT_FIFO; } isSocket(): boolean { return this[kType] === constants.UV_DIRENT_SOCKET; } } const DIRENT_TYPES: Record = { file: constants.UV_DIRENT_FILE, dir: constants.UV_DIRENT_DIR, symlink: constants.UV_DIRENT_LINK, }; // ---- the module ---- export function makeFs(ctx: RequireContext) { const deliver = (r: FsResult>, syscall: string, path: string | undefined, done: Done>, dest?: string): void => { if (r.ok) done(null, r.val); else done(uvError({ code: r.err.code, syscall, path, dest })); }; const syncIo: Io = (op, args, syscall, path, done, dest) => deliver(ctx.trySync(op, args), syscall, path, done, dest); const asyncIo: Io = (op, args, syscall, path, done, dest) => ctx.submit(op, args, (r) => deliver(r, syscall, path, done, dest)); /** Run a composite op on the sync Io: its continuation runs before this returns. */ function runSync(body: (io: Io, done: Done) => void): T { let result: { err: Error | null; val?: T } | null = null; body(syncIo, (err, val) => { result = { err, val }; }); const r = result as { err: Error | null; val?: T } | null; if (!r) throw new Error("nib: sync fs operation did not complete"); if (r.err) throw r.err; return r.val as T; } const promised = (body: (io: Io, done: Done) => void): Promise => new Promise((resolve, reject) => body(asyncIo, (err, val) => (err ? reject(err) : resolve(val as T)))); // Not normalized: the kernel walks "..", which after a symlink is not the lexical parent. const abs = (p: string): string => (p.startsWith("/") ? p : `${ctx.cwd}/${p}`); const warned = new Set(); const warnOnce = (code: string, message: string): void => { if (warned.has(code)) return; warned.add(code); ctx.process.emitWarning(message, "DeprecationWarning", code); }; // ---- composite operations, each written once ---- const statOp = (path: string, follow: boolean, bigint = false) => (io: Io, done: Done): void => io("stat", { path: abs(path), follow }, follow ? "stat" : "lstat", path, (err, r) => (err ? done(err) : done(null, statsAs(bigint)(r!)))); const accessOp = (path: string) => (io: Io, done: Done): void => io("stat", { path: abs(path), follow: true }, "access", path, (err) => done(err)); const readdirOp = (path: string, withFileTypes: boolean) => (io: Io, done: Done): void => io("readdir", { path: abs(path) }, "scandir", path, (err, entries) => { if (err) return done(err); done(null, withFileTypes ? entries!.map((e) => new Dirent(e.name, DIRENT_TYPES[e.kind], path)) : entries!.map((e) => e.name)); }); const openOp = (path: string, flags: OpenFlags, mode: number) => (io: Io, done: Done): void => io("open", { path: abs(path), flags, mode }, "open", path, (err, r) => (err ? done(err) : done(null, r!.fd))); /** read(2) into `buffer` at `offset`; resolves with the byte count. */ const readOp = (fd: number, buffer: Uint8Array, offset: number, length: number, position: number | null) => (io: Io, done: Done): void => { if (length === 0) return done(null, 0); io("read", { fd, len: length, pos: position }, "read", undefined, (err, r) => { if (err) return done(err); buffer.set(r!.bytes, offset); done(null, r!.bytes.length); }); }; const closeOp = (fd: number) => (io: Io, done: Done): void => io("close", { fd }, "close", undefined, (err) => done(err)); const fstatOp = (fd: number, bigint = false) => (io: Io, done: Done): void => io("fstat", { fd }, "fstat", undefined, (err, r) => (err ? done(err) : done(null, statsAs(bigint)(r!)))); /** Node's readFile: open, read to EOF in 64 KiB slices, close. */ const readFileOp = (path: string, flags: OpenFlags) => (io: Io, done: Done): void => { openOp(path, flags, 0o666)(io, (err, fd) => { if (err) return done(err); readFdToEnd(fd!)(io, (readErr, buf) => closeOp(fd!)(io, (closeErr) => done(readErr ?? closeErr, buf))); }); }; const readFdToEnd = (fd: number) => (io: Io, done: Done): void => { const chunks: Uint8Array[] = []; const next = (): void => io("read", { fd, len: 1 << 16, pos: null }, "read", undefined, (err, r) => { if (err) return done(err); if (r!.bytes.length) chunks.push(r!.bytes); if (r!.eof || r!.bytes.length === 0) return done(null, Buffer.concat(chunks)); next(); }); next(); }; /** native is realpath(3), which walks ".." physically. The JS form is Node's: it * resolves the path lexically first, then lstats each component, and a link (once * stat shows its target exists) restarts the walk from its target, resolved * lexically against the link's directory. */ const realpathOp = (path: string, native: boolean) => (io: Io, done: Done): void => { if (native) return io("realpath", { path: abs(path) }, "realpath", path, (err, r) => (err ? done(err) : done(null, r!.path))); const walk = (dir: string, rest: string[]): void => { if (rest.length === 0) return done(null, dir); const next = join(dir, rest[0]!); io("stat", { path: next, follow: false }, "lstat", next, (err, st) => { if (err) return done(err); if (st!.kind !== "symlink") return walk(next, rest.slice(1)); io("stat", { path: next, follow: true }, "stat", next, (statErr) => { if (statErr) return done(statErr); io("readlink", { path: next }, "readlink", next, (linkErr, r) => { if (linkErr) return done(linkErr); const target = r!.target.startsWith("/") ? r!.target : join(dir, r!.target); walk("/", [...target.split("/").filter(Boolean), ...rest.slice(1)]); }); }); }); }; walk("/", ctx.resolvePath(path).split("/").filter(Boolean)); }; const symlinkOp = (target: string, path: string) => (io: Io, done: Done): void => io("symlink", { target, path: abs(path) }, "symlink", target, (err) => done(err), path); const readlinkOp = (path: string, encoding: string | undefined) => (io: Io, done: Done): void => io("readlink", { path: abs(path) }, "readlink", path, (err, r) => (err ? done(err) : done(null, decodeAs(Buffer.from(r!.target), encoding ?? "utf8")))); /** write(2) until every byte is written; `pos` null writes at the fd's offset. */ const writeAllOp = (fd: number, bytes: Uint8Array, pos: number | null) => (io: Io, done: Done): void => { let off = 0; const next = (): void => { if (off >= bytes.length) return done(null, off); io("write", { fd, data: bytes.subarray(off), pos: pos === null ? null : pos + off }, "write", undefined, (err, r) => { if (err) return done(err); off += r!.written; next(); }); }; next(); }; /** Node's writeFile: open with `flags`, write everything, close. An fd is written at * its current offset and left open. */ const writeFileOp = (target: string | number, bytes: Uint8Array, flags: OpenFlags, mode: number) => (io: Io, done: Done): void => { if (typeof target === "number") return writeAllOp(target, bytes, null)(io, (err) => done(err)); openOp(target, flags, mode)(io, (err, fd) => { if (err) return done(err); writeAllOp(fd!, bytes, null)(io, (writeErr) => closeOp(fd!)(io, (closeErr) => done(writeErr ?? closeErr))); }); }; /** mkdir; recursive answers the first directory it created, in the caller's own * spelling of the path, as Node's mkdirp does (undefined when none was). */ const mkdirOp = (path: string, recursive: boolean, mode: number) => (io: Io, done: Done): void => io("mkdir", { path: abs(path), mode, recursive }, "mkdir", path, (err, r) => { if (err) return done(err); if (!recursive || r!.path === null) return done(null, undefined); const below = abs(path).split("/").filter(Boolean).length - r!.path.split("/").filter(Boolean).length; let first = path.replace(/(.)\/+$/, "$1"); for (let i = 0; i < below; i++) first = first.slice(0, Math.max(first.lastIndexOf("/"), 0)) || "/"; done(null, first); }); const unlinkOp = (path: string) => (io: Io, done: Done): void => io("unlink", { path: abs(path) }, "unlink", path, (err) => done(err)); const rmdirOp = (path: string, recursive: boolean) => (io: Io, done: Done): void => io("rmdir", { path: abs(path), recursive }, "rmdir", path, (err) => done(err)); const renameOp = (from: string, to: string) => (io: Io, done: Done): void => io("rename", { from: abs(from), to: abs(to) }, "rename", from, (err) => done(err), to); /** Node's rm: lstat, then unlink a file or (recursive) remove a directory tree. A * directory without `recursive` is ERR_FS_EISDIR; `force` ignores a missing path. */ const rmOp = (path: string, recursive: boolean, force: boolean) => (io: Io, done: Done): void => io("stat", { path: abs(path), follow: false }, "lstat", path, (err, st) => { if (err) return done(force && (err as { code?: string }).code === "ENOENT" ? null : err); if (st!.kind !== "dir") return unlinkOp(path)(io, done); if (!recursive) return done(rmIsDir(path)); rmdirOp(path, true)(io, done); }); /** copyfile: every step reports as Node's single copyfile syscall, src -> dest. */ const copyFileOp = (src: string, dest: string, mode: number) => (io: Io, done: Done): void => { const excl = (mode & constants.COPYFILE_EXCL) !== 0; io("open", { path: abs(src), flags: FLAG_STRINGS["r"]!, mode: 0 }, "copyfile", src, (err, r) => { if (err) return done(err); const from = r!.fd; readFdToEnd(from)(io, (readErr, bytes) => closeOp(from)(io, () => { if (readErr) return done(readErr); io("open", { path: abs(dest), flags: FLAG_STRINGS[excl ? "wx" : "w"]!, mode: 0o666 }, "copyfile", src, (openErr, w) => { if (openErr) return done(openErr); writeAllOp(w!.fd, bytes!, null)(io, (writeErr) => closeOp(w!.fd)(io, (closeErr) => done(writeErr ?? closeErr))); }, dest); })); }, dest); }; /** mkdtemp: the prefix plus six random [A-Za-z0-9] characters, retried on EEXIST. */ const mkdtempOp = (prefix: string) => (io: Io, done: Done): void => { const attempt = (left: number): void => { const path = prefix + randomSuffix(); io("mkdir", { path: abs(path), mode: 0o700, recursive: false }, "mkdtemp", `${prefix}XXXXXX`, (err) => { if (err && (err as { code?: string }).code === "EEXIST" && left > 0) return attempt(left - 1); done(err, path); }); }; attempt(100); }; const ftruncateOp = (fd: number, len: number) => (io: Io, done: Done): void => io("ftruncate", { fd, len }, "ftruncate", undefined, (err) => done(err)); /** Node's truncate(path, len): open "r+" (the file must exist), ftruncate, close. */ const truncateOp = (path: string, len: number) => (io: Io, done: Done): void => openOp(path, FLAG_STRINGS["r+"]!, 0o666)(io, (err, fd) => { if (err) return done(err); ftruncateOp(fd!, len)(io, (truncErr) => closeOp(fd!)(io, (closeErr) => done(truncErr ?? closeErr))); }); /** utimes (follow) and lutimes (don't); atime has nowhere to land (one stored time), * so it's accepted and dropped. */ const utimesPathOp = (path: string, follow: boolean, mtimeMs: number) => (io: Io, done: Done): void => io("utimes", { path: abs(path), follow, mtimeMs }, follow ? "utime" : "lutime", path, (err) => done(err)); const futimesOp = (fd: number, mtimeMs: number) => (io: Io, done: Done): void => io("futimes", { fd, mtimeMs }, "futime", undefined, (err) => done(err)); /** fsync/fdatasync: the VFS applies a write to its in-memory data the moment write() * returns — there is no separate buffer to flush — so these are a no-op once `fd` * is checked to be real (fstat already needs exactly that check). */ const syncOp = (fd: number, syscall: "fsync" | "fdatasync") => (io: Io, done: Done): void => io("fstat", { fd }, syscall, undefined, (err) => done(err)); /** readv/writev: our VFS has no vectored syscall, so each buffer is its own read or * write in order, position advancing by what actually transferred; a 0-byte read * (EOF) stops early, as a real readv(2) would. */ const readvOp = (fd: number, buffers: Uint8Array[], position: number | null) => (io: Io, done: Done): void => { let total = 0; let pos = position; const next = (i: number): void => { if (i >= buffers.length) return done(null, total); readOp(fd, buffers[i]!, 0, buffers[i]!.length, pos)(io, (err, n) => { if (err) return done(err); total += n!; if (pos !== null) pos += n!; if (n === 0) return done(null, total); next(i + 1); }); }; next(0); }; const writevOp = (fd: number, buffers: Uint8Array[], position: number | null) => (io: Io, done: Done): void => { let total = 0; let pos = position; const next = (i: number): void => { if (i >= buffers.length) return done(null, total); writeAllOp(fd, buffers[i]!, pos)(io, (err, n) => { if (err) return done(err); total += n!; if (pos !== null) pos += n!; next(i + 1); }); }; next(0); }; /** chmod/chown and their f- and l- forms: modes and owners aren't stored (stat * reports fixed values), so these are a no-op once the target is checked to exist * — npm's bin-links calls chmod on every install, and refusing it would break * installs. */ const noopOnPathOp = (path: string, syscall: string, follow: boolean) => (io: Io, done: Done): void => io("stat", { path: abs(path), follow }, syscall, path, (err) => done(err)); const noopOnFdOp = (fd: number, syscall: string) => (io: Io, done: Done): void => io("fstat", { fd }, syscall, undefined, (err) => done(err)); /** link: the VFS has no inodes shared across directory entries — unlink only drops a * name, it never affects a sibling link's — so a real hard link can't be made. */ const linkOp = (): Error => unsupported("fs", "link()", "the VFS has no hard links: every entry has exactly one parent"); /** The VFS is an in-memory tree with no backing store to report capacity for — not * even OPFS yet — and StorageManager.estimate() is the page's own quota, a real * number that would describe the wrong thing here. Refuse rather than fake one. */ const statfsOp = (): Error => unsupported("fs", "statfs()", "the VFS has no backing store to report capacity for"); /** A closed Dir's read: Node's ERR_DIR_CLOSED. */ function dirClosed(): Error { return Object.assign(new Error("Directory handle was closed"), { code: "ERR_DIR_CLOSED" }); } /** Dir entries are fetched once, up front (readdir), and served one at a time — * the VFS has no cursor-based scandir to stream lazily. */ class Dir { #entries: Dirent[] | null; readonly path: string; constructor(path: string, entries: Dirent[]) { this.path = path; this.#entries = entries; } read(cb?: unknown): Promise | void { if (typeof cb === "function") { const done = callbackArg>(cb); nextTick(() => { if (!this.#entries) return done(dirClosed()); done(null, this.#entries.shift() ?? null); }); return; } return this.#entries ? Promise.resolve(this.#entries.shift() ?? null) : Promise.reject(dirClosed()); } readSync(): Dirent | null { if (!this.#entries) throw dirClosed(); return this.#entries.shift() ?? null; } close(cb?: unknown): Promise | void { this.#entries = null; if (typeof cb === "function") { nextTick(() => callbackArg>(cb)(null)); return; } return Promise.resolve(); } closeSync(): void { this.#entries = null; } async* entries(): AsyncGenerator { for (;;) { const d = await (this.read() as Promise); if (d === null) return; yield d; } } [Symbol.asyncIterator](): AsyncGenerator { return this.entries(); } } /** opendir: like readdir but under the "opendir" syscall name in its errors. */ const opendirOp = (path: string) => (io: Io, done: Done): void => io("readdir", { path: abs(path) }, "opendir", path, (err, entries) => { if (err) return done(err); done(null, new Dir(path, entries!.map((e) => new Dirent(e.name, DIRENT_TYPES[e.kind], path)))); }); /** Walk `cwd` recursively, matching each entry's cwd-relative path against any of * `patterns`; a directory `exclude` rejects also prunes the walk below it, as * Node's glob does. The root itself is never a candidate. */ interface GlobOpts { exclude: (name: string, rel: string) => boolean; withFileTypes: boolean } const globOp = (patterns: string[], cwd: string, opts: GlobOpts) => (io: Io, done: Done>): void => { const out: Array = []; const visit = (dirAbs: string, rel: string, after: () => void): void => { io("readdir", { path: dirAbs }, "scandir", dirAbs, (err, entries) => { if (err) return done(err); const walk = (i: number): void => { if (i >= entries!.length) return after(); const e = entries![i]!; const entryRel = rel ? `${rel}/${e.name}` : e.name; if (opts.exclude(e.name, entryRel)) return walk(i + 1); if (patterns.some((p) => matchesGlob(entryRel, p))) { out.push(opts.withFileTypes ? new Dirent(e.name, DIRENT_TYPES[e.kind], dirAbs) : entryRel); } if (e.kind === "dir") return visit(join(dirAbs, e.name), entryRel, () => walk(i + 1)); walk(i + 1); }; walk(0); }); }; visit(cwd, "", () => done(null, out)); }; /** cp: lstat src, then copy a file (bytes via copyFile), recreate a symlink (its raw * target, not resolved against the new location), or (recursive only) mkdir + copy * every child. `force: false` skips an existing dest unless `errorOnExist`. */ interface CpOpts { recursive: boolean; force: boolean; errorOnExist: boolean; dereference: boolean; preserveTimestamps: boolean; mode: number; filter?: (src: string, dest: string) => boolean } const cpOp = (src: string, dest: string, opts: CpOpts) => (io: Io, done: Done): void => { const srcAbs = abs(src); const destAbs = abs(dest); if (destAbs === srcAbs || destAbs.startsWith(`${srcAbs}/`)) return done(cpEinval(src, dest)); if (opts.filter && !opts.filter(src, dest)) return done(null); io("stat", { path: srcAbs, follow: opts.dereference }, "lstat", src, (statErr, st) => { if (statErr) return done(statErr); if (st!.kind === "symlink") { return io("readlink", { path: srcAbs }, "readlink", src, (linkErr, link) => { if (linkErr) return done(linkErr); io("symlink", { target: link!.target, path: destAbs }, "symlink", link!.target, (symErr) => done(symErr), dest); }); } if (st!.kind === "dir") { if (!opts.recursive) return done(cpNotRecursive(src)); return io("mkdir", { path: destAbs, mode: 0o777, recursive: true }, "mkdir", dest, (mkdirErr) => { if (mkdirErr) return done(mkdirErr); io("readdir", { path: srcAbs }, "scandir", src, (readErr, entries) => { if (readErr) return done(readErr); const next = (i: number): void => { if (i >= entries!.length) return done(null); const e = entries![i]!; // Plain concatenation, not resolver's join(): that always roots at "/", // which would turn a relative src/dest absolute from here down. cpOp(`${src.replace(/\/+$/, "")}/${e.name}`, `${dest.replace(/\/+$/, "")}/${e.name}`, opts)(io, (err) => (err ? done(err) : next(i + 1))); }; next(0); }); }); } io("stat", { path: destAbs, follow: true }, "stat", dest, (destStatErr) => { const exists = !destStatErr; if (exists && !opts.force) return done(opts.errorOnExist ? cpEexist(dest) : null); io("open", { path: srcAbs, flags: FLAG_STRINGS["r"]!, mode: 0 }, "copyfile", src, (openErr, r) => { if (openErr) return done(openErr); const from = r!.fd; readFdToEnd(from)(io, (readErr, bytes) => closeOp(from)(io, () => { if (readErr) return done(readErr); const excl = (opts.mode & constants.COPYFILE_EXCL) !== 0; io("open", { path: destAbs, flags: FLAG_STRINGS[excl ? "wx" : "w"]!, mode: 0o666 }, "copyfile", src, (writeOpenErr, w) => { if (writeOpenErr) return done(writeOpenErr); writeAllOp(w!.fd, bytes!, null)(io, (writeErr) => closeOp(w!.fd)(io, (closeErr) => { if (writeErr ?? closeErr) return done(writeErr ?? closeErr); if (!opts.preserveTimestamps) return done(null); io("utimes", { path: destAbs, follow: true, mtimeMs: st!.mtimeMs }, "utime", dest, (utimesErr) => done(utimesErr)); })); }, dest); })); }, dest); }); }); }; function cpNotRecursive(src: string): Error { const path = `${src.replace(/\/+$/, "")}/`; const info = { code: "EISDIR", message: "is a directory", path, syscall: "cp", errno: 21 }; const e = new Error(`Recursive option not enabled, cannot copy a directory: ${path}`); Object.defineProperty(e, "name", { value: "SystemError", enumerable: false, configurable: true, writable: true }); return Object.assign(e, { code: "ERR_FS_EISDIR", info, errno: 21, syscall: "cp", path }); } function cpEexist(dest: string): Error { const info = { code: "EEXIST", message: "file already exists", path: dest, syscall: "cp", errno: 17 }; const e = new Error(`Target already exists: cp returned EEXIST (${dest} already exists) ${dest}`); Object.defineProperty(e, "name", { value: "SystemError", enumerable: false, configurable: true, writable: true }); return Object.assign(e, { code: "ERR_FS_CP_EEXIST", info, errno: 17, syscall: "cp", path: dest }); } function cpEinval(src: string, dest: string): Error { const e = new Error(`Invalid src or dest: cp returned EINVAL (cannot copy ${src} to a subdirectory of self ${dest}) ${dest}`); return Object.assign(e, { code: "ERR_FS_CP_EINVAL", errno: -22, syscall: "cp", path: src, dest }); } // ---- argument shapes shared by the sync, callback and promise forms ---- /** writeFile/appendFile data: a string in `encoding`, or any ArrayBufferView's bytes. */ function toBytes(data: unknown, encoding: string | undefined): Uint8Array { if (typeof data === "string") return Buffer.from(data, (encoding ?? "utf8")); if (ArrayBuffer.isView(data)) return new Uint8Array(data.buffer, data.byteOffset, data.byteLength); throw invalidArgType("data", "of type string or an instance of Buffer, TypedArray, or DataView", data); } const writeFileArgs = (options: unknown, flag: string) => ({ encoding: encodingOf(options), flags: parseFlags(options && typeof options === "object" ? (options as { flag?: unknown }).flag ?? flag : flag, flag), mode: (options && typeof options === "object" ? (options as { mode?: number }).mode : undefined) ?? 0o666, }); const mkdirArgs = (options: unknown): { recursive: boolean; mode: number } => typeof options === "number" || typeof options === "string" ? { recursive: false, mode: Number(options) } : { recursive: Boolean((options as { recursive?: boolean } | undefined)?.recursive), mode: (options as { mode?: number } | undefined)?.mode ?? 0o777 }; const rmArgs = (options: unknown): { recursive: boolean; force: boolean } => { const o = (options ?? {}) as { recursive?: boolean; force?: boolean }; return { recursive: Boolean(o.recursive), force: Boolean(o.force) }; }; /** fs.write's two shapes: (fd, buffer, offset?, length?, position?) or * (fd, buffer, { offset, length, position }), and (fd, string, position?, encoding?). */ function writeArgs(data: unknown, rest: unknown[]): { bytes: Uint8Array; position: number | null; source: unknown } { if (typeof data === "string") { const position = typeof rest[0] === "number" ? rest[0] : null; return { bytes: Buffer.from(data, (typeof rest[1] === "string" ? rest[1] : "utf8")), position, source: data }; } if (!ArrayBuffer.isView(data)) throw invalidArgType("buffer", "of type string or an instance of Buffer, TypedArray, or DataView", data); const view = new Uint8Array(data.buffer, data.byteOffset, data.byteLength); const opts = rest[0] !== null && typeof rest[0] === "object" ? rest[0] as { offset?: number; length?: number; position?: number | null } : { offset: rest[0] as number | undefined, length: rest[1] as number | undefined, position: rest[2] as number | null | undefined }; const offset = opts.offset ?? 0; const length = opts.length ?? view.byteLength - offset; const position = typeof opts.position === "number" && opts.position >= 0 ? opts.position : null; return { bytes: view.subarray(offset, offset + length), position, source: data }; } const readFileArgs = (options: unknown) => ({ encoding: encodingOf(options), flags: parseFlags(options && typeof options === "object" ? (options as { flag?: unknown }).flag : undefined, "r"), }); const decodeAs = (buf: Buffer, encoding: string | undefined): Buffer | string => encoding && encoding !== "buffer" ? buf.toString(encoding) : buf; /** read(fd, buffer, offset, length, position) or read(fd, buffer, { offset, length, position }). */ function readArgs(buffer: unknown, rest: unknown[]): { buffer: Uint8Array; offset: number; length: number; position: number | null } { if (!(buffer instanceof Uint8Array)) throw invalidArgType("buffer", "an instance of Buffer, TypedArray, or DataView", buffer); const opts = rest[0] !== null && typeof rest[0] === "object" ? rest[0] as { offset?: number; length?: number; position?: number | null } : { offset: rest[0] as number | undefined, length: rest[1] as number | undefined, position: rest[2] as number | null | undefined }; const offset = opts.offset ?? 0; const length = opts.length ?? buffer.byteLength - offset; const position = typeof opts.position === "number" && opts.position >= 0 ? opts.position : null; return { buffer, offset, length, position }; } function readdirArgs(options: unknown): boolean { return Boolean(options && typeof options === "object" && (options as { withFileTypes?: boolean }).withFileTypes); } /** utimes/lutimes/futimes time args: a Date, or a number/numeric string of seconds * since the epoch (not ms — that's Node's own convention for these three). */ function timeToMs(v: unknown, name: string): number { if (v instanceof Date) return v.getTime(); const n = Number(v); if ((typeof v === "number" || typeof v === "string") && Number.isFinite(n)) return n * 1000; throw invalidArgType(name, "a number, string, or Date", v); } function toBufferList(buffers: unknown): Uint8Array[] { if (!Array.isArray(buffers)) throw invalidArgType("buffers", "an array", buffers); return buffers.map((b) => { if (!ArrayBuffer.isView(b)) throw invalidArgType("buffer", "an instance of Buffer or TypedArray", b); return new Uint8Array(b.buffer, b.byteOffset, b.byteLength); }); } /** readv/writev's (fd, buffers[, position], cb) — position defaults to the fd's * current offset (advancing it), as a plain read/write would. */ function positionArg(v: unknown): number | null { return typeof v === "number" ? v : null; } function globPatterns(pattern: unknown): string[] { if (typeof pattern === "string") return [pattern]; if (Array.isArray(pattern) && pattern.every((p) => typeof p === "string")) return pattern as string[]; throw invalidArgType("pattern", "a string or an array of strings", pattern); } function globOptions(options: unknown): { cwd: string; opts: GlobOpts } { const o = (options ?? {}) as { cwd?: unknown; exclude?: unknown; withFileTypes?: boolean }; const excludeOpt = o.exclude; const exclude: GlobOpts["exclude"] = typeof excludeOpt === "function" ? (name) => Boolean((excludeOpt as (s: string) => unknown)(name)) : Array.isArray(excludeOpt) ? (_name, rel) => (excludeOpt as unknown[]).some((p) => typeof p === "string" && matchesGlob(rel, p)) : () => false; return { cwd: typeof o.cwd === "string" ? o.cwd : ctx.cwd, opts: { exclude, withFileTypes: Boolean(o.withFileTypes) } }; } function cpOptions(options: unknown): CpOpts { const o = (options ?? {}) as Partial & { filter?: (src: string, dest: string) => boolean }; return { recursive: Boolean(o.recursive), force: o.force ?? true, errorOnExist: Boolean(o.errorOnExist), dereference: Boolean(o.dereference), preserveTimestamps: Boolean(o.preserveTimestamps), mode: o.mode ?? 0, filter: o.filter, }; } // ---- sync forms ---- function statSync(path: PathLike, options?: { throwIfNoEntry?: boolean; bigint?: boolean }): AnyStats | undefined { const p = toPathString(path); try { return runSync(statOp(p, true, bigintOf(options))); } catch (e) { if (options?.throwIfNoEntry === false && (e as { code?: string }).code === "ENOENT") return undefined; throw e; } } function lstatSync(path: PathLike, options?: { throwIfNoEntry?: boolean; bigint?: boolean }): AnyStats | undefined { const p = toPathString(path); try { return runSync(statOp(p, false, bigintOf(options))); } catch (e) { if (options?.throwIfNoEntry === false && (e as { code?: string }).code === "ENOENT") return undefined; throw e; } } function realpathSync(path: PathLike): string { return runSync(realpathOp(toPathString(path), false)); } realpathSync.native = (path: PathLike): string => runSync(realpathOp(toPathString(path), true)); function truncateSync(path: PathLike, len = 0): void { runSync(truncateOp(toPathString(path), len)); } function ftruncateSync(fd: number, len = 0): void { runSync(ftruncateOp(fd, len)); } function utimesSync(path: PathLike, atime: unknown, mtime: unknown): void { timeToMs(atime, "atime"); runSync(utimesPathOp(toPathString(path), true, timeToMs(mtime, "mtime"))); } function lutimesSync(path: PathLike, atime: unknown, mtime: unknown): void { timeToMs(atime, "atime"); runSync(utimesPathOp(toPathString(path), false, timeToMs(mtime, "mtime"))); } function futimesSync(fd: number, atime: unknown, mtime: unknown): void { timeToMs(atime, "atime"); runSync(futimesOp(fd, timeToMs(mtime, "mtime"))); } function readvSync(fd: number, buffers: unknown, position?: unknown): number { return runSync(readvOp(fd, toBufferList(buffers), positionArg(position))); } function writevSync(fd: number, buffers: unknown, position?: unknown): number { return runSync(writevOp(fd, toBufferList(buffers), positionArg(position))); } function fsyncSync(fd: number): void { runSync(syncOp(fd, "fsync")); } function fdatasyncSync(fd: number): void { runSync(syncOp(fd, "fdatasync")); } function chmodSync(path: PathLike, _mode: number): void { runSync(noopOnPathOp(toPathString(path), "chmod", true)); } function lchmodSync(path: PathLike, _mode: number): void { runSync(noopOnPathOp(toPathString(path), "lchmod", false)); } function fchmodSync(fd: number, _mode: number): void { runSync(noopOnFdOp(fd, "fchmod")); } function chownSync(path: PathLike, _uid: number, _gid: number): void { runSync(noopOnPathOp(toPathString(path), "chown", true)); } function lchownSync(path: PathLike, _uid: number, _gid: number): void { runSync(noopOnPathOp(toPathString(path), "lchown", false)); } function fchownSync(fd: number, _uid: number, _gid: number): void { runSync(noopOnFdOp(fd, "fchown")); } function linkSync(_existingPath: PathLike, _newPath: PathLike): never { throw linkOp(); } function statfsSync(_path: PathLike, _options?: unknown): never { throw statfsOp(); } function opendirSync(path: PathLike): Dir { return runSync(opendirOp(toPathString(path))); } function globSync(pattern: unknown, options?: unknown): Array { const { cwd, opts } = globOptions(options); return runSync(globOp(globPatterns(pattern), abs(cwd), opts)); } function cpSync(src: PathLike, dest: PathLike, options?: unknown): void { runSync(cpOp(toPathString(src, "src"), toPathString(dest, "dest"), cpOptions(options))); } function mkdtempDisposableSync(prefix: string, _options?: unknown): { path: string; remove(): void; [Symbol.dispose](): void } { const path = runSync(mkdtempOp(String(prefix))); let removed = false; const remove = (): void => { if (removed) return; removed = true; runSync(rmOp(path, true, true)); }; return { path, remove, [Symbol.dispose]: remove }; } // ---- callback forms ---- /** Split Node's optional-options callback signatures: (a, cb) or (a, options, cb). */ const optsAndCb = (options: unknown, cb: unknown): [unknown, Done] => typeof options === "function" ? [undefined, options as Done] : [options, callbackArg>(cb)]; function stat(path: PathLike, options: unknown, cb?: unknown): void { const [opts, done] = optsAndCb(options, cb); statOp(toPathString(path), true, bigintOf(opts))(asyncIo, done); } function lstat(path: PathLike, options: unknown, cb?: unknown): void { const [opts, done] = optsAndCb(options, cb); statOp(toPathString(path), false, bigintOf(opts))(asyncIo, done); } function fstat(fd: number, options: unknown, cb?: unknown): void { const [opts, done] = optsAndCb(options, cb); fstatOp(fd, bigintOf(opts))(asyncIo, done); } function access(path: PathLike, mode: unknown, cb?: unknown): void { const [, done] = optsAndCb(mode, cb); accessOp(toPathString(path))(asyncIo, done); } function exists(path: PathLike, cb: (exists: boolean) => void): void { callbackArg(cb); let p: string; try { p = toPathString(path); } catch { cb(false); // Node answers an invalid path synchronously return; } ctx.submit("stat", { path: abs(p), follow: true }, (r) => cb(r.ok)); } function readdir(path: PathLike, options: unknown, cb?: unknown): void { const [opts, done] = optsAndCb(options, cb); readdirOp(toPathString(path), readdirArgs(opts))(asyncIo, done); } function readFile(path: PathLike | number, options: unknown, cb?: unknown): void { const [opts, done] = optsAndCb(options, cb); const { encoding, flags } = readFileArgs(opts); const body = typeof path === "number" ? readFdToEnd(path) : readFileOp(toPathString(path), flags); body(asyncIo, (err, buf) => (err ? done(err) : (done as Done)(null, decodeAs(buf!, encoding)))); } function open(path: PathLike, flags: unknown, mode?: unknown, cb?: unknown): void { if (typeof flags === "function") [flags, mode, cb] = [undefined, undefined, flags]; else if (typeof mode === "function") [mode, cb] = [undefined, mode]; const done = callbackArg>(cb); openOp(toPathString(path), parseFlags(flags, "r"), (mode as number | undefined) ?? 0o666)(asyncIo, done); } function read(fd: number, buffer: unknown, ...rest: unknown[]): void { let cb = rest.pop(); if (typeof buffer === "function") [cb, buffer, rest] = [buffer, Buffer.alloc(16384), []]; else if (buffer !== null && typeof buffer === "object" && !(buffer instanceof Uint8Array)) { const o = buffer as { buffer?: Uint8Array }; buffer = o.buffer ?? Buffer.alloc(16384); rest = [o]; } const done = callbackArg<(err: Error | null, bytesRead?: number, buffer?: Uint8Array) => void>(cb); const a = readArgs(buffer, rest); const stdin = ctx.process.stdin; if (fd === 0 && stdin instanceof StdinStream) { return stdin.readRaw(a.length, (bytes) => { if (bytes) a.buffer.set(bytes, a.offset); done(null, bytes?.length ?? 0, a.buffer); }); } readOp(fd, a.buffer, a.offset, a.length, a.position)(asyncIo, (err, n) => (err ? done(err) : done(null, n, a.buffer))); } function close(fd: number, cb?: unknown): void { const done: Done = typeof cb === "function" ? cb as Done : () => {}; closeOp(fd)(asyncIo, done); } function realpath(path: PathLike, options: unknown, cb?: unknown): void { const [, done] = optsAndCb(options, cb); realpathOp(toPathString(path), false)(asyncIo, done); } realpath.native = (path: PathLike, options: unknown, cb?: unknown): void => { const [, done] = optsAndCb(options, cb); realpathOp(toPathString(path), true)(asyncIo, done); }; const fileTarget = (path: PathLike | number): string | number => (typeof path === "number" ? path : toPathString(path)); function writeFile(path: PathLike | number, data: unknown, options: unknown, cb?: unknown): void { const [opts, done] = optsAndCb(options, cb); const { encoding, flags, mode } = writeFileArgs(opts, "w"); writeFileOp(fileTarget(path), toBytes(data, encoding), flags, mode)(asyncIo, done); } function appendFile(path: PathLike | number, data: unknown, options: unknown, cb?: unknown): void { const [opts, done] = optsAndCb(options, cb); const { encoding, flags, mode } = writeFileArgs(opts, "a"); writeFileOp(fileTarget(path), toBytes(data, encoding), flags, mode)(asyncIo, done); } function mkdir(path: PathLike, options: unknown, cb?: unknown): void { const [opts, done] = optsAndCb(options, cb); const { recursive, mode } = mkdirArgs(opts); mkdirOp(toPathString(path), recursive, mode)(asyncIo, done); } function rm(path: PathLike, options: unknown, cb?: unknown): void { const [opts, done] = optsAndCb(options, cb); const { recursive, force } = rmArgs(opts); rmOp(toPathString(path), recursive, force)(asyncIo, done); } function rmdir(path: PathLike, options: unknown, cb?: unknown): void { const [opts, done] = optsAndCb(options, cb); rmdirOp(toPathString(path), rmArgs(opts).recursive)(asyncIo, done); } // The type argument names a link's kind on Windows; Linux ignores it. function symlink(target: PathLike, path: PathLike, type: unknown, cb?: unknown): void { if (typeof type === "function") cb = type; symlinkOp(toPathString(target, "target"), toPathString(path))(asyncIo, callbackArg(cb)); } function readlink(path: PathLike, options: unknown, cb?: unknown): void { const [opts, done] = optsAndCb(options, cb); readlinkOp(toPathString(path), encodingOf(opts))(asyncIo, done); } function rename(from: PathLike, to: PathLike, cb: unknown): void { renameOp(toPathString(from, "oldPath"), toPathString(to, "newPath"))(asyncIo, callbackArg(cb)); } function unlink(path: PathLike, cb: unknown): void { unlinkOp(toPathString(path))(asyncIo, callbackArg(cb)); } function copyFile(src: PathLike, dest: PathLike, mode: unknown, cb?: unknown): void { if (typeof mode === "function") [mode, cb] = [0, mode]; copyFileOp(toPathString(src, "src"), toPathString(dest, "dest"), Number(mode ?? 0))(asyncIo, callbackArg(cb)); } function mkdtemp(prefix: string, options: unknown, cb?: unknown): void { const [, done] = optsAndCb(options, cb); mkdtempOp(String(prefix))(asyncIo, done); } function write(fd: number, data: unknown, ...rest: unknown[]): void { const done = callbackArg<(err: Error | null, written?: number, source?: unknown) => void>(rest.pop()); const a = writeArgs(data, rest); writeAllOp(fd, a.bytes, a.position)(asyncIo, (err, n) => (err ? done(err) : done(null, n, a.source))); } function truncate(path: PathLike, len: unknown, cb?: unknown): void { const [l, done] = typeof len === "function" ? [0, len as Done] : [Number(len ?? 0), callbackArg>(cb)]; truncateOp(toPathString(path), l)(asyncIo, done); } function ftruncate(fd: number, len: unknown, cb?: unknown): void { const [l, done] = typeof len === "function" ? [0, len as Done] : [Number(len ?? 0), callbackArg>(cb)]; ftruncateOp(fd, l)(asyncIo, done); } function utimes(path: PathLike, atime: unknown, mtime: unknown, cb: unknown): void { timeToMs(atime, "atime"); utimesPathOp(toPathString(path), true, timeToMs(mtime, "mtime"))(asyncIo, callbackArg(cb)); } function lutimes(path: PathLike, atime: unknown, mtime: unknown, cb: unknown): void { timeToMs(atime, "atime"); utimesPathOp(toPathString(path), false, timeToMs(mtime, "mtime"))(asyncIo, callbackArg(cb)); } function futimes(fd: number, atime: unknown, mtime: unknown, cb: unknown): void { timeToMs(atime, "atime"); futimesOp(fd, timeToMs(mtime, "mtime"))(asyncIo, callbackArg(cb)); } function readv(fd: number, buffers: unknown, ...rest: unknown[]): void { const done = callbackArg<(err: Error | null, n?: number, buffers?: unknown) => void>(rest.pop()); const position = positionArg(rest[0]); readvOp(fd, toBufferList(buffers), position)(asyncIo, (err, n) => (err ? done(err) : done(null, n, buffers))); } function writev(fd: number, buffers: unknown, ...rest: unknown[]): void { const done = callbackArg<(err: Error | null, n?: number, buffers?: unknown) => void>(rest.pop()); const position = positionArg(rest[0]); writevOp(fd, toBufferList(buffers), position)(asyncIo, (err, n) => (err ? done(err) : done(null, n, buffers))); } function fsync(fd: number, cb: unknown): void { syncOp(fd, "fsync")(asyncIo, callbackArg(cb)); } function fdatasync(fd: number, cb: unknown): void { syncOp(fd, "fdatasync")(asyncIo, callbackArg(cb)); } function chmod(path: PathLike, _mode: number, cb: unknown): void { noopOnPathOp(toPathString(path), "chmod", true)(asyncIo, callbackArg(cb)); } function lchmod(path: PathLike, _mode: number, cb: unknown): void { noopOnPathOp(toPathString(path), "lchmod", false)(asyncIo, callbackArg(cb)); } function fchmod(fd: number, _mode: number, cb: unknown): void { noopOnFdOp(fd, "fchmod")(asyncIo, callbackArg(cb)); } function chown(path: PathLike, _uid: number, _gid: number, cb: unknown): void { noopOnPathOp(toPathString(path), "chown", true)(asyncIo, callbackArg(cb)); } function lchown(path: PathLike, _uid: number, _gid: number, cb: unknown): void { noopOnPathOp(toPathString(path), "lchown", false)(asyncIo, callbackArg(cb)); } function fchown(fd: number, _uid: number, _gid: number, cb: unknown): void { noopOnFdOp(fd, "fchown")(asyncIo, callbackArg(cb)); } function link(_existingPath: PathLike, _newPath: PathLike, cb: unknown): void { callbackArg>(cb)(linkOp()); } function statfs(_path: PathLike, options: unknown, cb?: unknown): void { const [, done] = optsAndCb(options, cb); done(statfsOp()); } function opendir(path: PathLike, options: unknown, cb?: unknown): void { const [, done] = optsAndCb(options, cb); opendirOp(toPathString(path))(asyncIo, done as Done); } function glob(pattern: unknown, options: unknown, cb?: unknown): void { const [opts, done] = optsAndCb(options, cb); const { cwd, opts: g } = globOptions(opts); globOp(globPatterns(pattern), abs(cwd), g)(asyncIo, done as Done>); } function cp(src: PathLike, dest: PathLike, options: unknown, cb?: unknown): void { const [opts, done] = optsAndCb(options, cb); cpOp(toPathString(src, "src"), toPathString(dest, "dest"), cpOptions(opts))(asyncIo, done); } /** Node's own openAsBlob swallows the real failure (missing, not a file, …) behind * one generic validation error — golden-verified, not our choice. */ function openAsBlob(path: PathLike, options?: unknown): Promise { const type = (options && typeof options === "object" ? (options as { type?: string }).type : undefined) ?? ""; return promised(readFileOp(toPathString(path), FLAG_STRINGS["r"]!)) .then((buf) => new Blob([buf], { type })) .catch(() => { throw Object.assign(new TypeError("Unable to open file as blob"), { code: "ERR_INVALID_ARG_VALUE" }); }); } // ---- watchFile / unwatchFile: a timer-driven poll over stat, as Node's is ---- /** A missing watched file reports all-zero stats, as Node's statWatcher does, * rather than throwing out of the poll. */ function zeroStats(): Stats { return new Stats(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); } interface StatWatchEntry { timer: Timeout; prev: Stats; listeners: Set<(curr: Stats, prev: Stats) => void> } const statWatchers = new Map(); function watchFile(filename: PathLike, options: unknown, listener?: unknown): void { const fn = typeof options === "function" ? options as (curr: Stats, prev: Stats) => void : callbackArg<(curr: Stats, prev: Stats) => void>(listener); const opts = (typeof options === "object" && options ? options : {}) as { persistent?: boolean; interval?: number }; const path = abs(toPathString(filename, "filename")); let entry = statWatchers.get(path); if (!entry) { const first = ctx.trySync("stat", { path, follow: true }); // A path missing when the watch starts gets one call at the first poll even // though curr and prev are both the same all-zero Stats — Node's own quirk, // golden-verified: the very first "it's missing" is itself the notable change. let firstPollPending = !first.ok; const poll = (): void => { const r = ctx.trySync("stat", { path, follow: true }); const curr = r.ok ? toStats(r.val) : zeroStats(); const prev = entry!.prev; const fireOnceMissing = firstPollPending && !r.ok; firstPollPending = false; if (fireOnceMissing || curr.mtimeMs !== prev.mtimeMs || curr.ino !== prev.ino) { entry!.prev = curr; for (const l of entry!.listeners) l(curr, prev); } }; entry = { timer: ctx.timers.setInterval(poll, opts.interval ?? 5007), prev: first.ok ? toStats(first.val) : zeroStats(), listeners: new Set() }; if (opts.persistent === false) entry.timer.unref(); statWatchers.set(path, entry); } entry.listeners.add(fn); } function unwatchFile(filename: PathLike, listener?: unknown): void { const path = abs(toPathString(filename, "filename")); const entry = statWatchers.get(path); if (!entry) return; if (typeof listener === "function") entry.listeners.delete(listener as (curr: Stats, prev: Stats) => void); else entry.listeners.clear(); if (entry.listeners.size === 0) { ctx.timers.clearInterval(entry.timer); statWatchers.delete(path); } } // ---- promises ---- class FileHandle { constructor(readonly fd: number) {} read(buffer: Uint8Array, offset?: number | object, length?: number, position?: number | null): Promise<{ bytesRead: number; buffer: Uint8Array }> { const a = readArgs(buffer ?? Buffer.alloc(16384), [offset, length, position]); return promised(readOp(this.fd, a.buffer, a.offset, a.length, a.position)).then((bytesRead) => ({ bytesRead, buffer: a.buffer })); } readFile(options?: unknown): Promise { return promised(readFdToEnd(this.fd)).then((buf) => decodeAs(buf, encodingOf(options))); } stat(options?: { bigint?: boolean }): Promise { return promised(fstatOp(this.fd, bigintOf(options))); } write(data: unknown, ...rest: unknown[]): Promise<{ bytesWritten: number; buffer: unknown }> { const a = writeArgs(data, rest); return promised(writeAllOp(this.fd, a.bytes, a.position)).then((bytesWritten) => ({ bytesWritten, buffer: a.source })); } writeFile(data: unknown, options?: unknown): Promise { return promised(writeFileOp(this.fd, toBytes(data, encodingOf(options)), FLAG_STRINGS["w"]!, 0o666)); } appendFile(data: unknown, options?: unknown): Promise { return this.writeFile(data, options); } truncate(len = 0): Promise { return promised(ftruncateOp(this.fd, len)); } close(): Promise { return promised(closeOp(this.fd)); } } const promises = { access: (path: PathLike) => promised(accessOp(toPathString(path))), open: (path: PathLike, flags?: unknown, mode?: number) => promised(openOp(toPathString(path), parseFlags(flags, "r"), mode ?? 0o666)).then((fd) => new FileHandle(fd)), readdir: (path: PathLike, options?: unknown) => promised(readdirOp(toPathString(path), readdirArgs(options))), lstat: (path: PathLike, options?: { bigint?: boolean }) => promised(statOp(toPathString(path), false, bigintOf(options))), stat: (path: PathLike, options?: { bigint?: boolean }) => promised(statOp(toPathString(path), true, bigintOf(options))), // fs.promises.realpath is realpath(3), as realpathSync.native is. realpath: (path: PathLike) => promised(realpathOp(toPathString(path), true)), symlink: (target: PathLike, path: PathLike, _type?: unknown) => promised(symlinkOp(toPathString(target, "target"), toPathString(path))), readlink: (path: PathLike, options?: unknown) => promised(readlinkOp(toPathString(path), encodingOf(options))), readFile: (path: PathLike, options?: unknown) => { const { encoding, flags } = readFileArgs(options); return promised(readFileOp(toPathString(path), flags)).then((buf) => decodeAs(buf, encoding)); }, writeFile: (path: PathLike | FileHandle, data: unknown, options?: unknown) => { const { encoding, flags, mode } = writeFileArgs(options, "w"); return promised(writeFileOp(path instanceof FileHandle ? path.fd : toPathString(path), toBytes(data, encoding), flags, mode)); }, appendFile: (path: PathLike | FileHandle, data: unknown, options?: unknown) => { const { encoding, flags, mode } = writeFileArgs(options, "a"); return promised(writeFileOp(path instanceof FileHandle ? path.fd : toPathString(path), toBytes(data, encoding), flags, mode)); }, mkdir: (path: PathLike, options?: unknown) => { const { recursive, mode } = mkdirArgs(options); return promised(mkdirOp(toPathString(path), recursive, mode)); }, rm: (path: PathLike, options?: unknown) => { const { recursive, force } = rmArgs(options); return promised(rmOp(toPathString(path), recursive, force)); }, rmdir: (path: PathLike, options?: unknown) => promised(rmdirOp(toPathString(path), rmArgs(options).recursive)), rename: (from: PathLike, to: PathLike) => promised(renameOp(toPathString(from, "oldPath"), toPathString(to, "newPath"))), unlink: (path: PathLike) => promised(unlinkOp(toPathString(path))), copyFile: (src: PathLike, dest: PathLike, mode?: number) => promised(copyFileOp(toPathString(src, "src"), toPathString(dest, "dest"), mode ?? 0)), mkdtemp: (prefix: string) => promised(mkdtempOp(String(prefix))), truncate: (path: PathLike, len = 0) => promised(truncateOp(toPathString(path), len)), utimes: (path: PathLike, atime: unknown, mtime: unknown) => { timeToMs(atime, "atime"); return promised(utimesPathOp(toPathString(path), true, timeToMs(mtime, "mtime"))); }, lutimes: (path: PathLike, atime: unknown, mtime: unknown) => { timeToMs(atime, "atime"); return promised(utimesPathOp(toPathString(path), false, timeToMs(mtime, "mtime"))); }, chmod: (path: PathLike, _mode: number) => promised(noopOnPathOp(toPathString(path), "chmod", true)), lchmod: (path: PathLike, _mode: number) => promised(noopOnPathOp(toPathString(path), "lchmod", false)), chown: (path: PathLike, _uid: number, _gid: number) => promised(noopOnPathOp(toPathString(path), "chown", true)), lchown: (path: PathLike, _uid: number, _gid: number) => promised(noopOnPathOp(toPathString(path), "lchown", false)), link: (_existingPath: PathLike, _newPath: PathLike) => Promise.reject(linkOp()), statfs: (_path: PathLike, _options?: unknown) => Promise.reject(statfsOp()), opendir: (path: PathLike, _options?: unknown) => promised(opendirOp(toPathString(path))), cp: (src: PathLike, dest: PathLike, options?: unknown) => promised(cpOp(toPathString(src, "src"), toPathString(dest, "dest"), cpOptions(options))), openAsBlob, mkdtempDisposable: async (prefix: string, _options?: unknown) => { const path = await promises.mkdtemp(String(prefix)); let removed = false; const remove = async (): Promise => { if (removed) return; removed = true; await promises.rm(path, { recursive: true, force: true }); }; return { path, remove, [Symbol.asyncDispose]: remove }; }, async* glob(pattern: unknown, options?: unknown): AsyncGenerator { const { cwd, opts } = globOptions(options); const results = await promised(globOp(globPatterns(pattern), abs(cwd), opts)); yield* results; }, /** A real async generator (not just an AsyncIterable wrapper) so its surface shape * — a `next`/`return`/`throw`-bearing generator — matches Node's own. */ async* watch(filename: PathLike, options?: WatchOptions | string): AsyncGenerator<{ eventType: WatchEvent[0]; filename: string | Buffer | null }> { const w = watch(filename, options); const queue: Array<[WatchEvent[0], string | Buffer]> = []; let wake: (() => void) | null = null; let err: Error | null = null; let closed = false; const onChange = (type: WatchEvent[0], name: string | Buffer): void => { queue.push([type, name]); wake?.(); wake = null; }; const onError = (e: Error): void => { err = e; wake?.(); wake = null; }; const onClose = (): void => { closed = true; wake?.(); wake = null; }; w.on("change", onChange); w.on("error", onError); w.on("close", onClose); try { for (;;) { while (queue.length === 0 && !closed && !err) await new Promise((resolve) => { wake = resolve; }); if (err) throw err; if (queue.length === 0) return; const [eventType, name] = queue.shift()!; yield { eventType, filename: name }; } } finally { w.off("change", onChange); w.off("error", onError); w.off("close", onClose); w.close(); } }, constants, }; // ---- streams ---- interface ReadStreamOptions { flags?: string | number; mode?: number; fd?: number | null; start?: number; end?: number; highWaterMark?: number; autoClose?: boolean; encoding?: string; emitClose?: boolean; } /** Node's fs.ReadStream over the async fd ops: opens in the background (emitting * 'open' and 'ready'), reads highWaterMark-sized slices from `start` to `end` * inclusive, and closes the fd when destroyed (autoClose decides whether 'end' * destroys). */ class ReadStream extends Readable { path: string | undefined; fd: number | null; flags: string | number; mode: number; start: number | undefined; end: number; pos: number | undefined; bytesRead = 0; #autoClose: boolean; #opening = false; #inRead = false; #afterIo: (() => void) | null = null; constructor(path: PathLike | undefined, options: ReadStreamOptions | string = {}) { const opts: ReadStreamOptions = typeof options === "string" ? { encoding: options } : options ?? {}; super({ highWaterMark: opts.highWaterMark ?? 64 * 1024, encoding: opts.encoding, autoDestroy: opts.autoClose ?? true, }); this.fd = typeof opts.fd === "number" ? opts.fd : null; this.path = this.fd === null ? toPathString(path) : undefined; this.flags = opts.flags ?? "r"; this.mode = opts.mode ?? 0o666; this.start = opts.start; this.end = opts.end ?? Infinity; this.pos = opts.start; this.#autoClose = opts.autoClose ?? true; if (this.start !== undefined && this.start > this.end) { throw Object.assign(new RangeError(`The value of "start" is out of range. It must be <= "end" (here: ${this.end}). Received ${this.start}`), { code: "ERR_OUT_OF_RANGE" }); } if (this.fd === null) this.#open(); } get pending(): boolean { return this.fd === null; } get autoClose(): boolean { return this.#autoClose; } set autoClose(value: boolean) { this.#autoClose = value; } /** Deprecated (DEP0135): the construction-time open, exposed so old code that calls * it again doesn't throw. Node's own open() just reopens the same way. */ open(): void { warnOnce("DEP0135", "ReadStream.prototype.open() is deprecated"); this.#open(); } #open(): void { this.#opening = true; openOp(this.path!, parseFlags(this.flags, "r"), this.mode)(asyncIo, (err, fd) => { this.#opening = false; if (err) { this.#ioDone(); this.destroy(err); return; } this.fd = fd!; if (this.#ioDone()) return; this.emit("open", fd); this.emit("ready"); if (this.#wantRead !== null) this._read(this.#wantRead); }); } #wantRead: number | null = null; /** Runs a destroy that waited for in-flight I/O. True when one ran. */ #ioDone(): boolean { const after = this.#afterIo; this.#afterIo = null; after?.(); return after !== null; } override _read(n: number): void { if (this.fd === null) { this.#wantRead = n; return; } this.#wantRead = null; const want = this.pos !== undefined ? Math.min(this.end - this.pos + 1, n) : Math.min(this.end - this.bytesRead + 1, n); if (want <= 0) { this.push(null); return; } const buf = Buffer.allocUnsafeSlow(want); this.#inRead = true; readOp(this.fd, buf, 0, want, this.pos ?? null)(asyncIo, (err, bytesRead) => { this.#inRead = false; if (this.#ioDone()) return; if (err) { this.destroy(err); return; } const got = bytesRead!; if (got === 0) { this.push(null); return; } if (this.pos !== undefined) this.pos += got; this.bytesRead += got; this.push(got === buf.length ? buf : buf.subarray(0, got)); }); } override _destroy(error: Error | null, callback: (error?: Error | null) => void): void { const finish = (): void => { if (this.fd === null) return callback(error); const fd = this.fd; this.fd = null; closeOp(fd)(asyncIo, (closeErr) => callback(closeErr ?? error)); }; if (this.#opening || this.#inRead) this.#afterIo = finish; else finish(); } close(cb?: (err?: Error | null) => void): void { if (cb) this.once("close", () => cb()); this.destroy(); } } function createReadStream(path: PathLike | undefined, options?: ReadStreamOptions | string): ReadStream { return new ReadStream(path, options); } interface WriteStreamOptions { flags?: string | number; mode?: number; fd?: number | null; start?: number; highWaterMark?: number; autoClose?: boolean; encoding?: string; emitClose?: boolean; flush?: boolean; } /** Node's fs.WriteStream over the async fd ops: opens in the background (emitting * 'open' and 'ready'), writes each chunk at `start`-relative positions when given, * and closes the fd once destroyed (autoClose decides whether 'finish' destroys). */ class WriteStream extends Writable { path: string | undefined; fd: number | null; flags: string | number; mode: number; start: number | undefined; pos: number | undefined; bytesWritten = 0; #autoClose: boolean; #opening = false; #inWrite = false; #afterIo: (() => void) | null = null; #afterOpen: (() => void) | null = null; constructor(path: PathLike | undefined, options: WriteStreamOptions | string = {}) { const opts: WriteStreamOptions = typeof options === "string" ? { encoding: options } : options ?? {}; super({ highWaterMark: opts.highWaterMark, defaultEncoding: opts.encoding, autoDestroy: opts.autoClose ?? true, }); this.fd = typeof opts.fd === "number" ? opts.fd : null; this.path = this.fd === null ? toPathString(path) : undefined; this.flags = opts.flags ?? "w"; this.mode = opts.mode ?? 0o666; this.start = opts.start; this.pos = opts.start; this.#autoClose = opts.autoClose ?? true; if (this.fd === null) this.#open(); } get pending(): boolean { return this.fd === null; } get autoClose(): boolean { return this.#autoClose; } set autoClose(value: boolean) { this.#autoClose = value; } /** Deprecated (DEP0135): the construction-time open, exposed so old code that calls * it again doesn't throw. Node's own open() just reopens the same way. */ open(): void { warnOnce("DEP0135", "WriteStream.prototype.open() is deprecated"); this.#open(); } #open(): void { this.#opening = true; openOp(this.path!, parseFlags(this.flags, "w"), this.mode)(asyncIo, (err, fd) => { this.#opening = false; if (err) { this.#ioDone(); this.destroy(err); return; } this.fd = fd!; if (this.#ioDone()) return; this.emit("open", fd); this.emit("ready"); const resume = this.#afterOpen; this.#afterOpen = null; resume?.(); }); } /** Runs a destroy that waited for in-flight I/O. True when one ran. */ #ioDone(): boolean { const after = this.#afterIo; this.#afterIo = null; after?.(); return after !== null; } override _write(chunk: Buffer, encoding: string, callback: (error?: Error | null) => void): void { if (this.fd === null) { this.#afterOpen = () => this._write(chunk, encoding, callback); return; } this.#inWrite = true; writeAllOp(this.fd, chunk, this.pos ?? null)(asyncIo, (err, written) => { this.#inWrite = false; if (this.#ioDone()) return; if (err) { callback(err); return; } if (this.pos !== undefined) this.pos += written!; this.bytesWritten += written!; callback(null); }); } override _destroy(error: Error | null, callback: (error?: Error | null) => void): void { const finish = (): void => { if (this.fd === null) return callback(error); const fd = this.fd; this.fd = null; closeOp(fd)(asyncIo, (closeErr) => callback(closeErr ?? error)); }; if (this.#opening || this.#inWrite) this.#afterIo = finish; else finish(); } /** Node's close() ends rather than destroys (process 'exit' listeners shouldn't see * an error from a stream that's just finishing up). */ close(cb?: (err?: Error | null) => void): void { if (cb) this.once("close", () => cb()); if (!this.#autoClose) this.once("finish", () => this.destroy()); this.end(); } /** Deprecated alias: Node's destroySoon is literally its end(). */ destroySoon(chunk?: unknown, encoding?: string | ((error?: Error | null) => void) | null, cb?: (error?: Error | null) => void): this { return this.end(chunk, encoding, cb); } } function createWriteStream(path: PathLike | undefined, options?: WriteStreamOptions | string): WriteStream { return new WriteStream(path, options); } // ---- watchers ---- /** Node's FSWatcher over a kernel watch (inotify semantics, not recursive). It holds * the loop while ref'd, and each event is its own poll-phase callback. */ class FSWatcher extends (EventEmitter as unknown as EventEmitterConstructor) { #wid: number | null; #held = false; #buffer: boolean; constructor(wid: number, persistent: boolean, encoding: string | undefined) { super(); this.#wid = wid; this.#buffer = encoding === "buffer"; // fs.watch() creates the handle: a probed Node rule (als-probe3, child case), so // every 'change' it later reports runs in the frame current right now, not in // whatever frame is current when the kernel's fsEvents message happens to arrive. const frame = capture(); watchers.set(wid, (events) => runInFrame(frame, () => { for (const [type, name] of events) { ctx.loop.enqueueCompletion(() => { if (this.#wid !== null) this.emit("change", type, this.#buffer ? Buffer.from(name) : name); }); } })); if (persistent) this.ref(); } close(): void { if (this.#wid === null) return; watchers.delete(this.#wid); ctx.trySync("unwatch", { wid: this.#wid }); this.#wid = null; this.unref(); nextTick(() => this.emit("close")); } ref(): this { if (!this.#held && this.#wid !== null) { this.#held = true; ctx.loop.hold(); } return this; } unref(): this { if (this.#held) { this.#held = false; ctx.loop.release(); } return this; } } interface WatchOptions { persistent?: boolean; recursive?: boolean; encoding?: string; signal?: AbortSignal } function watch(filename: PathLike, options?: WatchOptions | string | ((...args: unknown[]) => void), listener?: (...args: unknown[]) => void): FSWatcher { if (typeof options === "function") [listener, options] = [options, undefined]; const opts: WatchOptions = typeof options === "string" ? { encoding: options } : options ?? {}; if (opts.recursive) throw unsupported("fs", "watch({ recursive: true })", "watches are per directory, as inotify's are"); const path = toPathString(filename, "filename"); const r = ctx.trySync("watch", { path: abs(path) }); if (!r.ok) throw uvError({ code: r.err.code, syscall: "watch", path }); const watcher = new FSWatcher(r.val.wid, opts.persistent ?? true, opts.encoding); if (listener) watcher.on("change", listener); opts.signal?.addEventListener("abort", () => watcher.close(), { once: true }); return watcher; } // ---- the module object ---- return { constants, Stats, Dirent, ReadStream, createReadStream, WriteStream, createWriteStream, // Node's deprecated aliases: the same classes under their original names. FileReadStream: ReadStream, FileWriteStream: WriteStream, watch, promises, Dir, // A pino-style buffered/periodic-flush writer with its own reopen/sync machinery — // real work beyond a thin VFS shim, and not load-bearing for web tooling. Refused // rather than half-built; see the fs chunk's compat report for the opportunity. Utf8Stream: refusedClass("fs", "Utf8Stream", "a buffered writer with periodic-flush and reopen semantics; not implemented"), stat, lstat, fstat, access, exists, readdir, readFile, open, read, close, realpath, writeFile, appendFile, mkdir, rm, rmdir, rename, unlink, copyFile, mkdtemp, write, symlink, readlink, truncate, ftruncate, utimes, lutimes, futimes, readv, writev, fsync, fdatasync, chmod, lchmod, fchmod, chown, lchown, fchown, link, statfs, opendir, glob, cp, openAsBlob, watchFile, unwatchFile, statSync, lstatSync, fstatSync: (fd: number, options?: { bigint?: boolean }): AnyStats => runSync(fstatOp(fd, bigintOf(options))), existsSync(path: PathLike): boolean { let p: string; try { p = toPathString(path); } catch { warnOnce("DEP0187", "Passing invalid argument types to fs.existsSync is deprecated"); return false; } return ctx.trySync("stat", { path: abs(p), follow: true }).ok; }, accessSync: (path: PathLike, _mode?: number): void => runSync(accessOp(toPathString(path))), openSync: (path: PathLike, flags?: unknown, mode?: number): number => runSync(openOp(toPathString(path), parseFlags(flags, "r"), mode ?? 0o666)), readSync(fd: number, buffer: unknown, ...rest: unknown[]): number { const a = readArgs(buffer, rest); return runSync(readOp(fd, a.buffer, a.offset, a.length, a.position)); }, closeSync: (fd: number): void => runSync(closeOp(fd)), realpathSync, readFileSync(path: PathLike | number, options?: unknown): Buffer | string { const { encoding, flags } = readFileArgs(options); const buf = runSync(typeof path === "number" ? readFdToEnd(path) : readFileOp(toPathString(path), flags)); return decodeAs(buf, encoding); }, writeFileSync(path: PathLike | number, data: unknown, options?: unknown): void { const { encoding, flags, mode } = writeFileArgs(options, "w"); runSync(writeFileOp(fileTarget(path), toBytes(data, encoding), flags, mode)); }, appendFileSync(path: PathLike | number, data: unknown, options?: unknown): void { const { encoding, flags, mode } = writeFileArgs(options, "a"); runSync(writeFileOp(fileTarget(path), toBytes(data, encoding), flags, mode)); }, writeSync(fd: number, data: unknown, ...rest: unknown[]): number { const a = writeArgs(data, rest); return runSync(writeAllOp(fd, a.bytes, a.position)); }, readdirSync: (path: PathLike, options?: unknown): string[] | Dirent[] => runSync(readdirOp(toPathString(path), readdirArgs(options))), unlinkSync: (path: PathLike): void => runSync(unlinkOp(toPathString(path))), mkdirSync(path: PathLike, options?: unknown): string | undefined { const { recursive, mode } = mkdirArgs(options); return runSync(mkdirOp(toPathString(path), recursive, mode)); }, rmdirSync: (path: PathLike, options?: unknown): void => runSync(rmdirOp(toPathString(path), rmArgs(options).recursive)), rmSync(path: PathLike, options?: unknown): void { const { recursive, force } = rmArgs(options); runSync(rmOp(toPathString(path), recursive, force)); }, symlinkSync: (target: PathLike, path: PathLike, _type?: unknown): void => runSync(symlinkOp(toPathString(target, "target"), toPathString(path))), readlinkSync: (path: PathLike, options?: unknown): string | Buffer => runSync(readlinkOp(toPathString(path), encodingOf(options))), renameSync: (from: PathLike, to: PathLike): void => runSync(renameOp(toPathString(from, "oldPath"), toPathString(to, "newPath"))), copyFileSync: (src: PathLike, dest: PathLike, mode?: number): void => runSync(copyFileOp(toPathString(src, "src"), toPathString(dest, "dest"), mode ?? 0)), mkdtempSync: (prefix: string): string => runSync(mkdtempOp(String(prefix))), truncateSync, ftruncateSync, utimesSync, lutimesSync, futimesSync, readvSync, writevSync, fsyncSync, fdatasyncSync, chmodSync, lchmodSync, fchmodSync, chownSync, lchownSync, fchownSync, linkSync, statfsSync, opendirSync, globSync, cpSync, mkdtempDisposableSync, }; } /** This thread's live watches, by kernel watch id. */ const watchers = new Map void>(); /** Events the kernel reported for watch `wid` (a closed watch's late events drop). */ export function deliverWatchEvents(wid: number, events: WatchEvent[]): void { watchers.get(wid)?.(events); } const TEMP_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; function randomSuffix(): string { const bytes = crypto.getRandomValues(new Uint8Array(6)); return Array.from(bytes, (b) => TEMP_CHARS[b % TEMP_CHARS.length]).join(""); } /** rm of a directory without `recursive`: Node's SystemError, not a uvException. */ function rmIsDir(path: string): Error { const info = { code: "EISDIR", message: "is a directory", path, syscall: "rm", errno: 21 }; const e = new Error(`Path is a directory: rm returned EISDIR (is a directory) ${path}`); Object.defineProperty(e, "name", { value: "SystemError", enumerable: false, configurable: true, writable: true }); return Object.assign(e, { code: "ERR_FS_EISDIR", info, errno: 21, syscall: "rm", path }); }