UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

191 lines • 7.67 kB
import { type SetAttrFields } from '../../../../types.ts'; import type { MutationJournal } from './journal.ts'; import type { ErrnoCodes, FlushFailure, FSHost, FSNode, FSStream, FSType, SetAttr, SyncVFS } from './types.ts'; /** * The metadata a `setattr` actually changes, or null when it changes * none of it. * * Emscripten hands this callback more than a guest's chmod and utime: * a stamp arrives beside a resize and beside a mode, and a write that * moved no field would still journal an op. Sending one costs a * dispatch and, worse, splits two writes the journal would otherwise * coalesce. Comparing against the node costs one read. * * The one case comparing cannot catch is a create, whose finalizing * chmod does lower a real mode; `PyodideFs.fresh` is what suppresses * that one. * * `ctime` is never included: no POSIX call sets it directly, so a mount * has nothing to write it to. `size` is not metadata here either, it is * the resize branch's bytes. * * Args: * node: the node as it stands before the write. * attr: the fields Emscripten passed, times as epoch ms. */ export declare function changedAttrs(node: FSNode, attr: SetAttr): SetAttrFields | null; /** * An Emscripten filesystem backed by a mirage mount. * * This is the pyodide runtime's interception layer. Mounting it at a mount * prefix puts mirage below the guest's syscall boundary, where every * spelling of an operation (`open`, `os.open`, `pathlib`, `shutil`'s * fd-relative walk) arrives as the same callback. Nothing is patched * inside the interpreter. * * Callbacks are synchronous: lookups, reads and writes wait for the host * through SyncVFS, which needs the worker. A file's stream reads and * writes through a handle on the run's open-file core, which holds its * bytes as every other runtime's open files are held; names and metadata * enter the journal and land at once, in guest order. * * The node table lives in `NodeTable`; what is left here is one method * per Emscripten callback, plus the errno translation only this layer * performs. */ export declare class PyodideFs { private readonly sync; private readonly deferred; private readonly streams; readonly type: FSType; private readonly host; private readonly errno; private readonly journal; private readonly nodes; private readonly mountOf; private fresh; /** * Args: * host: the Emscripten FS namespace (`pyodide.FS`). * errno: Emscripten's errno table (`pyodide.ERRNO_CODES`). * journal: the write-ahead log guest mutations are recorded on. * prefix: the mount prefix this filesystem serves. * mountOf: the mirage mount owning a path (`RuntimeFiles.mountOf`). * One mountpoint serves every mirage mount nested under its * prefix, so this is the only boundary fact left to check ops * against; Emscripten's own cross-mount checks cannot see it. * sync: the worker's blocking way to the mounts. * deferred: the refusals the mount gave a path whose call has not * come yet, shared by every mountpoint as the journal is. * streams: the file streams that hold a core handle, shared the same * way, which the run's end closes. */ constructor(host: FSHost, errno: ErrnoCodes, journal: MutationJournal, prefix: string, mountOf: (path: string) => string | null, sync: SyncVFS, deferred?: Map<string, FlushFailure>, streams?: Set<FSStream>); private getattr; /** * One node's stat, its inode named by `path`. * * Args: * node: the node to describe. * path: the name its inode is reported under. * fh: the core handle a stream stats through, which counts what it * holds; absent stats the node's path. */ private attrOf; private setattr; /** * Resize a file whose name is gone through a stream that writes it: its * bytes are only what the core's handles on it hold. * * Args: * node: the file with no name left. * size: the length it is left at. */ private resizeHeld; private lookup; private mknod; private rename; private unlink; /** * A file whose name the mount removed or replaced. One a stream has open * reads and writes on through the core, which keeps its bytes; marked * only once the mount took the change, so a refused removal leaves it * named. * * Args: * node: the file that lost its name. */ private orphan; private rmdir; /** * The directory's names, asking the mount for any it has gained. * * The first listing is classified, since Emscripten's getdents reads * every name's kind off its node. A node is never re-asked about once * placed, so a later listing asks for names alone and stats only the * names the tree lacks, rather than classifying every entry again. */ private readdir; invalidate(): void; /** * Send the journal to the mount now, in guest order. An entry the mount * refuses fails the call that touched its path with the mount's errno, * this call or that file's next one (its close), as a deferred write * error does; until then nothing more for that path reaches the mount, * and every other file's entries still land. Without a worker there is * no mount to send to yet, and the journal waits for the run's end. * * Args: * paths: the paths this call touched. */ private settle; private readThrough; private placeEntry; /** * Restamp a node placed from an unclassified row with what the mount * said it is. * * The listing that placed it swallowed a failed stat so the rest of * the directory could list; the guest's own stat is where that * failure belongs, so that stat asks the mount rather than answering * from the guess. Its answer turns the node into a directory if that * is what it is. * * Args: * node: the node to classify. * stat: the row the guest's stat got. */ private classifyNode; /** * Create a symlink node and record it for the mounts. * * A link is namespace state, which is exactly why this can be served: * the op reaches the node table rather than a backend, so a link lands * on an s3 or notion mount whose store has no such thing. The target * is stored as typed and never resolved here. * * Args: * parent: directory the link is created in. * name: the link's own name. * target: what it points at, verbatim. */ private symlink; /** * The target of a symlink node. * * Args: * node: the node to read. */ private readlink; /** * Open a file's stream on the run's core, which judges, holds and lands * it as it does every runtime's open files. Emscripten made the open's * create and truncate before it got here; what is left is how the * stream reads and writes. */ private streamOpen; /** * The core handle a file's stream reads and writes through. One a run's * end closed opens again, as long as its file still has a name: the * core held the file for that run only. A guest that exited * (`os._exit`, caught) has no files left. `create` makes the file the * open's own mknod named. */ private handleOf; /** Land what the stream wrote and let its handle go. */ private streamClose; private streamRead; private streamWrite; private llseek; } //# sourceMappingURL=fs.d.ts.map