1142 lines
51 KiB
JavaScript
1142 lines
51 KiB
JavaScript
"use strict";
|
|
/* eslint-disable no-unused-vars */
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
const bigint_1 = require("./polyfills/bigint");
|
|
const dataview_1 = require("./polyfills/dataview");
|
|
const buffer_1 = require("./polyfills/buffer");
|
|
// Import our default bindings depending on the environment
|
|
let defaultBindings;
|
|
/*ROLLUP_REPLACE_NODE
|
|
import nodeBindings from "./bindings/node";
|
|
defaultBindings = nodeBindings;
|
|
ROLLUP_REPLACE_NODE*/
|
|
/*ROLLUP_REPLACE_BROWSER
|
|
import browserBindings from "./bindings/browser";
|
|
defaultBindings = browserBindings;
|
|
ROLLUP_REPLACE_BROWSER*/
|
|
/*
|
|
|
|
This project is based from the Node implementation made by Gus Caplan
|
|
https://github.com/devsnek/node-wasi
|
|
However, JavaScript WASI is focused on:
|
|
* Bringing WASI to the Browsers
|
|
* Make easy to plug different filesystems
|
|
* Provide a type-safe api using Typescript
|
|
* Providing multiple output targets to support both browsers and node
|
|
* The API is adapted to the Node-WASI API: https://github.com/nodejs/wasi/blob/wasi/lib/wasi.js
|
|
|
|
Copyright 2019 Gus Caplan
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to
|
|
deal in the Software without restriction, including without limitation the
|
|
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
sell copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
IN THE SOFTWARE.
|
|
|
|
*/
|
|
const constants_1 = require("./constants");
|
|
const STDIN_DEFAULT_RIGHTS = constants_1.WASI_RIGHT_FD_DATASYNC |
|
|
constants_1.WASI_RIGHT_FD_READ |
|
|
constants_1.WASI_RIGHT_FD_SYNC |
|
|
constants_1.WASI_RIGHT_FD_ADVISE |
|
|
constants_1.WASI_RIGHT_FD_FILESTAT_GET |
|
|
constants_1.WASI_RIGHT_POLL_FD_READWRITE;
|
|
const STDOUT_DEFAULT_RIGHTS = constants_1.WASI_RIGHT_FD_DATASYNC |
|
|
constants_1.WASI_RIGHT_FD_WRITE |
|
|
constants_1.WASI_RIGHT_FD_SYNC |
|
|
constants_1.WASI_RIGHT_FD_ADVISE |
|
|
constants_1.WASI_RIGHT_FD_FILESTAT_GET |
|
|
constants_1.WASI_RIGHT_POLL_FD_READWRITE;
|
|
const STDERR_DEFAULT_RIGHTS = STDOUT_DEFAULT_RIGHTS;
|
|
const msToNs = (ms) => {
|
|
const msInt = Math.trunc(ms);
|
|
const decimal = bigint_1.BigIntPolyfill(Math.round((ms - msInt) * 1000000));
|
|
const ns = bigint_1.BigIntPolyfill(msInt) * bigint_1.BigIntPolyfill(1000000);
|
|
return ns + decimal;
|
|
};
|
|
const nsToMs = (ns) => {
|
|
if (typeof ns === 'number') {
|
|
ns = Math.trunc(ns);
|
|
}
|
|
const nsInt = bigint_1.BigIntPolyfill(ns);
|
|
return Number(nsInt / bigint_1.BigIntPolyfill(1000000));
|
|
};
|
|
const wrap = (f) => (...args) => {
|
|
try {
|
|
return f(...args);
|
|
}
|
|
catch (e) {
|
|
// If it's an error from the fs
|
|
if (e && e.code && typeof e.code === "string") {
|
|
return constants_1.ERROR_MAP[e.code] || constants_1.WASI_EINVAL;
|
|
}
|
|
// If it's a WASI error, we return it directly
|
|
if (e instanceof WASIError) {
|
|
return e.errno;
|
|
}
|
|
// Otherwise we let the error bubble up
|
|
throw e;
|
|
}
|
|
};
|
|
const stat = (wasi, fd) => {
|
|
const entry = wasi.FD_MAP.get(fd);
|
|
if (!entry) {
|
|
throw new WASIError(constants_1.WASI_EBADF);
|
|
}
|
|
if (entry.filetype === undefined) {
|
|
const stats = wasi.bindings.fs.fstatSync(entry.real);
|
|
const { filetype, rightsBase, rightsInheriting } = translateFileAttributes(wasi, fd, stats);
|
|
entry.filetype = filetype;
|
|
if (!entry.rights) {
|
|
entry.rights = {
|
|
base: rightsBase,
|
|
inheriting: rightsInheriting
|
|
};
|
|
}
|
|
}
|
|
return entry;
|
|
};
|
|
const translateFileAttributes = (wasi, fd, stats) => {
|
|
switch (true) {
|
|
case stats.isBlockDevice():
|
|
return {
|
|
filetype: constants_1.WASI_FILETYPE_BLOCK_DEVICE,
|
|
rightsBase: constants_1.RIGHTS_BLOCK_DEVICE_BASE,
|
|
rightsInheriting: constants_1.RIGHTS_BLOCK_DEVICE_INHERITING
|
|
};
|
|
case stats.isCharacterDevice(): {
|
|
const filetype = constants_1.WASI_FILETYPE_CHARACTER_DEVICE;
|
|
if (fd !== undefined && wasi.bindings.isTTY(fd)) {
|
|
return {
|
|
filetype,
|
|
rightsBase: constants_1.RIGHTS_TTY_BASE,
|
|
rightsInheriting: constants_1.RIGHTS_TTY_INHERITING
|
|
};
|
|
}
|
|
return {
|
|
filetype,
|
|
rightsBase: constants_1.RIGHTS_CHARACTER_DEVICE_BASE,
|
|
rightsInheriting: constants_1.RIGHTS_CHARACTER_DEVICE_INHERITING
|
|
};
|
|
}
|
|
case stats.isDirectory():
|
|
return {
|
|
filetype: constants_1.WASI_FILETYPE_DIRECTORY,
|
|
rightsBase: constants_1.RIGHTS_DIRECTORY_BASE,
|
|
rightsInheriting: constants_1.RIGHTS_DIRECTORY_INHERITING
|
|
};
|
|
case stats.isFIFO():
|
|
return {
|
|
filetype: constants_1.WASI_FILETYPE_SOCKET_STREAM,
|
|
rightsBase: constants_1.RIGHTS_SOCKET_BASE,
|
|
rightsInheriting: constants_1.RIGHTS_SOCKET_INHERITING
|
|
};
|
|
case stats.isFile():
|
|
return {
|
|
filetype: constants_1.WASI_FILETYPE_REGULAR_FILE,
|
|
rightsBase: constants_1.RIGHTS_REGULAR_FILE_BASE,
|
|
rightsInheriting: constants_1.RIGHTS_REGULAR_FILE_INHERITING
|
|
};
|
|
case stats.isSocket():
|
|
return {
|
|
filetype: constants_1.WASI_FILETYPE_SOCKET_STREAM,
|
|
rightsBase: constants_1.RIGHTS_SOCKET_BASE,
|
|
rightsInheriting: constants_1.RIGHTS_SOCKET_INHERITING
|
|
};
|
|
case stats.isSymbolicLink():
|
|
return {
|
|
filetype: constants_1.WASI_FILETYPE_SYMBOLIC_LINK,
|
|
rightsBase: bigint_1.BigIntPolyfill(0),
|
|
rightsInheriting: bigint_1.BigIntPolyfill(0)
|
|
};
|
|
default:
|
|
return {
|
|
filetype: constants_1.WASI_FILETYPE_UNKNOWN,
|
|
rightsBase: bigint_1.BigIntPolyfill(0),
|
|
rightsInheriting: bigint_1.BigIntPolyfill(0)
|
|
};
|
|
}
|
|
};
|
|
class WASIError extends Error {
|
|
constructor(errno) {
|
|
super();
|
|
this.errno = errno;
|
|
Object.setPrototypeOf(this, WASIError.prototype);
|
|
}
|
|
}
|
|
exports.WASIError = WASIError;
|
|
class WASIExitError extends Error {
|
|
constructor(code) {
|
|
super(`WASI Exit error: ${code}`);
|
|
this.code = code;
|
|
Object.setPrototypeOf(this, WASIExitError.prototype);
|
|
}
|
|
}
|
|
exports.WASIExitError = WASIExitError;
|
|
class WASIKillError extends Error {
|
|
constructor(signal) {
|
|
super(`WASI Kill signal: ${signal}`);
|
|
this.signal = signal;
|
|
Object.setPrototypeOf(this, WASIKillError.prototype);
|
|
}
|
|
}
|
|
exports.WASIKillError = WASIKillError;
|
|
class WASIDefault {
|
|
constructor(wasiConfig) {
|
|
// Destructure our wasiConfig
|
|
let preopens = {};
|
|
if (wasiConfig && wasiConfig.preopens) {
|
|
preopens = wasiConfig.preopens;
|
|
}
|
|
else if (wasiConfig && wasiConfig.preopenDirectories) {
|
|
preopens = wasiConfig
|
|
.preopenDirectories;
|
|
}
|
|
let env = {};
|
|
if (wasiConfig && wasiConfig.env) {
|
|
env = wasiConfig.env;
|
|
}
|
|
let args = [];
|
|
if (wasiConfig && wasiConfig.args) {
|
|
args = wasiConfig.args;
|
|
}
|
|
let bindings = defaultBindings;
|
|
if (wasiConfig && wasiConfig.bindings) {
|
|
bindings = wasiConfig.bindings;
|
|
}
|
|
// @ts-ignore
|
|
this.memory = undefined;
|
|
// @ts-ignore
|
|
this.view = undefined;
|
|
this.bindings = bindings;
|
|
this.FD_MAP = new Map([
|
|
[
|
|
constants_1.WASI_STDIN_FILENO,
|
|
{
|
|
real: 0,
|
|
filetype: constants_1.WASI_FILETYPE_CHARACTER_DEVICE,
|
|
// offset: BigInt(0),
|
|
rights: {
|
|
base: STDIN_DEFAULT_RIGHTS,
|
|
inheriting: bigint_1.BigIntPolyfill(0)
|
|
},
|
|
path: undefined
|
|
}
|
|
],
|
|
[
|
|
constants_1.WASI_STDOUT_FILENO,
|
|
{
|
|
real: 1,
|
|
filetype: constants_1.WASI_FILETYPE_CHARACTER_DEVICE,
|
|
// offset: BigInt(0),
|
|
rights: {
|
|
base: STDOUT_DEFAULT_RIGHTS,
|
|
inheriting: bigint_1.BigIntPolyfill(0)
|
|
},
|
|
path: undefined
|
|
}
|
|
],
|
|
[
|
|
constants_1.WASI_STDERR_FILENO,
|
|
{
|
|
real: 2,
|
|
filetype: constants_1.WASI_FILETYPE_CHARACTER_DEVICE,
|
|
// offset: BigInt(0),
|
|
rights: {
|
|
base: STDERR_DEFAULT_RIGHTS,
|
|
inheriting: bigint_1.BigIntPolyfill(0)
|
|
},
|
|
path: undefined
|
|
}
|
|
]
|
|
]);
|
|
let fs = this.bindings.fs;
|
|
let path = this.bindings.path;
|
|
for (const [k, v] of Object.entries(preopens)) {
|
|
const real = fs.openSync(v, fs.constants.O_RDONLY);
|
|
const newfd = [...this.FD_MAP.keys()].reverse()[0] + 1;
|
|
this.FD_MAP.set(newfd, {
|
|
real,
|
|
filetype: constants_1.WASI_FILETYPE_DIRECTORY,
|
|
// offset: BigInt(0),
|
|
rights: {
|
|
base: constants_1.RIGHTS_DIRECTORY_BASE,
|
|
inheriting: constants_1.RIGHTS_DIRECTORY_INHERITING
|
|
},
|
|
fakePath: k,
|
|
path: v
|
|
});
|
|
}
|
|
const getiovs = (iovs, iovsLen) => {
|
|
// iovs* -> [iov, iov, ...]
|
|
// __wasi_ciovec_t {
|
|
// void* buf,
|
|
// size_t buf_len,
|
|
// }
|
|
this.refreshMemory();
|
|
const buffers = Array.from({ length: iovsLen }, (_, i) => {
|
|
const ptr = iovs + i * 8;
|
|
const buf = this.view.getUint32(ptr, true);
|
|
const bufLen = this.view.getUint32(ptr + 4, true);
|
|
return new Uint8Array(this.memory.buffer, buf, bufLen);
|
|
});
|
|
return buffers;
|
|
};
|
|
const CHECK_FD = (fd, rights) => {
|
|
const stats = stat(this, fd);
|
|
// console.log(`CHECK_FD: stats.real: ${stats.real}, stats.path:`, stats.path);
|
|
if (rights !== bigint_1.BigIntPolyfill(0) && (stats.rights.base & rights) === bigint_1.BigIntPolyfill(0)) {
|
|
throw new WASIError(constants_1.WASI_EPERM);
|
|
}
|
|
return stats;
|
|
};
|
|
const CPUTIME_START = bindings.hrtime();
|
|
const now = (clockId) => {
|
|
switch (clockId) {
|
|
case constants_1.WASI_CLOCK_MONOTONIC:
|
|
return bindings.hrtime();
|
|
case constants_1.WASI_CLOCK_REALTIME:
|
|
return msToNs(Date.now());
|
|
case constants_1.WASI_CLOCK_PROCESS_CPUTIME_ID:
|
|
case constants_1.WASI_CLOCK_THREAD_CPUTIME_ID:
|
|
// return bindings.hrtime(CPUTIME_START)
|
|
return bindings.hrtime() - CPUTIME_START;
|
|
default:
|
|
return null;
|
|
}
|
|
};
|
|
this.wasiImport = {
|
|
args_get: (argv, argvBuf) => {
|
|
this.refreshMemory();
|
|
let coffset = argv;
|
|
let offset = argvBuf;
|
|
args.forEach(a => {
|
|
this.view.setUint32(coffset, offset, true);
|
|
coffset += 4;
|
|
offset += buffer_1.default.from(this.memory.buffer).write(`${a}\0`, offset);
|
|
});
|
|
return constants_1.WASI_ESUCCESS;
|
|
},
|
|
args_sizes_get: (argc, argvBufSize) => {
|
|
this.refreshMemory();
|
|
this.view.setUint32(argc, args.length, true);
|
|
const size = args.reduce((acc, a) => acc + buffer_1.default.byteLength(a) + 1, 0);
|
|
this.view.setUint32(argvBufSize, size, true);
|
|
return constants_1.WASI_ESUCCESS;
|
|
},
|
|
environ_get: (environ, environBuf) => {
|
|
this.refreshMemory();
|
|
let coffset = environ;
|
|
let offset = environBuf;
|
|
Object.entries(env).forEach(([key, value]) => {
|
|
this.view.setUint32(coffset, offset, true);
|
|
coffset += 4;
|
|
offset += buffer_1.default.from(this.memory.buffer).write(`${key}=${value}\0`, offset);
|
|
});
|
|
return constants_1.WASI_ESUCCESS;
|
|
},
|
|
environ_sizes_get: (environCount, environBufSize) => {
|
|
this.refreshMemory();
|
|
const envProcessed = Object.entries(env).map(([key, value]) => `${key}=${value}\0`);
|
|
const size = envProcessed.reduce((acc, e) => acc + buffer_1.default.byteLength(e), 0);
|
|
this.view.setUint32(environCount, envProcessed.length, true);
|
|
this.view.setUint32(environBufSize, size, true);
|
|
return constants_1.WASI_ESUCCESS;
|
|
},
|
|
clock_res_get: (clockId, resolution) => {
|
|
let res;
|
|
switch (clockId) {
|
|
case constants_1.WASI_CLOCK_MONOTONIC:
|
|
case constants_1.WASI_CLOCK_PROCESS_CPUTIME_ID:
|
|
case constants_1.WASI_CLOCK_THREAD_CPUTIME_ID: {
|
|
res = bigint_1.BigIntPolyfill(1);
|
|
break;
|
|
}
|
|
case constants_1.WASI_CLOCK_REALTIME: {
|
|
res = bigint_1.BigIntPolyfill(1000);
|
|
break;
|
|
}
|
|
}
|
|
this.view.setBigUint64(resolution, res);
|
|
return constants_1.WASI_ESUCCESS;
|
|
},
|
|
clock_time_get: (clockId, precision, time) => {
|
|
this.refreshMemory();
|
|
const n = now(clockId);
|
|
if (n === null) {
|
|
return constants_1.WASI_EINVAL;
|
|
}
|
|
this.view.setBigUint64(time, bigint_1.BigIntPolyfill(n), true);
|
|
return constants_1.WASI_ESUCCESS;
|
|
},
|
|
fd_advise: wrap((fd, offset, len, advice) => {
|
|
CHECK_FD(fd, constants_1.WASI_RIGHT_FD_ADVISE);
|
|
return constants_1.WASI_ENOSYS;
|
|
}),
|
|
fd_allocate: wrap((fd, offset, len) => {
|
|
CHECK_FD(fd, constants_1.WASI_RIGHT_FD_ALLOCATE);
|
|
return constants_1.WASI_ENOSYS;
|
|
}),
|
|
fd_close: wrap((fd) => {
|
|
const stats = CHECK_FD(fd, bigint_1.BigIntPolyfill(0));
|
|
fs.closeSync(stats.real);
|
|
this.FD_MAP.delete(fd);
|
|
return constants_1.WASI_ESUCCESS;
|
|
}),
|
|
fd_datasync: wrap((fd) => {
|
|
const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_DATASYNC);
|
|
fs.fdatasyncSync(stats.real);
|
|
return constants_1.WASI_ESUCCESS;
|
|
}),
|
|
fd_fdstat_get: wrap((fd, bufPtr) => {
|
|
const stats = CHECK_FD(fd, bigint_1.BigIntPolyfill(0));
|
|
this.refreshMemory();
|
|
this.view.setUint8(bufPtr, stats.filetype); // FILETYPE u8
|
|
this.view.setUint16(bufPtr + 2, 0, true); // FDFLAG u16
|
|
this.view.setUint16(bufPtr + 4, 0, true); // FDFLAG u16
|
|
this.view.setBigUint64(bufPtr + 8, bigint_1.BigIntPolyfill(stats.rights.base), true); // u64
|
|
this.view.setBigUint64(bufPtr + 8 + 8, bigint_1.BigIntPolyfill(stats.rights.inheriting), true); // u64
|
|
return constants_1.WASI_ESUCCESS;
|
|
}),
|
|
fd_fdstat_set_flags: wrap((fd, flags) => {
|
|
CHECK_FD(fd, constants_1.WASI_RIGHT_FD_FDSTAT_SET_FLAGS);
|
|
return constants_1.WASI_ENOSYS;
|
|
}),
|
|
fd_fdstat_set_rights: wrap((fd, fsRightsBase, fsRightsInheriting) => {
|
|
const stats = CHECK_FD(fd, bigint_1.BigIntPolyfill(0));
|
|
const nrb = stats.rights.base | fsRightsBase;
|
|
if (nrb > stats.rights.base) {
|
|
return constants_1.WASI_EPERM;
|
|
}
|
|
const nri = stats.rights.inheriting | fsRightsInheriting;
|
|
if (nri > stats.rights.inheriting) {
|
|
return constants_1.WASI_EPERM;
|
|
}
|
|
stats.rights.base = fsRightsBase;
|
|
stats.rights.inheriting = fsRightsInheriting;
|
|
return constants_1.WASI_ESUCCESS;
|
|
}),
|
|
fd_filestat_get: wrap((fd, bufPtr) => {
|
|
const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_FILESTAT_GET);
|
|
const rstats = fs.fstatSync(stats.real);
|
|
this.refreshMemory();
|
|
this.view.setBigUint64(bufPtr, bigint_1.BigIntPolyfill(rstats.dev), true);
|
|
bufPtr += 8;
|
|
this.view.setBigUint64(bufPtr, bigint_1.BigIntPolyfill(rstats.ino), true);
|
|
bufPtr += 8;
|
|
this.view.setUint8(bufPtr, stats.filetype);
|
|
bufPtr += 8;
|
|
this.view.setBigUint64(bufPtr, bigint_1.BigIntPolyfill(rstats.nlink), true);
|
|
bufPtr += 8;
|
|
this.view.setBigUint64(bufPtr, bigint_1.BigIntPolyfill(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 constants_1.WASI_ESUCCESS;
|
|
}),
|
|
fd_filestat_set_size: wrap((fd, stSize) => {
|
|
const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_FILESTAT_SET_SIZE);
|
|
fs.ftruncateSync(stats.real, Number(stSize));
|
|
return constants_1.WASI_ESUCCESS;
|
|
}),
|
|
fd_filestat_set_times: wrap((fd, stAtim, stMtim, fstflags) => {
|
|
const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_FILESTAT_SET_TIMES);
|
|
const rstats = fs.fstatSync(stats.real);
|
|
let atim = rstats.atime;
|
|
let mtim = rstats.mtime;
|
|
const n = nsToMs(now(constants_1.WASI_CLOCK_REALTIME));
|
|
const atimflags = constants_1.WASI_FILESTAT_SET_ATIM | constants_1.WASI_FILESTAT_SET_ATIM_NOW;
|
|
if ((fstflags & atimflags) === atimflags) {
|
|
return constants_1.WASI_EINVAL;
|
|
}
|
|
const mtimflags = constants_1.WASI_FILESTAT_SET_MTIM | constants_1.WASI_FILESTAT_SET_MTIM_NOW;
|
|
if ((fstflags & mtimflags) === mtimflags) {
|
|
return constants_1.WASI_EINVAL;
|
|
}
|
|
if ((fstflags & constants_1.WASI_FILESTAT_SET_ATIM) === constants_1.WASI_FILESTAT_SET_ATIM) {
|
|
atim = nsToMs(stAtim);
|
|
}
|
|
else if ((fstflags & constants_1.WASI_FILESTAT_SET_ATIM_NOW) === constants_1.WASI_FILESTAT_SET_ATIM_NOW) {
|
|
atim = n;
|
|
}
|
|
if ((fstflags & constants_1.WASI_FILESTAT_SET_MTIM) === constants_1.WASI_FILESTAT_SET_MTIM) {
|
|
mtim = nsToMs(stMtim);
|
|
}
|
|
else if ((fstflags & constants_1.WASI_FILESTAT_SET_MTIM_NOW) === constants_1.WASI_FILESTAT_SET_MTIM_NOW) {
|
|
mtim = n;
|
|
}
|
|
fs.futimesSync(stats.real, new Date(atim), new Date(mtim));
|
|
return constants_1.WASI_ESUCCESS;
|
|
}),
|
|
fd_prestat_get: wrap((fd, bufPtr) => {
|
|
const stats = CHECK_FD(fd, bigint_1.BigIntPolyfill(0));
|
|
if (!stats.path) {
|
|
return constants_1.WASI_EINVAL;
|
|
}
|
|
this.refreshMemory();
|
|
this.view.setUint8(bufPtr, constants_1.WASI_PREOPENTYPE_DIR);
|
|
this.view.setUint32(bufPtr + 4, buffer_1.default.byteLength(stats.fakePath), true);
|
|
return constants_1.WASI_ESUCCESS;
|
|
}),
|
|
fd_prestat_dir_name: wrap((fd, pathPtr, pathLen) => {
|
|
const stats = CHECK_FD(fd, bigint_1.BigIntPolyfill(0));
|
|
if (!stats.path) {
|
|
return constants_1.WASI_EINVAL;
|
|
}
|
|
this.refreshMemory();
|
|
buffer_1.default.from(this.memory.buffer).write(stats.fakePath, pathPtr, pathLen, "utf8");
|
|
return constants_1.WASI_ESUCCESS;
|
|
}),
|
|
fd_pwrite: wrap((fd, iovs, iovsLen, offset, nwritten) => {
|
|
const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_WRITE | constants_1.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 constants_1.WASI_ESUCCESS;
|
|
}),
|
|
fd_write: wrap((fd, iovs, iovsLen, nwritten) => {
|
|
const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_WRITE);
|
|
let written = 0;
|
|
getiovs(iovs, iovsLen).forEach(iov => {
|
|
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_1.BigIntPolyfill(i);
|
|
w += i;
|
|
}
|
|
written += w;
|
|
});
|
|
this.view.setUint32(nwritten, written, true);
|
|
return constants_1.WASI_ESUCCESS;
|
|
}),
|
|
fd_pread: wrap((fd, iovs, iovsLen, offset, nread) => {
|
|
const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_READ | constants_1.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 we don't read anything, or we receive less than requested
|
|
if (rr === 0 || rr < length) {
|
|
break outer;
|
|
}
|
|
}
|
|
read += r;
|
|
}
|
|
;
|
|
this.view.setUint32(nread, read, true);
|
|
return constants_1.WASI_ESUCCESS;
|
|
}),
|
|
fd_read: wrap((fd, iovs, iovsLen, nread) => {
|
|
const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_READ);
|
|
const IS_STDIN = stats.real === 0;
|
|
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 = fs.readSync(stats.real, // fd
|
|
iov, // buffer
|
|
r, // offset
|
|
length, // length
|
|
position // position
|
|
);
|
|
if (!IS_STDIN) {
|
|
stats.offset =
|
|
(stats.offset ? stats.offset : bigint_1.BigIntPolyfill(0)) + bigint_1.BigIntPolyfill(rr);
|
|
}
|
|
r += rr;
|
|
read += rr;
|
|
// If we don't read anything, or we receive less than requested
|
|
if (rr === 0 || rr < length) {
|
|
break outer;
|
|
}
|
|
}
|
|
}
|
|
// We should not modify the offset of stdin
|
|
this.view.setUint32(nread, read, true);
|
|
return constants_1.WASI_ESUCCESS;
|
|
}),
|
|
fd_readdir: wrap((fd, bufPtr, bufLen, cookie, bufusedPtr) => {
|
|
const stats = CHECK_FD(fd, constants_1.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_1.default.byteLength(entry.name);
|
|
if (bufPtr - startPtr > bufLen) {
|
|
break;
|
|
}
|
|
this.view.setBigUint64(bufPtr, bigint_1.BigIntPolyfill(i + 1), true);
|
|
bufPtr += 8;
|
|
if (bufPtr - startPtr > bufLen) {
|
|
break;
|
|
}
|
|
const rstats = fs.statSync(path.resolve(stats.path, entry.name));
|
|
this.view.setBigUint64(bufPtr, bigint_1.BigIntPolyfill(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 = constants_1.WASI_FILETYPE_BLOCK_DEVICE;
|
|
break;
|
|
case rstats.isCharacterDevice():
|
|
filetype = constants_1.WASI_FILETYPE_CHARACTER_DEVICE;
|
|
break;
|
|
case rstats.isDirectory():
|
|
filetype = constants_1.WASI_FILETYPE_DIRECTORY;
|
|
break;
|
|
case rstats.isFIFO():
|
|
filetype = constants_1.WASI_FILETYPE_SOCKET_STREAM;
|
|
break;
|
|
case rstats.isFile():
|
|
filetype = constants_1.WASI_FILETYPE_REGULAR_FILE;
|
|
break;
|
|
case rstats.isSocket():
|
|
filetype = constants_1.WASI_FILETYPE_SOCKET_STREAM;
|
|
break;
|
|
case rstats.isSymbolicLink():
|
|
filetype = constants_1.WASI_FILETYPE_SYMBOLIC_LINK;
|
|
break;
|
|
default:
|
|
filetype = constants_1.WASI_FILETYPE_UNKNOWN;
|
|
break;
|
|
}
|
|
this.view.setUint8(bufPtr, filetype);
|
|
bufPtr += 1;
|
|
bufPtr += 3; // padding
|
|
if (bufPtr + nameLength >= startPtr + bufLen) {
|
|
// It doesn't fit in the buffer
|
|
break;
|
|
}
|
|
let memory_buffer = buffer_1.default.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 constants_1.WASI_ESUCCESS;
|
|
}),
|
|
fd_renumber: wrap((from, to) => {
|
|
CHECK_FD(from, bigint_1.BigIntPolyfill(0));
|
|
CHECK_FD(to, bigint_1.BigIntPolyfill(0));
|
|
fs.closeSync(this.FD_MAP.get(from).real);
|
|
this.FD_MAP.set(from, this.FD_MAP.get(to));
|
|
this.FD_MAP.delete(to);
|
|
return constants_1.WASI_ESUCCESS;
|
|
}),
|
|
fd_seek: wrap((fd, offset, whence, newOffsetPtr) => {
|
|
const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_SEEK);
|
|
this.refreshMemory();
|
|
switch (whence) {
|
|
case constants_1.WASI_WHENCE_CUR:
|
|
stats.offset =
|
|
(stats.offset ? stats.offset : bigint_1.BigIntPolyfill(0)) + bigint_1.BigIntPolyfill(offset);
|
|
break;
|
|
case constants_1.WASI_WHENCE_END:
|
|
const { size } = fs.fstatSync(stats.real);
|
|
stats.offset = bigint_1.BigIntPolyfill(size) + bigint_1.BigIntPolyfill(offset);
|
|
break;
|
|
case constants_1.WASI_WHENCE_SET:
|
|
stats.offset = bigint_1.BigIntPolyfill(offset);
|
|
break;
|
|
}
|
|
this.view.setBigUint64(newOffsetPtr, stats.offset, true);
|
|
return constants_1.WASI_ESUCCESS;
|
|
}),
|
|
fd_tell: wrap((fd, offsetPtr) => {
|
|
const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_TELL);
|
|
this.refreshMemory();
|
|
if (!stats.offset) {
|
|
stats.offset = bigint_1.BigIntPolyfill(0);
|
|
}
|
|
this.view.setBigUint64(offsetPtr, stats.offset, true);
|
|
return constants_1.WASI_ESUCCESS;
|
|
}),
|
|
fd_sync: wrap((fd) => {
|
|
const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_SYNC);
|
|
fs.fsyncSync(stats.real);
|
|
return constants_1.WASI_ESUCCESS;
|
|
}),
|
|
path_create_directory: wrap((fd, pathPtr, pathLen) => {
|
|
const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_PATH_CREATE_DIRECTORY);
|
|
if (!stats.path) {
|
|
return constants_1.WASI_EINVAL;
|
|
}
|
|
this.refreshMemory();
|
|
const p = buffer_1.default.from(this.memory.buffer, pathPtr, pathLen).toString();
|
|
fs.mkdirSync(path.resolve(stats.path, p));
|
|
return constants_1.WASI_ESUCCESS;
|
|
}),
|
|
path_filestat_get: wrap((fd, flags, pathPtr, pathLen, bufPtr) => {
|
|
const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_PATH_FILESTAT_GET);
|
|
if (!stats.path) {
|
|
return constants_1.WASI_EINVAL;
|
|
}
|
|
this.refreshMemory();
|
|
const p = buffer_1.default.from(this.memory.buffer, pathPtr, pathLen).toString();
|
|
const rstats = fs.statSync(path.resolve(stats.path, p));
|
|
this.view.setBigUint64(bufPtr, bigint_1.BigIntPolyfill(rstats.dev), true);
|
|
bufPtr += 8;
|
|
this.view.setBigUint64(bufPtr, bigint_1.BigIntPolyfill(rstats.ino), true);
|
|
bufPtr += 8;
|
|
this.view.setUint8(bufPtr, translateFileAttributes(this, undefined, rstats).filetype);
|
|
bufPtr += 8;
|
|
this.view.setBigUint64(bufPtr, bigint_1.BigIntPolyfill(rstats.nlink), true);
|
|
bufPtr += 8;
|
|
this.view.setBigUint64(bufPtr, bigint_1.BigIntPolyfill(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 constants_1.WASI_ESUCCESS;
|
|
}),
|
|
path_filestat_set_times: wrap((fd, dirflags, pathPtr, pathLen, stAtim, stMtim, fstflags) => {
|
|
const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_PATH_FILESTAT_SET_TIMES);
|
|
if (!stats.path) {
|
|
return constants_1.WASI_EINVAL;
|
|
}
|
|
this.refreshMemory();
|
|
const rstats = fs.fstatSync(stats.real);
|
|
let atim = rstats.atime;
|
|
let mtim = rstats.mtime;
|
|
const n = nsToMs(now(constants_1.WASI_CLOCK_REALTIME));
|
|
const atimflags = constants_1.WASI_FILESTAT_SET_ATIM | constants_1.WASI_FILESTAT_SET_ATIM_NOW;
|
|
if ((fstflags & atimflags) === atimflags) {
|
|
return constants_1.WASI_EINVAL;
|
|
}
|
|
const mtimflags = constants_1.WASI_FILESTAT_SET_MTIM | constants_1.WASI_FILESTAT_SET_MTIM_NOW;
|
|
if ((fstflags & mtimflags) === mtimflags) {
|
|
return constants_1.WASI_EINVAL;
|
|
}
|
|
if ((fstflags & constants_1.WASI_FILESTAT_SET_ATIM) === constants_1.WASI_FILESTAT_SET_ATIM) {
|
|
atim = nsToMs(stAtim);
|
|
}
|
|
else if ((fstflags & constants_1.WASI_FILESTAT_SET_ATIM_NOW) === constants_1.WASI_FILESTAT_SET_ATIM_NOW) {
|
|
atim = n;
|
|
}
|
|
if ((fstflags & constants_1.WASI_FILESTAT_SET_MTIM) === constants_1.WASI_FILESTAT_SET_MTIM) {
|
|
mtim = nsToMs(stMtim);
|
|
}
|
|
else if ((fstflags & constants_1.WASI_FILESTAT_SET_MTIM_NOW) === constants_1.WASI_FILESTAT_SET_MTIM_NOW) {
|
|
mtim = n;
|
|
}
|
|
const p = buffer_1.default.from(this.memory.buffer, pathPtr, pathLen).toString();
|
|
fs.utimesSync(path.resolve(stats.path, p), new Date(atim), new Date(mtim));
|
|
return constants_1.WASI_ESUCCESS;
|
|
}),
|
|
path_link: wrap((oldFd, oldFlags, oldPath, oldPathLen, newFd, newPath, newPathLen) => {
|
|
const ostats = CHECK_FD(oldFd, constants_1.WASI_RIGHT_PATH_LINK_SOURCE);
|
|
const nstats = CHECK_FD(newFd, constants_1.WASI_RIGHT_PATH_LINK_TARGET);
|
|
if (!ostats.path || !nstats.path) {
|
|
return constants_1.WASI_EINVAL;
|
|
}
|
|
this.refreshMemory();
|
|
const op = buffer_1.default.from(this.memory.buffer, oldPath, oldPathLen).toString();
|
|
const np = buffer_1.default.from(this.memory.buffer, newPath, newPathLen).toString();
|
|
fs.linkSync(path.resolve(ostats.path, op), path.resolve(nstats.path, np));
|
|
return constants_1.WASI_ESUCCESS;
|
|
}),
|
|
path_open: wrap((dirfd, dirflags, pathPtr, pathLen, oflags, fsRightsBase, fsRightsInheriting, fsFlags, fd) => {
|
|
const stats = CHECK_FD(dirfd, constants_1.WASI_RIGHT_PATH_OPEN);
|
|
fsRightsBase = bigint_1.BigIntPolyfill(fsRightsBase);
|
|
fsRightsInheriting = bigint_1.BigIntPolyfill(fsRightsInheriting);
|
|
const read = (fsRightsBase & (constants_1.WASI_RIGHT_FD_READ | constants_1.WASI_RIGHT_FD_READDIR)) !==
|
|
bigint_1.BigIntPolyfill(0);
|
|
const write = (fsRightsBase &
|
|
(constants_1.WASI_RIGHT_FD_DATASYNC |
|
|
constants_1.WASI_RIGHT_FD_WRITE |
|
|
constants_1.WASI_RIGHT_FD_ALLOCATE |
|
|
constants_1.WASI_RIGHT_FD_FILESTAT_SET_SIZE)) !==
|
|
bigint_1.BigIntPolyfill(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;
|
|
}
|
|
// fsRightsBase is needed here but perhaps we should do it in neededInheriting
|
|
let neededBase = fsRightsBase | constants_1.WASI_RIGHT_PATH_OPEN;
|
|
let neededInheriting = fsRightsBase | fsRightsInheriting;
|
|
if ((oflags & constants_1.WASI_O_CREAT) !== 0) {
|
|
noflags |= fs.constants.O_CREAT;
|
|
neededBase |= constants_1.WASI_RIGHT_PATH_CREATE_FILE;
|
|
}
|
|
if ((oflags & constants_1.WASI_O_DIRECTORY) !== 0) {
|
|
noflags |= fs.constants.O_DIRECTORY;
|
|
}
|
|
if ((oflags & constants_1.WASI_O_EXCL) !== 0) {
|
|
noflags |= fs.constants.O_EXCL;
|
|
}
|
|
if ((oflags & constants_1.WASI_O_TRUNC) !== 0) {
|
|
noflags |= fs.constants.O_TRUNC;
|
|
neededBase |= constants_1.WASI_RIGHT_PATH_FILESTAT_SET_SIZE;
|
|
}
|
|
// Convert file descriptor flags.
|
|
if ((fsFlags & constants_1.WASI_FDFLAG_APPEND) !== 0) {
|
|
noflags |= fs.constants.O_APPEND;
|
|
}
|
|
if ((fsFlags & constants_1.WASI_FDFLAG_DSYNC) !== 0) {
|
|
if (fs.constants.O_DSYNC) {
|
|
noflags |= fs.constants.O_DSYNC;
|
|
}
|
|
else {
|
|
noflags |= fs.constants.O_SYNC;
|
|
}
|
|
neededInheriting |= constants_1.WASI_RIGHT_FD_DATASYNC;
|
|
}
|
|
if ((fsFlags & constants_1.WASI_FDFLAG_NONBLOCK) !== 0) {
|
|
noflags |= fs.constants.O_NONBLOCK;
|
|
}
|
|
if ((fsFlags & constants_1.WASI_FDFLAG_RSYNC) !== 0) {
|
|
if (fs.constants.O_RSYNC) {
|
|
noflags |= fs.constants.O_RSYNC;
|
|
}
|
|
else {
|
|
noflags |= fs.constants.O_SYNC;
|
|
}
|
|
neededInheriting |= constants_1.WASI_RIGHT_FD_SYNC;
|
|
}
|
|
if ((fsFlags & constants_1.WASI_FDFLAG_SYNC) !== 0) {
|
|
noflags |= fs.constants.O_SYNC;
|
|
neededInheriting |= constants_1.WASI_RIGHT_FD_SYNC;
|
|
}
|
|
if (write &&
|
|
(noflags & (fs.constants.O_APPEND | fs.constants.O_TRUNC)) === 0) {
|
|
neededInheriting |= constants_1.WASI_RIGHT_FD_SEEK;
|
|
}
|
|
this.refreshMemory();
|
|
const p = buffer_1.default.from(this.memory.buffer, pathPtr, pathLen).toString();
|
|
const fullUnresolved = path.resolve(stats.path, p);
|
|
if (path.relative(stats.path, fullUnresolved).startsWith("..")) {
|
|
return constants_1.WASI_ENOTCAPABLE;
|
|
}
|
|
let full;
|
|
try {
|
|
full = fs.realpathSync(fullUnresolved);
|
|
if (path.relative(stats.path, full).startsWith("..")) {
|
|
return constants_1.WASI_ENOTCAPABLE;
|
|
}
|
|
}
|
|
catch (e) {
|
|
if (e.code === "ENOENT") {
|
|
full = fullUnresolved;
|
|
}
|
|
else {
|
|
throw e;
|
|
}
|
|
}
|
|
/* check if the file is a directory (unless opening for write,
|
|
* in which case the file may not exist and should be created) */
|
|
let isDirectory;
|
|
try {
|
|
isDirectory = fs.statSync(full).isDirectory();
|
|
}
|
|
catch (e) { }
|
|
let realfd;
|
|
if (!write && isDirectory) {
|
|
realfd = fs.openSync(full, fs.constants.O_RDONLY);
|
|
}
|
|
else {
|
|
realfd = fs.openSync(full, noflags);
|
|
}
|
|
const newfd = [...this.FD_MAP.keys()].reverse()[0] + 1;
|
|
this.FD_MAP.set(newfd, {
|
|
real: realfd,
|
|
filetype: undefined,
|
|
// offset: BigInt(0),
|
|
rights: {
|
|
base: neededBase,
|
|
inheriting: neededInheriting
|
|
},
|
|
path: full
|
|
});
|
|
stat(this, newfd);
|
|
this.view.setUint32(fd, newfd, true);
|
|
return constants_1.WASI_ESUCCESS;
|
|
}),
|
|
path_readlink: wrap((fd, pathPtr, pathLen, buf, bufLen, bufused) => {
|
|
const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_PATH_READLINK);
|
|
if (!stats.path) {
|
|
return constants_1.WASI_EINVAL;
|
|
}
|
|
this.refreshMemory();
|
|
const p = buffer_1.default.from(this.memory.buffer, pathPtr, pathLen).toString();
|
|
const full = path.resolve(stats.path, p);
|
|
const r = fs.readlinkSync(full);
|
|
const used = buffer_1.default.from(this.memory.buffer).write(r, buf, bufLen);
|
|
this.view.setUint32(bufused, used, true);
|
|
return constants_1.WASI_ESUCCESS;
|
|
}),
|
|
path_remove_directory: wrap((fd, pathPtr, pathLen) => {
|
|
const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_PATH_REMOVE_DIRECTORY);
|
|
if (!stats.path) {
|
|
return constants_1.WASI_EINVAL;
|
|
}
|
|
this.refreshMemory();
|
|
const p = buffer_1.default.from(this.memory.buffer, pathPtr, pathLen).toString();
|
|
fs.rmdirSync(path.resolve(stats.path, p));
|
|
return constants_1.WASI_ESUCCESS;
|
|
}),
|
|
path_rename: wrap((oldFd, oldPath, oldPathLen, newFd, newPath, newPathLen) => {
|
|
const ostats = CHECK_FD(oldFd, constants_1.WASI_RIGHT_PATH_RENAME_SOURCE);
|
|
const nstats = CHECK_FD(newFd, constants_1.WASI_RIGHT_PATH_RENAME_TARGET);
|
|
if (!ostats.path || !nstats.path) {
|
|
return constants_1.WASI_EINVAL;
|
|
}
|
|
this.refreshMemory();
|
|
const op = buffer_1.default.from(this.memory.buffer, oldPath, oldPathLen).toString();
|
|
const np = buffer_1.default.from(this.memory.buffer, newPath, newPathLen).toString();
|
|
fs.renameSync(path.resolve(ostats.path, op), path.resolve(nstats.path, np));
|
|
return constants_1.WASI_ESUCCESS;
|
|
}),
|
|
path_symlink: wrap((oldPath, oldPathLen, fd, newPath, newPathLen) => {
|
|
const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_PATH_SYMLINK);
|
|
if (!stats.path) {
|
|
return constants_1.WASI_EINVAL;
|
|
}
|
|
this.refreshMemory();
|
|
const op = buffer_1.default.from(this.memory.buffer, oldPath, oldPathLen).toString();
|
|
const np = buffer_1.default.from(this.memory.buffer, newPath, newPathLen).toString();
|
|
fs.symlinkSync(op, path.resolve(stats.path, np));
|
|
return constants_1.WASI_ESUCCESS;
|
|
}),
|
|
path_unlink_file: wrap((fd, pathPtr, pathLen) => {
|
|
const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_PATH_UNLINK_FILE);
|
|
if (!stats.path) {
|
|
return constants_1.WASI_EINVAL;
|
|
}
|
|
this.refreshMemory();
|
|
const p = buffer_1.default.from(this.memory.buffer, pathPtr, pathLen).toString();
|
|
fs.unlinkSync(path.resolve(stats.path, p));
|
|
return constants_1.WASI_ESUCCESS;
|
|
}),
|
|
poll_oneoff: (sin, sout, nsubscriptions, nevents) => {
|
|
let eventc = 0;
|
|
let waitEnd = 0;
|
|
this.refreshMemory();
|
|
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;
|
|
switch (type) {
|
|
case constants_1.WASI_EVENTTYPE_CLOCK: {
|
|
sin += 7; // padding
|
|
const identifier = this.view.getBigUint64(sin, true);
|
|
sin += 8;
|
|
const clockid = this.view.getUint32(sin, true);
|
|
sin += 4;
|
|
sin += 4; // padding
|
|
const timestamp = this.view.getBigUint64(sin, true);
|
|
sin += 8;
|
|
const precision = this.view.getBigUint64(sin, true);
|
|
sin += 8;
|
|
const subclockflags = this.view.getUint16(sin, true);
|
|
sin += 2;
|
|
sin += 6; // padding
|
|
const absolute = subclockflags === 1;
|
|
let e = constants_1.WASI_ESUCCESS;
|
|
const n = bigint_1.BigIntPolyfill(now(clockid));
|
|
if (n === null) {
|
|
e = constants_1.WASI_EINVAL;
|
|
}
|
|
else {
|
|
const end = absolute ? timestamp : n + timestamp;
|
|
waitEnd =
|
|
end > waitEnd ? end : waitEnd;
|
|
}
|
|
this.view.setBigUint64(sout, userdata, true);
|
|
sout += 8;
|
|
this.view.setUint16(sout, e, true); // error
|
|
sout += 2; // pad offset 2
|
|
this.view.setUint8(sout, constants_1.WASI_EVENTTYPE_CLOCK);
|
|
sout += 1; // pad offset 3
|
|
sout += 5; // padding to 8
|
|
eventc += 1;
|
|
break;
|
|
}
|
|
case constants_1.WASI_EVENTTYPE_FD_READ:
|
|
case constants_1.WASI_EVENTTYPE_FD_WRITE: {
|
|
sin += 3; // padding
|
|
const fd = this.view.getUint32(sin, true);
|
|
sin += 4;
|
|
this.view.setBigUint64(sout, userdata, true);
|
|
sout += 8;
|
|
this.view.setUint16(sout, constants_1.WASI_ENOSYS, true); // error
|
|
sout += 2; // pad offset 2
|
|
this.view.setUint8(sout, type);
|
|
sout += 1; // pad offset 3
|
|
sout += 5; // padding to 8
|
|
eventc += 1;
|
|
break;
|
|
}
|
|
default:
|
|
return constants_1.WASI_EINVAL;
|
|
}
|
|
}
|
|
this.view.setUint32(nevents, eventc, true);
|
|
while (bindings.hrtime() < waitEnd) {
|
|
// nothing
|
|
}
|
|
return constants_1.WASI_ESUCCESS;
|
|
},
|
|
proc_exit: (rval) => {
|
|
bindings.exit(rval);
|
|
return constants_1.WASI_ESUCCESS;
|
|
},
|
|
proc_raise: (sig) => {
|
|
if (!(sig in constants_1.SIGNAL_MAP)) {
|
|
return constants_1.WASI_EINVAL;
|
|
}
|
|
bindings.kill(constants_1.SIGNAL_MAP[sig]);
|
|
return constants_1.WASI_ESUCCESS;
|
|
},
|
|
random_get: (bufPtr, bufLen) => {
|
|
this.refreshMemory();
|
|
bindings.randomFillSync(new Uint8Array(this.memory.buffer), bufPtr, bufLen);
|
|
return constants_1.WASI_ESUCCESS;
|
|
},
|
|
sched_yield() {
|
|
// Single threaded environment
|
|
// This is a no-op in JS
|
|
return constants_1.WASI_ESUCCESS;
|
|
},
|
|
sock_recv() {
|
|
return constants_1.WASI_ENOSYS;
|
|
},
|
|
sock_send() {
|
|
return constants_1.WASI_ENOSYS;
|
|
},
|
|
sock_shutdown() {
|
|
return constants_1.WASI_ENOSYS;
|
|
}
|
|
};
|
|
// Wrap each of the imports to show the calls in the console
|
|
if (wasiConfig.traceSyscalls) {
|
|
Object.keys(this.wasiImport).forEach((key) => {
|
|
const prevImport = this.wasiImport[key];
|
|
this.wasiImport[key] = function (...args) {
|
|
console.log(`WASI: wasiImport called: ${key} (${args})`);
|
|
try {
|
|
let result = prevImport(...args);
|
|
console.log(`WASI: => ${result}`);
|
|
return result;
|
|
}
|
|
catch (e) {
|
|
console.log(`Catched error: ${e}`);
|
|
throw e;
|
|
}
|
|
};
|
|
});
|
|
}
|
|
}
|
|
refreshMemory() {
|
|
// @ts-ignore
|
|
if (!this.view || this.view.buffer.byteLength === 0) {
|
|
this.view = new dataview_1.DataViewPolyfill(this.memory.buffer);
|
|
}
|
|
}
|
|
setMemory(memory) {
|
|
this.memory = memory;
|
|
}
|
|
start(instance) {
|
|
const exports = instance.exports;
|
|
if (exports === null || typeof exports !== "object") {
|
|
throw new Error(`instance.exports must be an Object. Received ${exports}.`);
|
|
}
|
|
const { memory } = exports;
|
|
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._start();
|
|
}
|
|
}
|
|
getImportNamespace(module) {
|
|
let namespace = null;
|
|
for (let imp of WebAssembly.Module.imports(module)) {
|
|
// We only check for the functions
|
|
if (imp.kind !== "function") {
|
|
continue;
|
|
}
|
|
// We allow functions in other namespaces other than wasi
|
|
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) {
|
|
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");
|
|
}
|
|
}
|
|
}
|
|
exports.default = WASIDefault;
|
|
WASIDefault.defaultBindings = defaultBindings;
|
|
// Also export it as a field in the export object
|
|
exports.WASI = WASIDefault;
|