import debug from "debug";
const log = debug("wasi");
const logOpen = debug("wasi:open");
const SC_OPEN_MAX = 32768;
import type {
WASIBindings,
WASIArgs,
WASIEnv,
WASIPreopenedDirs,
WASIConfig,
File,
} from "./types";
import { WASIError } from "./types";
import toBuffer from "typedarray-to-buffer";
import {
WASI_ESUCCESS,
WASI_EBADF,
WASI_EINVAL,
WASI_ENOSYS,
WASI_EPERM,
WASI_FILETYPE_UNKNOWN,
WASI_FILETYPE_BLOCK_DEVICE,
WASI_FILETYPE_CHARACTER_DEVICE,
WASI_FILETYPE_DIRECTORY,
WASI_FILETYPE_REGULAR_FILE,
WASI_FILETYPE_SOCKET_STREAM,
WASI_FILETYPE_SYMBOLIC_LINK,
WASI_FILETYPE,
WASI_FDFLAG_APPEND,
WASI_FDFLAG_DSYNC,
WASI_FDFLAG_NONBLOCK,
WASI_FDFLAG_RSYNC,
WASI_FDFLAG_SYNC,
WASI_RIGHT_FD_DATASYNC,
WASI_RIGHT_FD_READ,
WASI_RIGHT_FD_SEEK,
WASI_RIGHT_FD_FDSTAT_SET_FLAGS,
WASI_RIGHT_FD_SYNC,
WASI_RIGHT_FD_TELL,
WASI_RIGHT_FD_WRITE,
WASI_RIGHT_FD_ADVISE,
WASI_RIGHT_FD_ALLOCATE,
WASI_RIGHT_PATH_CREATE_DIRECTORY,
WASI_RIGHT_PATH_CREATE_FILE,
WASI_RIGHT_PATH_LINK_SOURCE,
WASI_RIGHT_PATH_LINK_TARGET,
WASI_RIGHT_PATH_OPEN,
WASI_RIGHT_FD_READDIR,
WASI_RIGHT_PATH_READLINK,
WASI_RIGHT_PATH_RENAME_SOURCE,
WASI_RIGHT_PATH_RENAME_TARGET,
WASI_RIGHT_PATH_FILESTAT_GET,
WASI_RIGHT_PATH_FILESTAT_SET_SIZE,
WASI_RIGHT_PATH_FILESTAT_SET_TIMES,
WASI_RIGHT_FD_FILESTAT_GET,
WASI_RIGHT_FD_FILESTAT_SET_SIZE,
WASI_RIGHT_FD_FILESTAT_SET_TIMES,
WASI_RIGHT_PATH_SYMLINK,
WASI_RIGHT_PATH_REMOVE_DIRECTORY,
WASI_RIGHT_POLL_FD_READWRITE,
WASI_RIGHT_PATH_UNLINK_FILE,
RIGHTS_BLOCK_DEVICE_BASE,
RIGHTS_BLOCK_DEVICE_INHERITING,
RIGHTS_CHARACTER_DEVICE_BASE,
RIGHTS_CHARACTER_DEVICE_INHERITING,
RIGHTS_REGULAR_FILE_BASE,
RIGHTS_REGULAR_FILE_INHERITING,
RIGHTS_DIRECTORY_BASE,
RIGHTS_DIRECTORY_INHERITING,
RIGHTS_SOCKET_BASE,
RIGHTS_SOCKET_INHERITING,
RIGHTS_TTY_BASE,
RIGHTS_TTY_INHERITING,
WASI_CLOCK_MONOTONIC,
WASI_CLOCK_PROCESS_CPUTIME_ID,
WASI_CLOCK_REALTIME,
WASI_CLOCK_THREAD_CPUTIME_ID,
WASI_EVENTTYPE_CLOCK,
WASI_EVENTTYPE_FD_READ,
WASI_EVENTTYPE_FD_WRITE,
WASI_FILESTAT_SET_ATIM,
WASI_FILESTAT_SET_ATIM_NOW,
WASI_FILESTAT_SET_MTIM,
WASI_FILESTAT_SET_MTIM_NOW,
WASI_O_CREAT,
WASI_O_DIRECTORY,
WASI_O_EXCL,
WASI_O_TRUNC,
WASI_PREOPENTYPE_DIR,
WASI_STDIN_FILENO,
WASI_STDOUT_FILENO,
WASI_STDERR_FILENO,
ERROR_MAP,
SIGNAL_MAP,
WASI_WHENCE_CUR,
WASI_WHENCE_END,
WASI_WHENCE_SET,
} from "./constants";
const STDIN_DEFAULT_RIGHTS =
WASI_RIGHT_FD_DATASYNC |
WASI_RIGHT_FD_READ |
WASI_RIGHT_FD_SYNC |
WASI_RIGHT_FD_ADVISE |
WASI_RIGHT_FD_FILESTAT_GET |
WASI_RIGHT_POLL_FD_READWRITE;
const STDOUT_DEFAULT_RIGHTS =
WASI_RIGHT_FD_DATASYNC |
WASI_RIGHT_FD_WRITE |
WASI_RIGHT_FD_SYNC |
WASI_RIGHT_FD_ADVISE |
WASI_RIGHT_FD_FILESTAT_GET |
WASI_RIGHT_POLL_FD_READWRITE;
const STDERR_DEFAULT_RIGHTS = STDOUT_DEFAULT_RIGHTS;
export const SOCKET_DEFAULT_RIGHTS =
WASI_RIGHT_FD_DATASYNC |
WASI_RIGHT_FD_READ |
WASI_RIGHT_FD_WRITE |
WASI_RIGHT_FD_ADVISE |
WASI_RIGHT_FD_FILESTAT_GET |
WASI_RIGHT_POLL_FD_READWRITE |
WASI_RIGHT_FD_FDSTAT_SET_FLAGS;
const msToNs = (ms: number) => {
const msInt = Math.trunc(ms);
const decimal = BigInt(Math.round((ms - msInt) * 1000000));
const ns = BigInt(msInt) * BigInt(1000000);
return ns + decimal;
};
const nsToMs = (ns: number | bigint) => {
if (typeof ns === "number") {
ns = Math.trunc(ns);
}
const nsInt = BigInt(ns);
return Number(nsInt / BigInt(1000000));
};
const wrap =
<T extends Function>(f: T) =>
(...args: any[]) => {
try {
return f(...args);
} catch (err) {
let e: any = err;
while (e.prev != null) {
e = e.prev;
}
if (e?.code && typeof e?.code === "string") {
return ERROR_MAP[e.code] || WASI_EINVAL;
}
if (e instanceof WASIError) {
return e.errno;
}
throw e;
}
};
const stat = (wasi: WASI, fd: number): File => {
const entry = wasi.FD_MAP.get(fd);
if (!entry) {
throw new WASIError(WASI_EBADF);
}
if (entry.filetype === undefined) {
const stats = wasi.fstatSync(entry.real);
const { filetype, rightsBase, rightsInheriting } = translateFileAttributes(
wasi,
fd,
stats
);
entry.filetype = filetype as WASI_FILETYPE;
if (!entry.rights) {
entry.rights = {
base: rightsBase,
inheriting: rightsInheriting,
};
}
}
return entry;
};
const translateFileAttributes = (
wasi: WASI,
fd: number | undefined,
stats: any
) => {
switch (true) {
case stats.isBlockDevice():
return {
filetype: WASI_FILETYPE_BLOCK_DEVICE,
rightsBase: RIGHTS_BLOCK_DEVICE_BASE,
rightsInheriting: RIGHTS_BLOCK_DEVICE_INHERITING,
};
case stats.isCharacterDevice(): {
const filetype = WASI_FILETYPE_CHARACTER_DEVICE;
if (fd !== undefined && wasi.bindings.isTTY(fd)) {
return {
filetype,
rightsBase: RIGHTS_TTY_BASE,
rightsInheriting: RIGHTS_TTY_INHERITING,
};
}
return {
filetype,
rightsBase: RIGHTS_CHARACTER_DEVICE_BASE,
rightsInheriting: RIGHTS_CHARACTER_DEVICE_INHERITING,
};
}
case stats.isDirectory():
return {
filetype: WASI_FILETYPE_DIRECTORY,
rightsBase: RIGHTS_DIRECTORY_BASE,
rightsInheriting: RIGHTS_DIRECTORY_INHERITING,
};
case stats.isFIFO():
return {
filetype: WASI_FILETYPE_SOCKET_STREAM,
rightsBase: RIGHTS_SOCKET_BASE,
rightsInheriting: RIGHTS_SOCKET_INHERITING,
};
case stats.isFile():
return {
filetype: WASI_FILETYPE_REGULAR_FILE,
rightsBase: RIGHTS_REGULAR_FILE_BASE,
rightsInheriting: RIGHTS_REGULAR_FILE_INHERITING,
};
case stats.isSocket():
return {
filetype: WASI_FILETYPE_SOCKET_STREAM,
rightsBase: RIGHTS_SOCKET_BASE,
rightsInheriting: RIGHTS_SOCKET_INHERITING,
};
case stats.isSymbolicLink():
return {
filetype: WASI_FILETYPE_SYMBOLIC_LINK,
rightsBase: BigInt(0),
rightsInheriting: BigInt(0),
};
default:
return {
filetype: WASI_FILETYPE_UNKNOWN,
rightsBase: BigInt(0),
rightsInheriting: BigInt(0),
};
}
};
type Exports = {
[key: string]: any;
};
let warnedAboutSleep = false;
interface State {
env: WASIEnv;
FD_MAP: Map<number, File>;
bindings: WASIBindings;
}
export default class WASI {
memory: WebAssembly.Memory;
view: DataView;
FD_MAP: Map<number, File>;
wasiImport: Exports;
bindings: WASIBindings;
sleep?: (milliseconds: number) => void;
lastStdin: number = 0;
getStdin?: (milliseconds?: number) => Buffer;
stdinBuffer?: Buffer;
sendStdout?: (Buffer) => void;
sendStderr?: (Buffer) => void;
env: WASIEnv = {};
getState(): State {
return { env: this.env, FD_MAP: this.FD_MAP, bindings: this.bindings };
}
setState(state: State) {
this.env = state.env;
this.FD_MAP = state.FD_MAP;
this.bindings = state.bindings;
}
fstatSync(real_fd: number) {
if (real_fd <= 2) {
try {
return this.bindings.fs.fstatSync(real_fd);
} catch (_) {
const now = new Date();
return {
dev: 0,
mode: 8592,
nlink: 1,
uid: 0,
gid: 0,
rdev: 0,
blksize: 65536,
ino: 0,
size: 0,
blocks: 0,
atimeMs: now.valueOf(),
mtimeMs: now.valueOf(),
ctimeMs: now.valueOf(),
birthtimeMs: 0,
atime: new Date(),
mtime: new Date(),
ctime: new Date(),
birthtime: new Date(0),
};
}
}
return this.bindings.fs.fstatSync(real_fd);
}
constructor(wasiConfig: WASIConfig) {
this.sleep = wasiConfig.sleep;
this.getStdin = wasiConfig.getStdin;
this.sendStdout = wasiConfig.sendStdout;
this.sendStderr = wasiConfig.sendStderr;
let preopens: WASIPreopenedDirs = {};
if (wasiConfig.preopens) {
preopens = wasiConfig.preopens;
}
if (wasiConfig && wasiConfig.env) {
this.env = wasiConfig.env;
}
let args: WASIArgs = [];
if (wasiConfig && wasiConfig.args) {
args = wasiConfig.args;
}
this.memory = undefined;
this.view = undefined;
this.bindings = wasiConfig.bindings;
const fs = this.bindings.fs;
this.FD_MAP = new Map([
[
WASI_STDIN_FILENO,
{
real: 0,
filetype: WASI_FILETYPE_CHARACTER_DEVICE,
rights: {
base: STDIN_DEFAULT_RIGHTS,
inheriting: BigInt(0),
},
path: "/dev/stdin",
},
],
[
WASI_STDOUT_FILENO,
{
real: 1,
filetype: WASI_FILETYPE_CHARACTER_DEVICE,
rights: {
base: STDOUT_DEFAULT_RIGHTS,
inheriting: BigInt(0),
},
path: "/dev/stdout",
},
],
[
WASI_STDERR_FILENO,
{
real: 2,
filetype: WASI_FILETYPE_CHARACTER_DEVICE,
rights: {
base: STDERR_DEFAULT_RIGHTS,
inheriting: BigInt(0),
},
path: "/dev/stderr",
},
],
]);
const path = this.bindings.path;
for (const [k, v] of Object.entries(preopens)) {
const real = fs.openSync(v, fs.constants.O_RDONLY);
const newfd = this.getUnusedFileDescriptor();
this.FD_MAP.set(newfd, {
real,
filetype: WASI_FILETYPE_DIRECTORY,
rights: {
base: RIGHTS_DIRECTORY_BASE,
inheriting: RIGHTS_DIRECTORY_INHERITING,
},
fakePath: k,
path: v,
});
}
const getiovs = (iovs: number, iovsLen: number) => {
this.refreshMemory();
const buffers = Array.from({ length: iovsLen }, (_, i) => {
const ptr = iovs + i * 8;
const buf = this.view.getUint32(ptr, true);
let bufLen = this.view.getUint32(ptr + 4, true);
if (bufLen > this.memory.buffer.byteLength - buf) {
log("getiovs: warning -- truncating buffer to fit in memory");
bufLen = Math.min(
bufLen,
Math.max(0, this.memory.buffer.byteLength - buf)
);
}
try {
const buffer = new Uint8Array(this.memory.buffer, buf, bufLen);
return toBuffer(buffer);
} catch (err) {
console.warn("WASI.getiovs -- invalid buffer", err);
throw new WASIError(WASI_EINVAL);
}
});
return buffers;
};
const CHECK_FD = (fd: number, rights: bigint) => {
const stats = stat(this, fd);
if (rights !== BigInt(0) && (stats.rights.base & rights) === BigInt(0)) {
throw new WASIError(WASI_EPERM);
}
return stats;
};
const CPUTIME_START = this.bindings.hrtime();
const now = (clockId?: number) => {
switch (clockId) {
case WASI_CLOCK_MONOTONIC:
return this.bindings.hrtime();
case WASI_CLOCK_REALTIME:
return msToNs(Date.now());
case WASI_CLOCK_PROCESS_CPUTIME_ID:
case WASI_CLOCK_THREAD_CPUTIME_ID:
return this.bindings.hrtime() - CPUTIME_START;
default:
return null;
}
};
this.wasiImport = {
args_get: (argv: number, argvBuf: number) => {
this.refreshMemory();
let coffset = argv;
let offset = argvBuf;
args.forEach((a) => {
this.view.setUint32(coffset, offset, true);
coffset += 4;
offset += Buffer.from(this.memory.buffer).write(`${a}\0`, offset);
});
return WASI_ESUCCESS;
},
args_sizes_get: (argc: number, argvBufSize: number) => {
this.refreshMemory();
this.view.setUint32(argc, args.length, true);
const size = args.reduce((acc, a) => acc + Buffer.byteLength(a) + 1, 0);
this.view.setUint32(argvBufSize, size, true);
return WASI_ESUCCESS;
},
environ_get: (environ: number, environBuf: number) => {
this.refreshMemory();
let coffset = environ;
let offset = environBuf;
Object.entries(this.env).forEach(([key, value]) => {
this.view.setUint32(coffset, offset, true);
coffset += 4;
offset += Buffer.from(this.memory.buffer).write(
`${key}=${value}\0`,
offset
);
});
return WASI_ESUCCESS;
},
environ_sizes_get: (environCount: number, environBufSize: number) => {
this.refreshMemory();
const envProcessed = Object.entries(this.env).map(
([key, value]) => `${key}=${value}\0`
);
const size = envProcessed.reduce(
(acc, e) => acc + Buffer.byteLength(e),
0
);
this.view.setUint32(environCount, envProcessed.length, true);
this.view.setUint32(environBufSize, size, true);
return WASI_ESUCCESS;
},
clock_res_get: (clockId: number, resolution: number) => {
let res;
switch (clockId) {
case WASI_CLOCK_MONOTONIC:
case WASI_CLOCK_PROCESS_CPUTIME_ID:
case WASI_CLOCK_THREAD_CPUTIME_ID: {
res = BigInt(1);
break;
}
case WASI_CLOCK_REALTIME: {
res = BigInt(1000);
break;
}
}
if (!res) {
throw Error("invalid clockId");
}
this.view.setBigUint64(resolution, res);
return WASI_ESUCCESS;
},
clock_time_get: (clockId: number, _precision: number, time: number) => {
this.refreshMemory();
const n = now(clockId);
if (n === null) {
return WASI_EINVAL;
}
this.view.setBigUint64(time, BigInt(n), true);
return WASI_ESUCCESS;
},
fd_advise: wrap(
(fd: number, _offset: number, _len: number, _advice: number) => {
CHECK_FD(fd, WASI_RIGHT_FD_ADVISE);
return WASI_ENOSYS;
}
),
fd_allocate: wrap((fd: number, _offset: number, _len: number) => {
CHECK_FD(fd, WASI_RIGHT_FD_ALLOCATE);
return WASI_ENOSYS;
}),
fd_close: wrap((fd: number) => {
const stats = CHECK_FD(fd, BigInt(0));
fs.closeSync(stats.real);
this.FD_MAP.delete(fd);
return WASI_ESUCCESS;
}),
fd_datasync: wrap((fd: number) => {
const stats = CHECK_FD(fd, WASI_RIGHT_FD_DATASYNC);
fs.fdatasyncSync(stats.real);
return WASI_ESUCCESS;
}),
fd_fdstat_get: wrap((fd: number, bufPtr: number) => {
const stats = CHECK_FD(fd, BigInt(0));
this.refreshMemory();
if (stats.filetype == null) {
throw Error("stats.filetype must be set");
}
this.view.setUint8(bufPtr, stats.filetype);
this.view.setUint16(bufPtr + 2, 0, true);
this.view.setUint16(bufPtr + 4, 0, true);
this.view.setBigUint64(bufPtr + 8, BigInt(stats.rights.base), true);
this.view.setBigUint64(
bufPtr + 8 + 8,
BigInt(stats.rights.inheriting),
true
);
return WASI_ESUCCESS;
}),
fd_fdstat_set_flags: wrap((fd: number, flags: number) => {
CHECK_FD(fd, WASI_RIGHT_FD_FDSTAT_SET_FLAGS);
if (this.wasiImport.sock_fcntlSetFlags(fd, flags) == 0) {
return WASI_ESUCCESS;
}
return WASI_ENOSYS;
}),
fd_fdstat_set_rights: wrap(
(fd: number, fsRightsBase: bigint, fsRightsInheriting: bigint) => {
const stats = CHECK_FD(fd, BigInt(0));
const nrb = stats.rights.base | fsRightsBase;
if (nrb > stats.rights.base) {
return WASI_EPERM;
}
const nri = stats.rights.inheriting | fsRightsInheriting;
if (nri > stats.rights.inheriting) {
return WASI_EPERM;
}
stats.rights.base = fsRightsBase;
stats.rights.inheriting = fsRightsInheriting;
return WASI_ESUCCESS;
}
),
fd_filestat_get: wrap((fd: number, bufPtr: number) => {
const stats = CHECK_FD(fd, WASI_RIGHT_FD_FILESTAT_GET);
const rstats = this.fstatSync(stats.real);
this.refreshMemory();
this.view.setBigUint64(bufPtr, BigInt(rstats.dev), true);
bufPtr += 8;
this.view.setBigUint64(bufPtr, BigInt(rstats.ino), true);
bufPtr += 8;
if (stats.filetype == null) {
throw Error("stats.filetype must be set");
}
this.view.setUint8(bufPtr, stats.filetype);
bufPtr += 8;
this.view.setBigUint64(bufPtr, BigInt(rstats.nlink), true);
bufPtr += 8;
this.view.setBigUint64(bufPtr, BigInt(rstats.size), true);
bufPtr += 8;
this.view.setBigUint64(bufPtr, msToNs(rstats.atimeMs), true);
bufPtr += 8;
this.view.setBigUint64(bufPtr, msToNs(rstats.mtimeMs), true);
bufPtr += 8;
this.view.setBigUint64(bufPtr, msToNs(rstats.ctimeMs), true);
return WASI_ESUCCESS;
}),
fd_filestat_set_size: wrap((fd: number, stSize: number) => {
const stats = CHECK_FD(fd, WASI_RIGHT_FD_FILESTAT_SET_SIZE);
fs.ftruncateSync(stats.real, Number(stSize));
return WASI_ESUCCESS;
}),
fd_filestat_set_times: wrap(
(fd: number, stAtim: number, stMtim: number, fstflags: number) => {
const stats = CHECK_FD(fd, WASI_RIGHT_FD_FILESTAT_SET_TIMES);
const rstats = this.fstatSync(stats.real);
let atim = rstats.atime;
let mtim = rstats.mtime;
const n = nsToMs(now(WASI_CLOCK_REALTIME)!);
const atimflags = WASI_FILESTAT_SET_ATIM | WASI_FILESTAT_SET_ATIM_NOW;
if ((fstflags & atimflags) === atimflags) {
return WASI_EINVAL;
}
const mtimflags = WASI_FILESTAT_SET_MTIM | WASI_FILESTAT_SET_MTIM_NOW;
if ((fstflags & mtimflags) === mtimflags) {
return WASI_EINVAL;
}
if ((fstflags & WASI_FILESTAT_SET_ATIM) === WASI_FILESTAT_SET_ATIM) {
atim = nsToMs(stAtim);
} else if (
(fstflags & WASI_FILESTAT_SET_ATIM_NOW) ===
WASI_FILESTAT_SET_ATIM_NOW
) {
atim = n;
}
if ((fstflags & WASI_FILESTAT_SET_MTIM) === WASI_FILESTAT_SET_MTIM) {
mtim = nsToMs(stMtim);
} else if (
(fstflags & WASI_FILESTAT_SET_MTIM_NOW) ===
WASI_FILESTAT_SET_MTIM_NOW
) {
mtim = n;
}
fs.futimesSync(stats.real, new Date(atim), new Date(mtim));
return WASI_ESUCCESS;
}
),
fd_prestat_get: wrap((fd: number, bufPtr: number) => {
const stats = CHECK_FD(fd, BigInt(0));
this.refreshMemory();
this.view.setUint8(bufPtr, WASI_PREOPENTYPE_DIR);
this.view.setUint32(
bufPtr + 4,
Buffer.byteLength(stats.fakePath ?? stats.path ?? ""),
true
);
return WASI_ESUCCESS;
}),
fd_prestat_dir_name: wrap(
(fd: number, pathPtr: number, pathLen: number) => {
const stats = CHECK_FD(fd, BigInt(0));
this.refreshMemory();
Buffer.from(this.memory.buffer).write(
stats.fakePath ?? stats.path ?? "" ,
pathPtr,
pathLen,
"utf8"
);
return WASI_ESUCCESS;
}
),
fd_pwrite: wrap(
(
fd: number,
iovs: number,
iovsLen: number,
offset: number,
nwritten: number
) => {
const stats = CHECK_FD(fd, WASI_RIGHT_FD_WRITE | WASI_RIGHT_FD_SEEK);
let written = 0;
getiovs(iovs, iovsLen).forEach((iov) => {
let w = 0;
while (w < iov.byteLength) {
w += fs.writeSync(
stats.real,
iov,
w,
iov.byteLength - w,
Number(offset) + written + w
);
}
written += w;
});
this.view.setUint32(nwritten, written, true);
return WASI_ESUCCESS;
}
),
fd_write: wrap(
(fd: number, iovs: number, iovsLen: number, nwritten: number) => {
const stats = CHECK_FD(fd, WASI_RIGHT_FD_WRITE);
const IS_STDOUT = fd == WASI_STDOUT_FILENO;
const IS_STDERR = fd == WASI_STDERR_FILENO;
let written = 0;
getiovs(iovs, iovsLen).forEach((iov) => {
if (iov.byteLength == 0) return;
if (IS_STDOUT && this.sendStdout != null) {
this.sendStdout(iov);
written += iov.byteLength;
} else if (IS_STDERR && this.sendStderr != null) {
this.sendStderr(iov);
written += iov.byteLength;
} else {
let w = 0;
while (w < iov.byteLength) {
const i = fs.writeSync(
stats.real,
iov,
w,
iov.byteLength - w,
stats.offset ? Number(stats.offset) : null
);
if (stats.offset) stats.offset += BigInt(i);
w += i;
}
written += w;
}
});
this.view.setUint32(nwritten, written, true);
return WASI_ESUCCESS;
}
),
fd_pread: wrap(
(
fd: number,
iovs: number,
iovsLen: number,
offset: number,
nread: number
) => {
const stats = CHECK_FD(fd, WASI_RIGHT_FD_READ | WASI_RIGHT_FD_SEEK);
let read = 0;
outer: for (const iov of getiovs(iovs, iovsLen)) {
let r = 0;
while (r < iov.byteLength) {
const length = iov.byteLength - r;
const rr = fs.readSync(
stats.real,
iov,
r,
iov.byteLength - r,
Number(offset) + read + r
);
r += rr;
read += rr;
if (rr === 0 || rr < length) {
break outer;
}
}
read += r;
}
this.view.setUint32(nread, read, true);
return WASI_ESUCCESS;
}
),
fd_read: wrap(
(fd: number, iovs: number, iovsLen: number, nread: number) => {
const stats = CHECK_FD(fd, WASI_RIGHT_FD_READ);
const IS_STDIN = fd == WASI_STDIN_FILENO;
let read = 0;
outer: for (const iov of getiovs(iovs, iovsLen)) {
let r = 0;
while (r < iov.byteLength) {
let length = iov.byteLength - r;
let position =
IS_STDIN || stats.offset === undefined
? null
: Number(stats.offset);
let rr = 0;
if (IS_STDIN) {
if (this.getStdin != null) {
if (this.stdinBuffer == null) {
this.stdinBuffer = this.getStdin();
}
if (this.stdinBuffer != null) {
rr = this.stdinBuffer.copy(iov);
if (rr == this.stdinBuffer.length) {
this.stdinBuffer = undefined;
} else {
this.stdinBuffer = this.stdinBuffer.slice(rr);
}
if (rr > 0) {
this.lastStdin = new Date().valueOf();
}
}
} else {
if (this.sleep == null && !warnedAboutSleep) {
warnedAboutSleep = true;
console.log(
"(cpu waiting for stdin: please define a way to sleep!) "
);
}
try {
rr = fs.readSync(
stats.real,
iov,
r,
length,
position
);
} catch (_err) {}
if (rr == 0) {
this.shortPause();
} else {
this.lastStdin = new Date().valueOf();
}
}
} else {
rr = fs.readSync(
stats.real,
iov,
r,
length,
position
);
}
if (stats.filetype == WASI_FILETYPE_REGULAR_FILE) {
stats.offset =
(stats.offset ? stats.offset : BigInt(0)) + BigInt(rr);
}
r += rr;
read += rr;
if (rr === 0 || rr < length) {
break outer;
}
}
}
this.view.setUint32(nread, read, true);
return WASI_ESUCCESS;
}
),
fd_readdir: wrap(
(
fd: number,
bufPtr: number,
bufLen: number,
cookie: number,
bufusedPtr: number
) => {
const stats = CHECK_FD(fd, WASI_RIGHT_FD_READDIR);
this.refreshMemory();
const entries = fs.readdirSync(stats.path, { withFileTypes: true });
const startPtr = bufPtr;
for (let i = Number(cookie); i < entries.length; i += 1) {
const entry = entries[i];
let nameLength = Buffer.byteLength(entry.name);
if (bufPtr - startPtr > bufLen) {
break;
}
this.view.setBigUint64(bufPtr, BigInt(i + 1), true);
bufPtr += 8;
if (bufPtr - startPtr > bufLen) {
break;
}
const rstats = fs.lstatSync(path.resolve(stats.path, entry.name));
this.view.setBigUint64(bufPtr, BigInt(rstats.ino), true);
bufPtr += 8;
if (bufPtr - startPtr > bufLen) {
break;
}
this.view.setUint32(bufPtr, nameLength, true);
bufPtr += 4;
if (bufPtr - startPtr > bufLen) {
break;
}
let filetype;
switch (true) {
case rstats.isBlockDevice():
filetype = WASI_FILETYPE_BLOCK_DEVICE;
break;
case rstats.isCharacterDevice():
filetype = WASI_FILETYPE_CHARACTER_DEVICE;
break;
case rstats.isDirectory():
filetype = WASI_FILETYPE_DIRECTORY;
break;
case rstats.isFIFO():
filetype = WASI_FILETYPE_SOCKET_STREAM;
break;
case rstats.isFile():
filetype = WASI_FILETYPE_REGULAR_FILE;
break;
case rstats.isSocket():
filetype = WASI_FILETYPE_SOCKET_STREAM;
break;
case rstats.isSymbolicLink():
filetype = WASI_FILETYPE_SYMBOLIC_LINK;
break;
default:
filetype = WASI_FILETYPE_UNKNOWN;
break;
}
this.view.setUint8(bufPtr, filetype);
bufPtr += 1;
bufPtr += 3;
if (bufPtr + nameLength >= startPtr + bufLen) {
break;
}
let memory_buffer = Buffer.from(this.memory.buffer);
memory_buffer.write(entry.name, bufPtr);
bufPtr += nameLength;
}
const bufused = bufPtr - startPtr;
this.view.setUint32(bufusedPtr, Math.min(bufused, bufLen), true);
return WASI_ESUCCESS;
}
),
fd_renumber: wrap((from: number, to: number) => {
CHECK_FD(from, BigInt(0));
CHECK_FD(to, BigInt(0));
fs.closeSync((this.FD_MAP.get(from) as File).real);
this.FD_MAP.set(from, this.FD_MAP.get(to) as File);
this.FD_MAP.delete(to);
return WASI_ESUCCESS;
}),
fd_seek: wrap(
(
fd: number,
offset: number | bigint,
whence: number,
newOffsetPtr: number
) => {
const stats = CHECK_FD(fd, WASI_RIGHT_FD_SEEK);
this.refreshMemory();
switch (whence) {
case WASI_WHENCE_CUR:
stats.offset =
(stats.offset ? stats.offset : BigInt(0)) + BigInt(offset);
break;
case WASI_WHENCE_END:
const { size } = this.fstatSync(stats.real);
stats.offset = BigInt(size) + BigInt(offset);
break;
case WASI_WHENCE_SET:
stats.offset = BigInt(offset);
break;
}
if (stats.offset == null) {
throw Error("stats.offset must be defined");
}
this.view.setBigUint64(newOffsetPtr, stats.offset, true);
return WASI_ESUCCESS;
}
),
fd_tell: wrap((fd: number, offsetPtr: number) => {
const stats = CHECK_FD(fd, WASI_RIGHT_FD_TELL);
this.refreshMemory();
if (!stats.offset) {
stats.offset = BigInt(0);
}
this.view.setBigUint64(offsetPtr, stats.offset, true);
return WASI_ESUCCESS;
}),
fd_sync: wrap((fd: number) => {
const stats = CHECK_FD(fd, WASI_RIGHT_FD_SYNC);
fs.fsyncSync(stats.real);
return WASI_ESUCCESS;
}),
path_create_directory: wrap(
(fd: number, pathPtr: number, pathLen: number) => {
const stats = CHECK_FD(fd, WASI_RIGHT_PATH_CREATE_DIRECTORY);
if (!stats.path) {
return WASI_EINVAL;
}
this.refreshMemory();
const p = Buffer.from(
this.memory.buffer,
pathPtr,
pathLen
).toString();
fs.mkdirSync(path.resolve(stats.path, p));
return WASI_ESUCCESS;
}
),
path_filestat_get: wrap(
(
fd: number,
flags: number,
pathPtr: number,
pathLen: number,
bufPtr: number
) => {
const stats = CHECK_FD(fd, WASI_RIGHT_PATH_FILESTAT_GET);
if (!stats.path) {
return WASI_EINVAL;
}
this.refreshMemory();
const p = Buffer.from(
this.memory.buffer,
pathPtr,
pathLen
).toString();
let rstats;
if (flags) {
rstats = fs.statSync(path.resolve(stats.path, p));
} else {
rstats = fs.lstatSync(path.resolve(stats.path, p));
}
this.view.setBigUint64(bufPtr, BigInt(rstats.dev), true);
bufPtr += 8;
this.view.setBigUint64(bufPtr, BigInt(rstats.ino), true);
bufPtr += 8;
this.view.setUint8(
bufPtr,
translateFileAttributes(this, undefined, rstats).filetype
);
bufPtr += 8;
this.view.setBigUint64(bufPtr, BigInt(rstats.nlink), true);
bufPtr += 8;
this.view.setBigUint64(bufPtr, BigInt(rstats.size), true);
bufPtr += 8;
this.view.setBigUint64(bufPtr, msToNs(rstats.atimeMs), true);
bufPtr += 8;
this.view.setBigUint64(bufPtr, msToNs(rstats.mtimeMs), true);
bufPtr += 8;
this.view.setBigUint64(bufPtr, msToNs(rstats.ctimeMs), true);
return WASI_ESUCCESS;
}
),
path_filestat_set_times: wrap(
(
fd: number,
_dirflags: number,
pathPtr: number,
pathLen: number,
stAtim: number,
stMtim: number,
fstflags: number
) => {
const stats = CHECK_FD(fd, WASI_RIGHT_PATH_FILESTAT_SET_TIMES);
if (!stats.path) {
return WASI_EINVAL;
}
this.refreshMemory();
const rstats = this.fstatSync(stats.real);
let atim = rstats.atime;
let mtim = rstats.mtime;
const n = nsToMs(now(WASI_CLOCK_REALTIME)!);
const atimflags = WASI_FILESTAT_SET_ATIM | WASI_FILESTAT_SET_ATIM_NOW;
if ((fstflags & atimflags) === atimflags) {
return WASI_EINVAL;
}
const mtimflags = WASI_FILESTAT_SET_MTIM | WASI_FILESTAT_SET_MTIM_NOW;
if ((fstflags & mtimflags) === mtimflags) {
return WASI_EINVAL;
}
if ((fstflags & WASI_FILESTAT_SET_ATIM) === WASI_FILESTAT_SET_ATIM) {
atim = nsToMs(stAtim);
} else if (
(fstflags & WASI_FILESTAT_SET_ATIM_NOW) ===
WASI_FILESTAT_SET_ATIM_NOW
) {
atim = n;
}
if ((fstflags & WASI_FILESTAT_SET_MTIM) === WASI_FILESTAT_SET_MTIM) {
mtim = nsToMs(stMtim);
} else if (
(fstflags & WASI_FILESTAT_SET_MTIM_NOW) ===
WASI_FILESTAT_SET_MTIM_NOW
) {
mtim = n;
}
const p = Buffer.from(
this.memory.buffer,
pathPtr,
pathLen
).toString();
fs.utimesSync(
path.resolve(stats.path, p),
new Date(atim),
new Date(mtim)
);
return WASI_ESUCCESS;
}
),
path_link: wrap(
(
oldFd: number,
_oldFlags: number,
oldPath: number,
oldPathLen: number,
newFd: number,
newPath: number,
newPathLen: number
) => {
const ostats = CHECK_FD(oldFd, WASI_RIGHT_PATH_LINK_SOURCE);
const nstats = CHECK_FD(newFd, WASI_RIGHT_PATH_LINK_TARGET);
if (!ostats.path || !nstats.path) {
return WASI_EINVAL;
}
this.refreshMemory();
const op = Buffer.from(
this.memory.buffer,
oldPath,
oldPathLen
).toString();
const np = Buffer.from(
this.memory.buffer,
newPath,
newPathLen
).toString();
fs.linkSync(
path.resolve(ostats.path, op),
path.resolve(nstats.path, np)
);
return WASI_ESUCCESS;
}
),
path_open: wrap(
(
dirfd: number,
_dirflags: number,
pathPtr: number,
pathLen: number,
oflags: number,
fsRightsBase: bigint | number,
fsRightsInheriting: bigint | number,
fsFlags: number,
fdPtr: number
) => {
const stats = CHECK_FD(dirfd, WASI_RIGHT_PATH_OPEN);
fsRightsBase = BigInt(fsRightsBase);
fsRightsInheriting = BigInt(fsRightsInheriting);
const read =
(fsRightsBase & (WASI_RIGHT_FD_READ | WASI_RIGHT_FD_READDIR)) !==
BigInt(0);
const write =
(fsRightsBase &
(WASI_RIGHT_FD_DATASYNC |
WASI_RIGHT_FD_WRITE |
WASI_RIGHT_FD_ALLOCATE |
WASI_RIGHT_FD_FILESTAT_SET_SIZE)) !==
BigInt(0);
let noflags;
if (write && read) {
noflags = fs.constants.O_RDWR;
} else if (read) {
noflags = fs.constants.O_RDONLY;
} else if (write) {
noflags = fs.constants.O_WRONLY;
}
let neededBase = fsRightsBase | WASI_RIGHT_PATH_OPEN;
let neededInheriting = fsRightsBase | fsRightsInheriting;
if ((oflags & WASI_O_CREAT) !== 0) {
noflags |= fs.constants.O_CREAT;
neededBase |= WASI_RIGHT_PATH_CREATE_FILE;
}
if ((oflags & WASI_O_DIRECTORY) !== 0) {
noflags |= fs.constants.O_DIRECTORY;
}
if ((oflags & WASI_O_EXCL) !== 0) {
noflags |= fs.constants.O_EXCL;
}
if ((oflags & WASI_O_TRUNC) !== 0) {
noflags |= fs.constants.O_TRUNC;
neededBase |= WASI_RIGHT_PATH_FILESTAT_SET_SIZE;
}
if ((fsFlags & WASI_FDFLAG_APPEND) !== 0) {
noflags |= fs.constants.O_APPEND;
}
if ((fsFlags & WASI_FDFLAG_DSYNC) !== 0) {
if (fs.constants.O_DSYNC) {
noflags |= fs.constants.O_DSYNC;
} else {
noflags |= fs.constants.O_SYNC;
}
neededInheriting |= WASI_RIGHT_FD_DATASYNC;
}
if ((fsFlags & WASI_FDFLAG_NONBLOCK) !== 0) {
noflags |= fs.constants.O_NONBLOCK;
}
if ((fsFlags & WASI_FDFLAG_RSYNC) !== 0) {
if (fs.constants.O_RSYNC) {
noflags |= fs.constants.O_RSYNC;
} else {
noflags |= fs.constants.O_SYNC;
}
neededInheriting |= WASI_RIGHT_FD_SYNC;
}
if ((fsFlags & WASI_FDFLAG_SYNC) !== 0) {
noflags |= fs.constants.O_SYNC;
neededInheriting |= WASI_RIGHT_FD_SYNC;
}
if (
write &&
(noflags & (fs.constants.O_APPEND | fs.constants.O_TRUNC)) === 0
) {
neededInheriting |= WASI_RIGHT_FD_SEEK;
}
this.refreshMemory();
const p = Buffer.from(
this.memory.buffer,
pathPtr,
pathLen
).toString();
if (p == "dev/tty") {
this.view.setUint32(fdPtr, WASI_STDIN_FILENO, true);
return WASI_ESUCCESS;
}
logOpen("path_open", p);
if (p.startsWith("proc/")) {
throw new WASIError(WASI_EBADF);
}
const fullUnresolved = path.resolve(stats.path, p);
let full;
try {
full = fs.realpathSync(fullUnresolved);
} catch (e) {
if ((e as any)?.code === "ENOENT") {
full = fullUnresolved;
} else {
throw e;
}
}
let isDirectory;
if (write) {
try {
isDirectory = fs.statSync(full).isDirectory();
} catch (_err) {
}
}
let realfd;
if (!write && isDirectory) {
realfd = fs.openSync(full, fs.constants.O_RDONLY);
} else {
realfd = fs.openSync(full, noflags);
}
const newfd = this.getUnusedFileDescriptor();
this.FD_MAP.set(newfd, {
real: realfd,
filetype: undefined,
rights: {
base: neededBase,
inheriting: neededInheriting,
},
path: full,
});
stat(this, newfd);
this.view.setUint32(fdPtr, newfd, true);
return WASI_ESUCCESS;
}
),
path_readlink: wrap(
(
fd: number,
pathPtr: number,
pathLen: number,
buf: number,
bufLen: number,
bufused: number
) => {
const stats = CHECK_FD(fd, WASI_RIGHT_PATH_READLINK);
if (!stats.path) {
return WASI_EINVAL;
}
this.refreshMemory();
const p = Buffer.from(
this.memory.buffer,
pathPtr,
pathLen
).toString();
const full = path.resolve(stats.path, p);
const r = fs.readlinkSync(full);
const used = Buffer.from(this.memory.buffer).write(r, buf, bufLen);
this.view.setUint32(bufused, used, true);
return WASI_ESUCCESS;
}
),
path_remove_directory: wrap(
(fd: number, pathPtr: number, pathLen: number) => {
const stats = CHECK_FD(fd, WASI_RIGHT_PATH_REMOVE_DIRECTORY);
if (!stats.path) {
return WASI_EINVAL;
}
this.refreshMemory();
const p = Buffer.from(
this.memory.buffer,
pathPtr,
pathLen
).toString();
fs.rmdirSync(path.resolve(stats.path, p));
return WASI_ESUCCESS;
}
),
path_rename: wrap(
(
oldFd: number,
oldPath: number,
oldPathLen: number,
newFd: number,
newPath: number,
newPathLen: number
) => {
const ostats = CHECK_FD(oldFd, WASI_RIGHT_PATH_RENAME_SOURCE);
const nstats = CHECK_FD(newFd, WASI_RIGHT_PATH_RENAME_TARGET);
if (!ostats.path || !nstats.path) {
return WASI_EINVAL;
}
this.refreshMemory();
const op = Buffer.from(
this.memory.buffer,
oldPath,
oldPathLen
).toString();
const np = Buffer.from(
this.memory.buffer,
newPath,
newPathLen
).toString();
fs.renameSync(
path.resolve(ostats.path, op),
path.resolve(nstats.path, np)
);
return WASI_ESUCCESS;
}
),
path_symlink: wrap(
(
oldPath: number,
oldPathLen: number,
fd: number,
newPath: number,
newPathLen: number
) => {
const stats = CHECK_FD(fd, WASI_RIGHT_PATH_SYMLINK);
if (!stats.path) {
return WASI_EINVAL;
}
this.refreshMemory();
const op = Buffer.from(
this.memory.buffer,
oldPath,
oldPathLen
).toString();
const np = Buffer.from(
this.memory.buffer,
newPath,
newPathLen
).toString();
fs.symlinkSync(op, path.resolve(stats.path, np));
return WASI_ESUCCESS;
}
),
path_unlink_file: wrap((fd: number, pathPtr: number, pathLen: number) => {
const stats = CHECK_FD(fd, WASI_RIGHT_PATH_UNLINK_FILE);
if (!stats.path) {
return WASI_EINVAL;
}
this.refreshMemory();
const p = Buffer.from(this.memory.buffer, pathPtr, pathLen).toString();
fs.unlinkSync(path.resolve(stats.path, p));
return WASI_ESUCCESS;
}),
poll_oneoff: (
sin: number,
sout: number,
nsubscriptions: number,
neventsPtr: number
) => {
let nevents = 0;
let name = "";
let waitTimeNs = BigInt(0);
let fd = -1;
let fd_type: "read" | "write" = "read";
let fd_timeout_ms = 0;
const startNs = BigInt(this.bindings.hrtime());
this.refreshMemory();
let last_sin = sin;
for (let i = 0; i < nsubscriptions; i += 1) {
const userdata = this.view.getBigUint64(sin, true);
sin += 8;
const type = this.view.getUint8(sin);
sin += 1;
sin += 7;
if (log.enabled) {
if (type == WASI_EVENTTYPE_CLOCK) {
name = "poll_oneoff (type=WASI_EVENTTYPE_CLOCK): ";
} else if (type == WASI_EVENTTYPE_FD_READ) {
name = "poll_oneoff (type=WASI_EVENTTYPE_FD_READ): ";
} else {
name = "poll_oneoff (type=WASI_EVENTTYPE_FD_WRITE): ";
}
log(name);
}
switch (type) {
case WASI_EVENTTYPE_CLOCK: {
const clockid = this.view.getUint32(sin, true);
sin += 4;
sin += 4;
const timeout = this.view.getBigUint64(sin, true);
sin += 8;
sin += 8;
const subclockflags = this.view.getUint16(sin, true);
sin += 2;
sin += 6;
const absolute = subclockflags === 1;
if (log.enabled) {
log(name, { clockid, timeout, absolute });
}
if (!absolute) {
fd_timeout_ms = Number(timeout / BigInt(1000000));
}
let e = WASI_ESUCCESS;
const t = now(clockid);
if (t == null) {
e = WASI_EINVAL;
} else {
const end = absolute ? timeout : t + timeout;
const waitNs = end - t;
if (waitNs > waitTimeNs) {
waitTimeNs = waitNs;
}
}
this.view.setBigUint64(sout, userdata, true);
sout += 8;
this.view.setUint16(sout, e, true);
sout += 2;
this.view.setUint8(sout, WASI_EVENTTYPE_CLOCK);
sout += 1;
sout += 5;
nevents += 1;
break;
}
case WASI_EVENTTYPE_FD_READ:
case WASI_EVENTTYPE_FD_WRITE: {
fd = this.view.getUint32(sin, true);
fd_type = type == WASI_EVENTTYPE_FD_READ ? "read" : "write";
sin += 4;
log(name, "fd =", fd);
sin += 28;
this.view.setBigUint64(sout, userdata, true);
sout += 8;
this.view.setUint16(sout, WASI_ENOSYS, true);
sout += 2;
this.view.setUint8(sout, type);
sout += 1;
sout += 5;
nevents += 1;
if (fd == WASI_STDIN_FILENO && WASI_EVENTTYPE_FD_READ == type) {
this.shortPause();
}
break;
}
default:
return WASI_EINVAL;
}
if (sin - last_sin != 48) {
console.warn("*** BUG in wasi-js in poll_oneoff ", {
i,
sin,
last_sin,
diff: sin - last_sin,
});
}
last_sin = sin;
}
this.view.setUint32(neventsPtr, nevents, true);
if (nevents == 2 && fd >= 0) {
const r = this.wasiImport.sock_pollSocket(fd, fd_type, fd_timeout_ms);
if (r != WASI_ENOSYS) {
return r;
}
}
if (waitTimeNs > 0) {
waitTimeNs -= BigInt(this.bindings.hrtime()) - startNs;
if (waitTimeNs >= 1000000) {
if (this.sleep == null && !warnedAboutSleep) {
warnedAboutSleep = true;
console.log(
"(100% cpu burning waiting for stdin: please define a way to sleep!) "
);
}
if (this.sleep != null) {
const ms = nsToMs(waitTimeNs);
this.sleep(ms);
} else {
const end = BigInt(this.bindings.hrtime()) + waitTimeNs;
while (BigInt(this.bindings.hrtime()) < end) {
}
}
}
}
return WASI_ESUCCESS;
},
proc_exit: (rval: number) => {
this.bindings.exit(rval);
return WASI_ESUCCESS;
},
proc_raise: (sig: number) => {
if (!(sig in SIGNAL_MAP)) {
return WASI_EINVAL;
}
this.bindings.kill(SIGNAL_MAP[sig]);
return WASI_ESUCCESS;
},
random_get: (bufPtr: number, bufLen: number) => {
this.refreshMemory();
this.bindings.randomFillSync(
new Uint8Array(this.memory.buffer),
bufPtr,
bufLen
);
return WASI_ESUCCESS;
},
sched_yield() {
return WASI_ESUCCESS;
},
sock_recv() {
return WASI_ENOSYS;
},
sock_send() {
return WASI_ENOSYS;
},
sock_shutdown() {
return WASI_ENOSYS;
},
sock_fcntlSetFlags(_fd: number, _flags: number) {
return WASI_ENOSYS;
},
sock_pollSocket(
_fd: number,
_eventtype: "read" | "write",
_timeout_ms: number
) {
return WASI_ENOSYS;
},
};
if (log.enabled) {
Object.keys(this.wasiImport).forEach((key: string) => {
const prevImport = this.wasiImport[key];
this.wasiImport[key] = function (...args: any[]) {
log(key, args);
try {
let result = prevImport(...args);
log("result", result);
return result;
} catch (e) {
log("error: ", e);
throw e;
}
};
});
}
}
shortPause() {
if (this.sleep == null) return;
const now = new Date().valueOf();
if (now - this.lastStdin > 2000) {
this.sleep(50);
}
}
getUnusedFileDescriptor(start = 3) {
let fd = start;
while (this.FD_MAP.has(fd)) {
fd += 1;
}
if (fd > SC_OPEN_MAX) {
throw Error("no available file descriptors");
}
return fd;
}
refreshMemory() {
if (!this.view || this.view.buffer.byteLength === 0) {
this.view = new DataView(this.memory.buffer);
}
}
setMemory(memory: WebAssembly.Memory) {
this.memory = memory;
}
start(instance: WebAssembly.Instance, memory?: WebAssembly.Memory) {
const exports = instance.exports;
if (exports === null || typeof exports !== "object") {
throw new Error(
`instance.exports must be an Object. Received ${exports}.`
);
}
if (memory == null) {
memory = exports.memory as any;
if (!(memory instanceof WebAssembly.Memory)) {
throw new Error(
`instance.exports.memory must be a WebAssembly.Memory. Recceived ${memory}.`
);
}
}
this.setMemory(memory);
if (exports._start) {
(exports as any)._start();
}
}
private getImportNamespace(module: WebAssembly.Module): string {
let namespace: string | null = null;
for (let imp of WebAssembly.Module.imports(module)) {
if (imp.kind !== "function") {
continue;
}
if (!imp.module.startsWith("wasi_")) {
continue;
}
if (!namespace) {
namespace = imp.module;
} else {
if (namespace !== imp.module) {
throw new Error("Multiple namespaces detected.");
}
}
}
return namespace!;
}
getImports(
module: WebAssembly.Module
): Record<string, Record<string, Function>> {
let namespace = this.getImportNamespace(module);
switch (namespace) {
case "wasi_unstable":
return {
wasi_unstable: this.wasiImport,
};
case "wasi_snapshot_preview1":
return {
wasi_snapshot_preview1: this.wasiImport,
};
default:
throw new Error(
"Can't detect a WASI namespace for the WebAssembly Module"
);
}
}
initWasiFdInfo() {
if (this.env["WASI_FD_INFO"] != null) {
const fdInfo = JSON.parse(this.env["WASI_FD_INFO"]);
for (const wasi_fd in fdInfo) {
console.log(wasi_fd);
const fd = parseInt(wasi_fd);
if (this.FD_MAP.has(fd)) {
continue;
}
const real = fdInfo[wasi_fd];
try {
this.fstatSync(real);
} catch (_err) {
console.log("discarding ", { wasi_fd, real });
continue;
}
const file = {
real,
filetype: WASI_FILETYPE_SOCKET_STREAM,
rights: {
base: STDIN_DEFAULT_RIGHTS,
inheriting: BigInt(0),
},
} as File;
this.FD_MAP.set(fd, file);
}
console.log("after initWasiFdInfo: ", this.FD_MAP);
console.log("fdInfo = ", fdInfo);
} else {
console.log("no WASI_FD_INFO");
}
}
}