@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
776 lines • 29.5 kB
JavaScript
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
import { enoent, enotdir } from "../../errors/fs.js";
import { errnoFor } from "../../errors/wasi.js";
import { FileTable } from "../handles/file_table.js";
import { posixStat } from "../stat.js";
import { posixNormpath } from "../../utils/path.js";
import { CHAR_MODE } from "../../utils/stat_view.js";
import { FDFLAG_APPEND, FST_ATIM, FST_ATIM_NOW, FST_MTIM, FST_MTIM_NOW, FT_CHR, FT_DIR, FT_REG, LOOKUP_SYMLINK_FOLLOW, OFLAG_CREAT, OFLAG_DIRECTORY, OFLAG_EXCL, OFLAG_TRUNC, RIGHT_FD_READ, RIGHT_FD_WRITE, } from "./constants.js";
import { EBADF, EINVAL, ELOOP, ENOENT, ENOTDIR, EPIPE, ESPIPE, LINK_REFUSAL, OK } from "./errors.js";
import { packDirent } from "./list.js";
import { filetypeOf, packFdstat, packFilestat, packPrestat } from "./stat.js";
import { INTERRUPTED_STATUS } from "../../shell/constants.js";
const ENC = new TextEncoder();
const DEC = new TextDecoder();
/** What ends a guest from inside a host call: `proc_exit`, or a stop met
* while the guest waited. The run's caller catches it out of `_start`. */
export class GuestExit extends Error {
code;
constructor(code) {
super(`the guest exited ${String(code)}`);
this.code = code;
this.name = 'GuestExit';
}
}
/** One utimensat stamp as ISO text, null when the call omits it. preview1
* splits each of the two stamps into "write the argument" and "write the
* host clock" bits, and neither set is UTIME_OMIT: leave that field alone. */
function stamp(fstFlags, setBit, nowBit, value, now) {
if ((fstFlags & nowBit) !== 0)
return new Date(now).toISOString();
if ((fstFlags & setBit) !== 0)
return new Date(Number(value / 1000000n)).toISOString();
return null;
}
/** The open facts preview1 spells as oflags, rights and fdflags. Only the
* write right lets the descriptor write, as open(2)'s access mode does: a
* read-only open that creates hands out a file that only reads. */
function openMode(oflags, rightsBase, fdflags) {
const create = (oflags & OFLAG_CREAT) !== 0;
const writable = (rightsBase & RIGHT_FD_WRITE) !== 0n;
return {
readable: (rightsBase & RIGHT_FD_READ) !== 0n,
writable,
truncate: writable && (oflags & OFLAG_TRUNC) !== 0,
append: (fdflags & FDFLAG_APPEND) !== 0,
create,
exclusive: create && (oflags & OFLAG_EXCL) !== 0,
binary: true,
};
}
/** A clock reading in nanoseconds: 0 is the epoch clock, 1 monotonic. */
function clockNs(clock) {
const ms = clock === 0 ? performance.timeOrigin + performance.now() : performance.now();
const whole = Math.floor(ms);
return BigInt(whole) * 1000000n + BigInt(Math.round((ms - whole) * 1_000_000));
}
/** One guest fd: a directory, a stdio stream, or a file. Mirrors Python's `FdEntry`. */
class FdEntry {
kind;
file;
pipe;
path;
preopen;
dirents;
opened;
readable;
constructor(kind, file = null, pipe = null, path = '', preopen = false, dirents = null, opened = '', readable = true) {
this.kind = kind;
this.file = file;
this.pipe = pipe;
this.path = path;
this.preopen = preopen;
this.dirents = dirents;
this.opened = opened;
this.readable = readable;
}
/** Read from the position, advancing it. */
read(size) {
if (this.file !== null)
return this.file.read(size);
if (this.pipe !== null)
return size > 0 ? this.pipe(size) : new Uint8Array();
return new Uint8Array();
}
/** Read at an explicit offset without moving the position. */
pread(offset, size) {
return this.file === null ? new Uint8Array() : this.file.pread(offset, size);
}
/** Where the next read or write lands. */
position() {
return this.file === null ? 0 : this.file.position();
}
/** The file's length, what was written and not landed counted. */
size() {
return this.file === null ? 0 : this.file.size();
}
}
/**
* Preview1 host functions over the run's view. One per run: it owns the
* guest fd table (stdin, stdout and stderr plus one preopen at "/"),
* keeps each open file's writes on the host until its close (or the
* guest's exit), and translates the preview1 ABI (iovecs, filestats,
* dirents) for the view. Mirrors Python's `WasiFs`, the args, environment
* and clock calls included, which wasmtime serves natively there.
*
* @param fs - the files the guest sees.
* @param stdin - reads the guest's input as a pipe does; null gives it none.
* @param output - takes each stdout or stderr write as the guest makes it,
* the guest waiting until it is taken; false once its reader has gone.
* @param wait - holds the guest for a poll's delay; true when the run was
* stopped while it waited.
* @param argv - the guest's argv, its program name first.
* @param env - its environment as `NAME=value` entries.
*/
export class WasiFs {
fs;
output;
wait;
argv;
env;
offset;
exitCode = null;
memory = null;
fds = new FileTable(4);
constructor(fs, stdin, output, wait, argv, env,
// Seconds the guest's wall clock runs ahead of the host's: the
// workspace's system_time.
offset = 0) {
this.fs = fs;
this.output = output;
this.wait = wait;
this.argv = argv;
this.env = env;
this.offset = offset;
this.fds.set(0, new FdEntry('stdin', null, stdin ?? (() => new Uint8Array())));
this.fds.set(1, new FdEntry('stdout'));
this.fds.set(2, new FdEntry('stderr'));
this.fds.set(3, new FdEntry('dir', null, null, '/', true));
}
// -- guest memory -------------------------------------------------------
bytes() {
if (this.memory === null)
throw new Error('wasm module exports no memory');
return new Uint8Array(this.memory.buffer);
}
data() {
return new DataView(this.bytes().buffer);
}
load(ptr, n) {
return this.bytes().slice(ptr, ptr + n);
}
store(ptr, data) {
this.bytes().set(data, ptr);
}
storeU32(ptr, value) {
this.data().setUint32(ptr, value, true);
}
storeU64(ptr, value) {
this.data().setBigUint64(ptr, value, true);
}
iovs(ptr, count) {
const view = this.data();
const out = [];
for (let i = 0; i < count; i++)
out.push([view.getUint32(ptr + i * 8, true), view.getUint32(ptr + i * 8 + 4, true)]);
return out;
}
pathArg(dirfd, ptr, length) {
const entry = this.fds.get(dirfd);
if (entry?.kind !== 'dir')
return null;
const rel = DEC.decode(this.load(ptr, length));
if (rel === '')
throw enoent(rel);
const joined = rel.startsWith('/') ? rel : `${entry.path.replace(/\/$/, '')}/${rel}`;
const parts = joined.split('/');
for (const [index, part] of parts.entries()) {
if ((part === '.' || part === '..') && index > 1) {
const ancestor = parts.slice(0, index).join('/');
if (!this.fs.stat(ancestor).isDir)
throw enotdir(ancestor);
}
}
const trailing = joined.endsWith('/') || joined.endsWith('/.');
const normed = posixNormpath(this.fs.walked(joined, false));
if (trailing && normed !== '/') {
const row = this.fs.statOrNull(normed);
if (row !== null && !row.isDir)
throw enotdir(joined);
}
const lexical = posixNormpath(joined);
let requested = parts.includes('..') && normed !== lexical ? joined : lexical;
if (trailing && requested !== '/')
requested = `${requested.replace(/\/+$/, '')}/`;
return requested.startsWith('/') ? requested : `/${requested}`;
}
// -- fd lookups ---------------------------------------------------------
/** The entry under `fd` when it reads: a file's, or stdin. */
readableEntry(fd) {
const entry = this.fds.get(fd);
if (entry === undefined || (entry.kind !== 'file' && entry.kind !== 'stdin'))
return null;
return entry.readable ? entry : null;
}
/** The mount file under `fd` when its open asked to write. */
writer(fd) {
const file = this.fds.get(fd)?.file ?? null;
return file?.mode.writable === true ? file : null;
}
// -- process ------------------------------------------------------------
proc_exit(code) {
this.exitCode = code & 255;
throw new GuestExit(this.exitCode);
}
strings(ptrs, buf, items) {
let at = buf;
for (const [index, item] of items.entries()) {
const encoded = ENC.encode(`${item}\0`);
this.storeU32(ptrs + index * 4, at);
this.store(at, encoded);
at += encoded.byteLength;
}
return OK;
}
sizes(countPtr, sizePtr, items) {
this.storeU32(countPtr, items.length);
this.storeU32(sizePtr, items.reduce((total, item) => total + ENC.encode(item).byteLength + 1, 0));
return OK;
}
args_get(ptrs, buf) {
return this.strings(ptrs, buf, this.argv);
}
args_sizes_get(count, size) {
return this.sizes(count, size, this.argv);
}
environ_get(ptrs, buf) {
return this.strings(ptrs, buf, this.env);
}
environ_sizes_get(count, size) {
return this.sizes(count, size, this.env);
}
random_get(ptr, length) {
const out = new Uint8Array(length);
for (let at = 0; at < length; at += 65536)
crypto.getRandomValues(out.subarray(at, at + 65536));
this.store(ptr, out);
return OK;
}
sched_yield() {
return OK;
}
clock_time_get(clock, _precision, result) {
if (clock !== 0 && clock !== 1)
return EINVAL;
this.storeU64(result, this.now(clock));
return OK;
}
// Nanoseconds on `clock`, the wall clock at the workspace's system time.
now(clock) {
const ns = clockNs(clock);
return clock === 0 && this.offset !== 0 ? ns + BigInt(Math.round(this.offset * 1e9)) : ns;
}
clock_res_get(clock, result) {
if (clock !== 0 && clock !== 1)
return EINVAL;
this.storeU64(result, 1000n);
return OK;
}
poll_oneoff(incoming, outgoing, count, nevents) {
if (count <= 0)
return EINVAL;
const subscriptions = new DataView(this.load(incoming, count * 48).buffer);
const now = clockNs(1);
const events = [];
for (let offset = 0; offset < count * 48; offset += 48) {
const userdata = subscriptions.getBigUint64(offset, true);
const kind = subscriptions.getUint8(offset + 8);
let [error, nbytes, deadline] = [OK, 0, now];
if (kind === 0) {
const clock = subscriptions.getUint32(offset + 16, true);
const timeout = subscriptions.getBigUint64(offset + 24, true);
const flags = subscriptions.getUint16(offset + 40, true);
if ((clock !== 0 && clock !== 1) || (flags & ~1) !== 0)
error = EINVAL;
else {
let remaining = timeout;
if ((flags & 1) !== 0)
remaining -= clock === 0 ? this.now(0) : now;
deadline = now + (remaining > 0n ? remaining : 0n);
}
}
else if (kind === 1 || kind === 2) {
const fd = subscriptions.getUint32(offset + 16, true);
const entry = this.fds.get(fd);
if (entry === undefined || entry.kind === 'dir')
error = EBADF;
else if (kind === 1) {
if ((entry.file === null && entry.pipe === null) || !entry.readable)
error = EBADF;
else
nbytes = Math.max(0, entry.size() - entry.position());
}
else if (entry.kind === 'stdin' ||
(entry.kind === 'file' && entry.file?.mode.writable !== true))
error = EBADF;
}
else
return EINVAL;
const event = new Uint8Array(32);
const view = new DataView(event.buffer);
view.setBigUint64(0, userdata, true);
view.setUint16(8, error, true);
view.setUint8(10, kind);
view.setBigUint64(16, BigInt(nbytes), true);
events.push([deadline, event]);
}
const soonest = events.reduce((least, [at]) => (at < least ? at : least), events[0]?.[0] ?? now);
const delay = soonest - clockNs(1);
if (this.wait(delay > 0n ? Number(delay / 1000000n) : 0)) {
this.exitCode = INTERRUPTED_STATUS;
throw new GuestExit(INTERRUPTED_STATUS);
}
const after = clockNs(1);
const ready = events.filter(([at]) => at <= after).map(([, event]) => event);
let at = outgoing;
for (const event of ready) {
this.store(at, event);
at += event.byteLength;
}
this.storeU32(nevents, ready.length);
return OK;
}
// -- prestat ------------------------------------------------------------
fd_prestat_get(fd, buf) {
const entry = this.fds.get(fd);
if (entry?.preopen !== true)
return EBADF;
this.store(buf, packPrestat(ENC.encode(entry.path).byteLength));
return OK;
}
fd_prestat_dir_name(fd, ptr, length) {
const entry = this.fds.get(fd);
if (entry?.preopen !== true)
return EBADF;
this.store(ptr, ENC.encode(entry.path).subarray(0, length));
return OK;
}
// -- open/close ---------------------------------------------------------
path_open(dirfd, dirflags, ptr, length, oflags, rightsBase, _rightsInherit, fdflags, out) {
const path = this.pathArg(dirfd, ptr, length);
if (path === null)
return EBADF;
if ((dirflags & LOOKUP_SYMLINK_FOLLOW) === 0) {
const row = this.fs.statOrNull(path, true);
if (row?.isLink === true)
return ELOOP;
}
if ((oflags & OFLAG_DIRECTORY) !== 0) {
const st = this.fs.statOrNull(path);
if (st === null)
return ENOENT;
if (!st.isDir)
return ENOTDIR;
return this.openDir(path, out);
}
const mode = openMode(oflags, rightsBase, fdflags);
let file;
try {
file = this.fs.open(path, mode);
}
catch (err) {
// POSIX opens a directory read-only without O_DIRECTORY too; only a
// mode that would write refuses it.
if (err.code !== 'EISDIR' || mode.writable)
throw err;
return this.openDir(path, out);
}
const fd = this.fds.add(new FdEntry('file', file, null, '', false, null, path, mode.readable));
this.storeU32(out, fd);
return OK;
}
openDir(path, out) {
this.storeU32(out, this.fds.add(new FdEntry('dir', null, null, path)));
return OK;
}
fd_close(fd) {
const entry = this.fds.get(fd);
if (entry === undefined || entry.preopen)
return EBADF;
this.fds.pop(fd);
entry.file?.close();
return OK;
}
/**
* Send what every file the guest left open still owes the mount. A
* process's writes are in its files whether or not it closed them, so
* the guest's exit flushes each open file the way a close would. One
* that fails does not stop the rest.
*
* @returns one line per file whose writes could not land.
*/
closeAll() {
const failures = [];
for (const entry of this.fds.values()) {
const file = entry.file;
entry.file = null;
if (file === null)
continue;
try {
file.close();
}
catch (err) {
failures.push(`${entry.opened}: ${err instanceof Error ? err.message : String(err)}`);
}
}
return failures;
}
fd_renumber(fd, to) {
const entry = this.fds.get(fd);
if (entry === undefined)
return EBADF;
if (fd === to)
return OK;
this.fd_close(to);
this.fds.pop(fd);
this.fds.set(to, entry);
return OK;
}
// -- read/write/seek ----------------------------------------------------
scatter(vectors, data, nread) {
let total = 0;
for (const [bptr, blen] of vectors) {
const chunk = data.subarray(total, total + blen);
if (chunk.byteLength > 0)
this.store(bptr, chunk);
total += chunk.byteLength;
if (chunk.byteLength < blen)
break;
}
this.storeU32(nread, total);
return OK;
}
fd_read(fd, iovs, count, nread) {
const entry = this.readableEntry(fd);
if (entry === null)
return EBADF;
const vectors = this.iovs(iovs, count);
const data = entry.read(vectors.reduce((sum, [, length]) => sum + length, 0));
return this.scatter(vectors, data, nread);
}
fd_pread(fd, iovs, count, offset, nread) {
const entry = this.readableEntry(fd);
if (entry?.kind !== 'file')
return EBADF;
const vectors = this.iovs(iovs, count);
const data = entry.pread(Number(offset), vectors.reduce((sum, [, length]) => sum + length, 0));
return this.scatter(vectors, data, nread);
}
fd_write(fd, iovs, count, nwritten) {
const entry = this.fds.get(fd);
if (entry === undefined)
return EBADF;
let total = 0;
for (const [bptr, blen] of this.iovs(iovs, count)) {
const data = this.load(bptr, blen);
if (entry.kind === 'stdout' || entry.kind === 'stderr') {
if (!this.output(entry.kind === 'stdout' ? 1 : 2, data))
return EPIPE;
}
else if (entry.kind === 'file') {
if (entry.file?.mode.writable !== true)
return EBADF;
entry.file.write(data);
}
else
return EINVAL;
total += blen;
}
this.storeU32(nwritten, total);
return OK;
}
fd_pwrite(fd, iovs, count, offset, nwritten) {
const file = this.writer(fd);
if (file === null)
return EBADF;
let [total, pos] = [0, Number(offset)];
for (const [bptr, blen] of this.iovs(iovs, count)) {
file.pwrite(pos, this.load(bptr, blen));
pos += blen;
total += blen;
}
this.storeU32(nwritten, total);
return OK;
}
fd_seek(fd, offset, whence, out) {
if (fd === 0 || fd === 1 || fd === 2)
return ESPIPE;
const entry = this.fds.get(fd);
if (entry?.kind !== 'file' || entry.file === null)
return EBADF;
// preview1's WHENCE_* numbering is POSIX's 0/1/2, which seek speaks.
const pos = entry.file.seek(Number(offset), whence);
if (pos === null)
return EINVAL;
this.storeU64(out, BigInt(pos));
return OK;
}
fd_tell(fd, out) {
if (fd === 0 || fd === 1 || fd === 2)
return ESPIPE;
const entry = this.fds.get(fd);
if (entry?.kind !== 'file')
return EBADF;
this.storeU64(out, BigInt(entry.position()));
return OK;
}
// -- stat ---------------------------------------------------------------
fd_fdstat_get(fd, buf) {
const entry = this.fds.get(fd);
if (entry === undefined)
return EBADF;
const filetype = entry.kind === 'dir' ? FT_DIR : entry.kind === 'file' ? FT_REG : FT_CHR;
this.store(buf, packFdstat(filetype));
return OK;
}
fd_filestat_get(fd, buf) {
const entry = this.fds.get(fd);
if (entry === undefined)
return EBADF;
let packed;
if (entry.kind === 'file' && entry.file !== null) {
// The core's row, its size the handle's, which counts what it wrote
// and has not landed.
const posix = posixStat(entry.file.fstat(), entry.file.inode, '/');
packed = packFilestat(entry.file.unlinked() ? { ...posix, nlink: 0 } : posix, FT_REG);
}
else if (entry.kind === 'dir') {
packed = packFilestat(posixStat(this.fs.stat(entry.path), entry.path, '/'), FT_DIR);
}
else {
const stream = { size: 0, isDir: false, mode: CHAR_MODE };
packed = packFilestat(posixStat(stream, `/dev/fd/${String(fd)}`, '/'), FT_CHR);
}
this.store(buf, packed);
return OK;
}
path_filestat_get(dirfd, flags, ptr, length, buf) {
const path = this.pathArg(dirfd, ptr, length);
if (path === null)
return EBADF;
// The follow bit is how a guest spells stat versus lstat, so it is read
// rather than ignored.
const follow = (flags & LOOKUP_SYMLINK_FOLLOW) !== 0 || path.endsWith('/');
const st = follow ? this.fs.stat(path) : this.fs.lstat(path);
if (path.endsWith('/') && !st.isDir)
return ENOTDIR;
this.store(buf, packFilestat(posixStat(st, path, '/'), filetypeOf(st)));
return OK;
}
fd_filestat_set_size(fd, size) {
const file = this.writer(fd);
if (file === null)
return EBADF;
file.truncate(Number(size));
return OK;
}
// -- readdir ------------------------------------------------------------
fd_readdir(fd, buf, bufLen, cookie, used) {
const entry = this.fds.get(fd);
if (entry?.kind !== 'dir')
return EBADF;
entry.dirents ??= this.fs.readdir(entry.path);
const out = [];
let filled = 0;
for (let i = Number(cookie); i < entry.dirents.length && filled < bufLen; i++) {
const dirent = entry.dirents[i];
if (dirent === undefined)
break;
const [name, filetype] = dirent;
const record = packDirent(i, ENC.encode(name), filetype).subarray(0, bufLen - filled);
out.push(record);
filled += record.byteLength;
}
let at = buf;
for (const record of out) {
this.store(at, record);
at += record.byteLength;
}
this.storeU32(used, filled);
return OK;
}
// -- fs mutation --------------------------------------------------------
path_unlink_file(dirfd, ptr, length) {
const path = this.pathArg(dirfd, ptr, length);
if (path === null)
return EBADF;
this.fs.unlink(path);
return OK;
}
path_create_directory(dirfd, ptr, length) {
const path = this.pathArg(dirfd, ptr, length);
if (path === null)
return EBADF;
this.fs.mkdir(path);
return OK;
}
path_remove_directory(dirfd, ptr, length) {
const path = this.pathArg(dirfd, ptr, length);
if (path === null)
return EBADF;
this.fs.rmdir(path);
return OK;
}
path_rename(dirfd, ptr, length, dstDirfd, dstPtr, dstLength) {
const src = this.pathArg(dirfd, ptr, length);
const dst = this.pathArg(dstDirfd, dstPtr, dstLength);
if (src === null || dst === null)
return EBADF;
this.fs.rename(src, dst);
return OK;
}
// -- stubs and no-ops ---------------------------------------------------
fd_advise() {
return OK;
}
fd_datasync(fd) {
return this.fd_sync(fd);
}
fd_sync(fd) {
const entry = this.fds.get(fd);
if (entry === undefined)
return EBADF;
entry.file?.land();
return OK;
}
fd_fdstat_set_flags() {
return OK;
}
/** Store the stamps a utimensat-shaped call selects. */
setTimes(path, atim, mtim, fstFlags, nofollow) {
const now = Date.now() + this.offset * 1000;
const atime = stamp(fstFlags, FST_ATIM, FST_ATIM_NOW, atim, now);
const mtime = stamp(fstFlags, FST_MTIM, FST_MTIM_NOW, mtim, now);
// No stamp selected is a no-op, not an error: utimensat(2) with two
// UTIME_OMIT values does nothing and succeeds.
if (atime === null && mtime === null)
return;
this.fs.setattr(path, atime, mtime, nofollow);
}
fd_filestat_set_times(fd, atim, mtim, flags) {
const entry = this.fds.get(fd);
if (entry === undefined || (entry.kind !== 'file' && entry.kind !== 'dir'))
return EBADF;
// Writes any fd on the file still owes land first (the core sees to
// it), or landing them at a close would stamp over the times set here.
this.setTimes(entry.file === null ? entry.path : entry.file.path(), atim, mtim, flags, false);
return OK;
}
path_filestat_set_times(dirfd, flags, ptr, length, atim, mtim, fstFlags) {
const path = this.pathArg(dirfd, ptr, length);
if (path === null)
return EBADF;
this.setTimes(path, atim, mtim, fstFlags, (flags & LOOKUP_SYMLINK_FOLLOW) === 0);
return OK;
}
path_readlink(dirfd, ptr, length, buf, bufLen, used) {
const path = this.pathArg(dirfd, ptr, length);
if (path === null)
return EBADF;
const raw = ENC.encode(this.fs.readlink(path)).subarray(0, bufLen);
this.store(buf, raw);
// preview1 truncates rather than refusing a short buffer, and reports
// what it wrote; a guest that wants the whole target retries with a
// bigger one, which is what wasi-libc does.
this.storeU32(used, raw.byteLength);
return OK;
}
path_link() {
// A hard link is a second name for one inode, and nothing above a mount
// holds that. Which refusal that is is decided once for every surface.
return LINK_REFUSAL;
}
path_symlink(oldPtr, oldLength, dirfd, newPtr, newLength) {
// The old pair is the target string, not a path to resolve: a link
// stores what was typed.
const target = DEC.decode(this.load(oldPtr, oldLength));
const path = this.pathArg(dirfd, newPtr, newLength);
if (path === null)
return EBADF;
this.fs.symlink(path, target);
return OK;
}
}
// The preview1 imports `wasiImports` serves; the rest a guest imports are
// absent, so a module that needs one fails to link rather than run wrong.
const CALLS = [
'args_get',
'args_sizes_get',
'environ_get',
'environ_sizes_get',
'random_get',
'sched_yield',
'clock_time_get',
'clock_res_get',
'poll_oneoff',
'fd_advise',
'fd_close',
'fd_datasync',
'fd_fdstat_get',
'fd_fdstat_set_flags',
'fd_filestat_get',
'fd_filestat_set_size',
'fd_filestat_set_times',
'fd_pread',
'fd_prestat_get',
'fd_prestat_dir_name',
'fd_pwrite',
'fd_read',
'fd_readdir',
'fd_renumber',
'fd_seek',
'fd_sync',
'fd_tell',
'fd_write',
'path_create_directory',
'path_filestat_get',
'path_filestat_set_times',
'path_link',
'path_open',
'path_readlink',
'path_remove_directory',
'path_rename',
'path_symlink',
'path_unlink_file',
];
/**
* The guest's preview1 imports over one run's `WasiFs`, each mapping a
* failure to a guest errno. Filesystem-shaped errors get their own errno;
* anything else, a backend's upstream error on one record most often, is
* EIO: a syscall has no other channel to say it failed. A `GuestExit`
* passes through, so it ends `_start`. Mirrors Python's `install_wasi_fs`.
*
* @param wasi - the run's host-function table.
*/
export function wasiImports(wasi) {
const calls = {
proc_exit: (code) => wasi.proc_exit(code),
};
for (const name of CALLS) {
const method = wasi[name];
calls[name] = (...args) => {
try {
return method.apply(wasi, args);
}
catch (err) {
if (err instanceof GuestExit)
throw err;
return errnoFor(err);
}
};
}
return { wasi_snapshot_preview1: calls };
}
//# sourceMappingURL=fs.js.map