UNPKG

@wapython/wasi

Version:

Javascript library for interacting with WASI Modules in Node.js and the Browser.

1,050 lines (1,049 loc) 64.3 kB
"use strict"; /* MIT licensed. See README.md for copyright and history information. */ var __read = (this && this.__read) || function (o, n) { var m = typeof Symbol === "function" && o[Symbol.iterator]; if (!m) return o; var i = m.call(o), r, ar = [], e; try { while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); } catch (error) { e = { error: error }; } finally { try { if (r && !r.done && (m = i["return"])) m.call(i); } finally { if (e) throw e.error; } } return ar; }; var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); }; var __values = (this && this.__values) || function(o) { var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; if (m) return m.call(o); if (o && typeof o.length === "number") return { next: function () { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); var debug_1 = __importDefault(require("debug")); var log = (0, debug_1.default)("wasi"); var logOpen = (0, debug_1.default)("wasi:open"); // just log opening files, which is useful var types_1 = require("./types"); var typedarray_to_buffer_1 = __importDefault(require("typedarray-to-buffer")); var constants_1 = require("./constants"); var 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; var 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; var STDERR_DEFAULT_RIGHTS = STDOUT_DEFAULT_RIGHTS; var msToNs = function (ms) { var msInt = Math.trunc(ms); var decimal = BigInt(Math.round((ms - msInt) * 1000000)); var ns = BigInt(msInt) * BigInt(1000000); return ns + decimal; }; var nsToMs = function (ns) { if (typeof ns === "number") { ns = Math.trunc(ns); } var nsInt = BigInt(ns); return Number(nsInt / BigInt(1000000)); }; var wrap = function (f) { return function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } try { return f.apply(void 0, __spreadArray([], __read(args), false)); } catch (err) { // log("WASI error", err); var e = err; // If it's an error from the fs if ((e === null || e === void 0 ? void 0 : e.code) && typeof (e === null || e === void 0 ? void 0 : 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 types_1.WASIError) { return e.errno; } // Otherwise we let the error bubble up throw e; } }; }; var stat = function (wasi, fd) { var entry = wasi.FD_MAP.get(fd); // console.log("stat", { fd, entry, FD_MAP: wasi.FD_MAP }); // log("stat", { fd, entry, FD_MAP: wasi.FD_MAP }); if (!entry) { throw new types_1.WASIError(constants_1.WASI_EBADF); } if (entry.filetype === undefined) { var stats = wasi.bindings.fs.fstatSync(entry.real); var _a = translateFileAttributes(wasi, fd, stats), filetype = _a.filetype, rightsBase = _a.rightsBase, rightsInheriting = _a.rightsInheriting; entry.filetype = filetype; if (!entry.rights) { entry.rights = { base: rightsBase, inheriting: rightsInheriting, }; } } return entry; }; var translateFileAttributes = function (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(): { var filetype = constants_1.WASI_FILETYPE_CHARACTER_DEVICE; if (fd !== undefined && wasi.bindings.isTTY(fd)) { return { filetype: filetype, rightsBase: constants_1.RIGHTS_TTY_BASE, rightsInheriting: constants_1.RIGHTS_TTY_INHERITING, }; } return { filetype: 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(0), rightsInheriting: BigInt(0), }; default: return { filetype: constants_1.WASI_FILETYPE_UNKNOWN, rightsBase: BigInt(0), rightsInheriting: BigInt(0), }; } }; // const logToFile = (...args) => { // require("fs").appendFileSync( // "/tmp/wasi.log", // args.map((x) => `${x}`).join(" ") + "\n" // ); // }; var WASI = /** @class */ (function () { function WASI(wasiConfig) { var e_1, _a; var _this = this; this.spinLock = wasiConfig.spinLock; this.waitForStdin = wasiConfig.waitForStdin; this.sendStdout = wasiConfig.sendStdout; this.sendStderr = wasiConfig.sendStderr; // Destructure our wasiConfig var preopens = {}; if (wasiConfig.preopens) { preopens = wasiConfig.preopens; } var env = {}; if (wasiConfig && wasiConfig.env) { env = wasiConfig.env; } var args = []; if (wasiConfig && wasiConfig.args) { args = wasiConfig.args; } var 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(0), }, path: "/dev/stdin", }, ], [ constants_1.WASI_STDOUT_FILENO, { real: 1, filetype: constants_1.WASI_FILETYPE_CHARACTER_DEVICE, // offset: BigInt(0), rights: { base: STDOUT_DEFAULT_RIGHTS, inheriting: BigInt(0), }, path: "/dev/stdout", }, ], [ constants_1.WASI_STDERR_FILENO, { real: 2, filetype: constants_1.WASI_FILETYPE_CHARACTER_DEVICE, // offset: BigInt(0), rights: { base: STDERR_DEFAULT_RIGHTS, inheriting: BigInt(0), }, path: "/dev/stderr", }, ], ]); var fs = this.bindings.fs; var path = this.bindings.path; try { for (var _b = __values(Object.entries(preopens)), _c = _b.next(); !_c.done; _c = _b.next()) { var _d = __read(_c.value, 2), k = _d[0], v = _d[1]; var real = fs.openSync(v, fs.constants.O_RDONLY); var newfd = this.getUnusedFileDescriptor(); this.FD_MAP.set(newfd, { real: 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, }); } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } finally { if (e_1) throw e_1.error; } } var getiovs = function (iovs, iovsLen) { // iovs* -> [iov, iov, ...] // __wasi_ciovec_t { // void* buf, // size_t buf_len, // } _this.refreshMemory(); var buffers = Array.from({ length: iovsLen }, function (_, i) { var ptr = iovs + i * 8; var buf = _this.view.getUint32(ptr, true); var bufLen = _this.view.getUint32(ptr + 4, true); var buffer = new Uint8Array(_this.memory.buffer, buf, bufLen); return (0, typedarray_to_buffer_1.default)(buffer); }); return buffers; }; var CHECK_FD = function (fd, rights) { // log("CHECK_FD", { fd, rights }); var stats = stat(_this, fd); // log("CHECK_FD", { stats }); if (rights !== BigInt(0) && (stats.rights.base & rights) === BigInt(0)) { throw new types_1.WASIError(constants_1.WASI_EPERM); } return stats; }; var CPUTIME_START = bindings.hrtime(); var now = function (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: // TODO -- this assumes 1 thread return bindings.hrtime() - CPUTIME_START; default: return null; } }; this.wasiImport = { args_get: function (argv, argvBuf) { _this.refreshMemory(); var coffset = argv; var offset = argvBuf; args.forEach(function (a) { _this.view.setUint32(coffset, offset, true); coffset += 4; offset += Buffer.from(_this.memory.buffer).write("".concat(a, "\0"), offset); }); return constants_1.WASI_ESUCCESS; }, args_sizes_get: function (argc, argvBufSize) { _this.refreshMemory(); _this.view.setUint32(argc, args.length, true); var size = args.reduce(function (acc, a) { return acc + Buffer.byteLength(a) + 1; }, 0); _this.view.setUint32(argvBufSize, size, true); return constants_1.WASI_ESUCCESS; }, environ_get: function (environ, environBuf) { _this.refreshMemory(); var coffset = environ; var offset = environBuf; Object.entries(env).forEach(function (_a) { var _b = __read(_a, 2), key = _b[0], value = _b[1]; _this.view.setUint32(coffset, offset, true); coffset += 4; offset += Buffer.from(_this.memory.buffer).write("".concat(key, "=").concat(value, "\0"), offset); }); return constants_1.WASI_ESUCCESS; }, environ_sizes_get: function (environCount, environBufSize) { _this.refreshMemory(); var envProcessed = Object.entries(env).map(function (_a) { var _b = __read(_a, 2), key = _b[0], value = _b[1]; return "".concat(key, "=").concat(value, "\0"); }); var size = envProcessed.reduce(function (acc, e) { return acc + Buffer.byteLength(e); }, 0); _this.view.setUint32(environCount, envProcessed.length, true); _this.view.setUint32(environBufSize, size, true); return constants_1.WASI_ESUCCESS; }, clock_res_get: function (clockId, resolution) { var 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); break; } case constants_1.WASI_CLOCK_REALTIME: { res = BigInt(1000); break; } } if (!res) { throw Error("invalid clockId"); } _this.view.setBigUint64(resolution, res); return constants_1.WASI_ESUCCESS; }, clock_time_get: function (clockId, _precision, time) { _this.refreshMemory(); var n = now(clockId); if (n === null) { return constants_1.WASI_EINVAL; } _this.view.setBigUint64(time, BigInt(n), true); return constants_1.WASI_ESUCCESS; }, fd_advise: wrap(function (fd, _offset, _len, _advice) { CHECK_FD(fd, constants_1.WASI_RIGHT_FD_ADVISE); return constants_1.WASI_ENOSYS; }), fd_allocate: wrap(function (fd, _offset, _len) { CHECK_FD(fd, constants_1.WASI_RIGHT_FD_ALLOCATE); return constants_1.WASI_ENOSYS; }), fd_close: wrap(function (fd) { var stats = CHECK_FD(fd, BigInt(0)); fs.closeSync(stats.real); _this.FD_MAP.delete(fd); return constants_1.WASI_ESUCCESS; }), fd_datasync: wrap(function (fd) { var stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_DATASYNC); fs.fdatasyncSync(stats.real); return constants_1.WASI_ESUCCESS; }), fd_fdstat_get: wrap(function (fd, bufPtr) { var 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); // 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(stats.rights.base), true); // u64 _this.view.setBigUint64(bufPtr + 8 + 8, BigInt(stats.rights.inheriting), true); // u64 return constants_1.WASI_ESUCCESS; }), fd_fdstat_set_flags: wrap(function (fd, _flags) { CHECK_FD(fd, constants_1.WASI_RIGHT_FD_FDSTAT_SET_FLAGS); return constants_1.WASI_ENOSYS; }), fd_fdstat_set_rights: wrap(function (fd, fsRightsBase, fsRightsInheriting) { var stats = CHECK_FD(fd, BigInt(0)); var nrb = stats.rights.base | fsRightsBase; if (nrb > stats.rights.base) { return constants_1.WASI_EPERM; } var 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(function (fd, bufPtr) { var stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_FILESTAT_GET); var rstats = fs.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 constants_1.WASI_ESUCCESS; }), fd_filestat_set_size: wrap(function (fd, stSize) { var 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(function (fd, stAtim, stMtim, fstflags) { var stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_FILESTAT_SET_TIMES); var rstats = fs.fstatSync(stats.real); var atim = rstats.atime; var mtim = rstats.mtime; var n = nsToMs(now(constants_1.WASI_CLOCK_REALTIME)); var atimflags = constants_1.WASI_FILESTAT_SET_ATIM | constants_1.WASI_FILESTAT_SET_ATIM_NOW; if ((fstflags & atimflags) === atimflags) { return constants_1.WASI_EINVAL; } var 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(function (fd, bufPtr) { var _a; var stats = CHECK_FD(fd, BigInt(0)); // log("fd_prestat_get", { fd, stats }); if (!stats.path) { return constants_1.WASI_EINVAL; } _this.refreshMemory(); _this.view.setUint8(bufPtr, constants_1.WASI_PREOPENTYPE_DIR); _this.view.setUint32(bufPtr + 4, // TODO: this is definitely completely wrong unless preopens=/. Buffer.byteLength((_a = stats.fakePath) !== null && _a !== void 0 ? _a : stats.path), true); return constants_1.WASI_ESUCCESS; }), fd_prestat_dir_name: wrap(function (fd, pathPtr, pathLen) { var _a; var stats = CHECK_FD(fd, BigInt(0)); if (!stats.path) { return constants_1.WASI_EINVAL; } _this.refreshMemory(); Buffer.from(_this.memory.buffer).write((_a = stats.fakePath) !== null && _a !== void 0 ? _a : stats.path /* TODO: wrong in general! */, pathPtr, pathLen, "utf8"); return constants_1.WASI_ESUCCESS; }), fd_pwrite: wrap(function (fd, iovs, iovsLen, offset, nwritten) { var stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_WRITE | constants_1.WASI_RIGHT_FD_SEEK); var written = 0; getiovs(iovs, iovsLen).forEach(function (iov) { var 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(function (fd, iovs, iovsLen, nwritten) { var stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_WRITE); var IS_STDOUT = stats.real == 1; var IS_STDERR = stats.real == 2; var written = 0; getiovs(iovs, iovsLen).forEach(function (iov) { if (iov.byteLength == 0) return; // log( // `writing to fd=${fd}: `, // JSON.stringify(new TextDecoder().decode(iov)), // JSON.stringify(iov) // ); 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 { // useful to be absolutely sure if wasi is writing something: // log(`write "${new TextDecoder().decode(iov)}" to ${fd})`); var w = 0; while (w < iov.byteLength) { // log(`write ${iov.byteLength} bytes to fd=${stats.real}`); var i = fs.writeSync(stats.real, iov, w, iov.byteLength - w, stats.offset ? Number(stats.offset) : null); // log(`just wrote i=${i} bytes`); if (stats.offset) stats.offset += BigInt(i); w += i; } written += w; } }); _this.view.setUint32(nwritten, written, true); return constants_1.WASI_ESUCCESS; }), fd_pread: wrap(function (fd, iovs, iovsLen, offset, nread) { var e_2, _a; var stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_READ | constants_1.WASI_RIGHT_FD_SEEK); var read = 0; try { outer: for (var _b = __values(getiovs(iovs, iovsLen)), _c = _b.next(); !_c.done; _c = _b.next()) { var iov = _c.value; var r = 0; while (r < iov.byteLength) { var length_1 = iov.byteLength - r; var 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_1) { break outer; } } read += r; } } catch (e_2_1) { e_2 = { error: e_2_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } finally { if (e_2) throw e_2.error; } } _this.view.setUint32(nread, read, true); return constants_1.WASI_ESUCCESS; }), fd_read: wrap(function (fd, iovs, iovsLen, nread) { var e_3, _a; var stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_READ); var IS_STDIN = stats.real === 0; var read = 0; try { // logToFile( // `fd_read: ${IS_STDIN}, ${ // this.stdinBuffer?.length // } ${this.stdinBuffer?.toString()}` // ); // log("stat", { fd, entry, FD_MAP: wasi.FD_MAP }); outer: for (var _b = __values(getiovs(iovs, iovsLen)), _c = _b.next(); !_c.done; _c = _b.next()) { var iov = _c.value; var r = 0; while (r < iov.byteLength) { var length_2 = iov.byteLength - r; var position = IS_STDIN || stats.offset === undefined ? null : Number(stats.offset); var rr = 0; if (IS_STDIN && _this.waitForStdin != null) { if (_this.stdinBuffer != null) { // just got stdin after waiting for it in poll_oneoff // TODO: Do we need to limit length or iov will overflow? // Or will the below just work fine? It might. // Second remark -- we do not do anything special here to try to // handle seeing EOF (ctrl+d) in the stream. No matter what I try, // doing something here (e.g., returning 0 bytes read) doesn't // properly work with libedit. So we leave it alone and let // our slightly patched libedit handle control+d. rr = _this.stdinBuffer.copy(iov); if (rr == _this.stdinBuffer.length) { _this.stdinBuffer = undefined; } else { _this.stdinBuffer = _this.stdinBuffer.slice(rr); } } } else { // console.log("fs.readSync", {fd:stats.real, iov,r,length, position}); rr = fs.readSync(stats.real, // fd iov, // buffer r, // offset length_2, // length position // position ); } // TODO: I'm not sure which type of files should have an offset yet. // E.g., obviously a regular file should and obviously stdin (a character // device) and a pipe (which has type WASI_FILETYPE_SOCKET_STREAM) does not. if (stats.filetype == constants_1.WASI_FILETYPE_REGULAR_FILE) { stats.offset = (stats.offset ? stats.offset : BigInt(0)) + BigInt(rr); } r += rr; read += rr; // If we don't read anything, or we receive less than requested if (rr === 0 || rr < length_2) { break outer; } } } } catch (e_3_1) { e_3 = { error: e_3_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } finally { if (e_3) throw e_3.error; } } _this.view.setUint32(nread, read, true); return constants_1.WASI_ESUCCESS; }), fd_readdir: wrap(function (fd, bufPtr, bufLen, cookie, bufusedPtr) { var stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_READDIR); // log("fd_readdir got stats = ", stats); _this.refreshMemory(); var entries = fs.readdirSync(stats.path, { withFileTypes: true }); var startPtr = bufPtr; for (var i = Number(cookie); i < entries.length; i += 1) { var entry = entries[i]; var 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; } // We use lstat instead of stat, since stat fails on broken links. // Also, stat resolves the link giving the wrong inode! On the other // hand, lstat works fine on non-links. This is wrong in upstream, // which breaks testing test_compileall.py in the python test suite, // due to doing os.scandir on a directory that contains a broken link. var 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; } var filetype = void 0; 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; } var memory_buffer = Buffer.from(_this.memory.buffer); memory_buffer.write(entry.name, bufPtr); bufPtr += nameLength; } var bufused = bufPtr - startPtr; _this.view.setUint32(bufusedPtr, Math.min(bufused, bufLen), true); return constants_1.WASI_ESUCCESS; }), fd_renumber: wrap(function (from, to) { CHECK_FD(from, BigInt(0)); CHECK_FD(to, BigInt(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(function (fd, offset, whence, newOffsetPtr) { var 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(0)) + BigInt(offset); break; case constants_1.WASI_WHENCE_END: var size = fs.fstatSync(stats.real).size; stats.offset = BigInt(size) + BigInt(offset); break; case constants_1.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 constants_1.WASI_ESUCCESS; }), fd_tell: wrap(function (fd, offsetPtr) { var stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_TELL); _this.refreshMemory(); if (!stats.offset) { stats.offset = BigInt(0); } _this.view.setBigUint64(offsetPtr, stats.offset, true); return constants_1.WASI_ESUCCESS; }), fd_sync: wrap(function (fd) { var stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_SYNC); fs.fsyncSync(stats.real); return constants_1.WASI_ESUCCESS; }), path_create_directory: wrap(function (fd, pathPtr, pathLen) { var stats = CHECK_FD(fd, constants_1.WASI_RIGHT_PATH_CREATE_DIRECTORY); if (!stats.path) { return constants_1.WASI_EINVAL; } _this.refreshMemory(); var p = Buffer.from(_this.memory.buffer, pathPtr, pathLen).toString(); fs.mkdirSync(path.resolve(stats.path, p)); return constants_1.WASI_ESUCCESS; }), path_filestat_get: wrap(function (fd, flags, pathPtr, pathLen, bufPtr) { var stats = CHECK_FD(fd, constants_1.WASI_RIGHT_PATH_FILESTAT_GET); if (!stats.path) { return constants_1.WASI_EINVAL; } _this.refreshMemory(); var p = Buffer.from(_this.memory.buffer, pathPtr, pathLen).toString(); var rstats; if (flags) { rstats = fs.statSync(path.resolve(stats.path, p)); } else { // there is exactly one flag implemented called "__WASI_LOOKUPFLAGS_SYMLINK_FOLLOW"; // it's 1 and is used to follow links, i.e., // implement lstat -- this is ignored in upstream. // See zig/lib/libc/wasi/libc-bottom-half/cloudlibc/src/libc/sys/stat/fstatat.c 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 constants_1.WASI_ESUCCESS; }), path_filestat_set_times: wrap(function (fd, _dirflags, pathPtr, pathLen, stAtim, stMtim, fstflags) { var stats = CHECK_FD(fd, constants_1.WASI_RIGHT_PATH_FILESTAT_SET_TIMES); if (!stats.path) { return constants_1.WASI_EINVAL; } _this.refreshMemory(); var rstats = fs.fstatSync(stats.real); var atim = rstats.atime; var mtim = rstats.mtime; var n = nsToMs(now(constants_1.WASI_CLOCK_REALTIME)); var atimflags = constants_1.WASI_FILESTAT_SET_ATIM | constants_1.WASI_FILESTAT_SET_ATIM_NOW; if ((fstflags & atimflags) === atimflags) { return constants_1.WASI_EINVAL; } var 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; } var p = Buffer.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(function (oldFd, _oldFlags, oldPath, oldPathLen, newFd, newPath, newPathLen) { var ostats = CHECK_FD(oldFd, constants_1.WASI_RIGHT_PATH_LINK_SOURCE); var nstats = CHECK_FD(newFd, constants_1.WASI_RIGHT_PATH_LINK_TARGET); if (!ostats.path || !nstats.path) { return constants_1.WASI_EINVAL; } _this.refreshMemory(); var op = Buffer.from(_this.memory.buffer, oldPath, oldPathLen).toString(); var np = Buffer.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(function (dirfd, _dirflags, pathPtr, pathLen, oflags, fsRightsBase, fsRightsInheriting, fsFlags, fd) { var stats = CHECK_FD(dirfd, constants_1.WASI_RIGHT_PATH_OPEN); fsRightsBase = BigInt(fsRightsBase); fsRightsInheriting = BigInt(fsRightsInheriting); var read = (fsRightsBase & (constants_1.WASI_RIGHT_FD_READ | constants_1.WASI_RIGHT_FD_READDIR)) !== BigInt(0); var 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(0); var 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 var neededBase = fsRightsBase | constants_1.WASI_RIGHT_PATH_OPEN; var 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(); var p = Buffer.from(_this.memory.buffer, pathPtr, pathLen).toString(); logOpen("path_open", p); var fullUnresolved = path.resolve(stats.path, p); if (path.relative(stats.path, fullUnresolved).startsWith("..")) { return constants_1.WASI_ENOTCAPABLE; } var full; try { full = fs.realpathSync(fullUnresolved); if (path.relative(stats.path, full).startsWith("..")) { return constants_1.WASI_ENOTCAPABLE; } } catch (e) { if ((e === null || e === void 0 ? void 0 : e.code) === "ENOENT") { full = fullUnresolved; } else { // log("** openpath FAIL: p = ", p, e); 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) */ var isDirectory; try { isDirectory = fs.statSync(full).isDirectory(); } catch (e) { } var realfd; if (!write && isDirectory) { realfd = fs.openSync(full, fs.constants.O_RDONLY); } else { realfd = fs.openSync(full, noflags); } var newfd = _this.getUnusedFileDescriptor(); // log(`** openpath got fd: p='${p}', fd=${newfd}`); _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(function (fd, pathPtr, pathLen, buf, bufLen, bufused) { var stats = CHECK_FD(fd, constants_1.WASI_RIGHT_PATH_READLINK); if (!stats.path) { return constants_1.WASI_EINVAL; } _this.refreshMemory(); var p = Buffer.from(_this.memory.buffer, pathPtr, pathLen).toString(); var full = path.resolve(stats.path, p); var r = fs.readlinkSync(full); var used = Buffer.from(_this.memory.buffer).write(r, buf, bufLen); _this.view.setUint32(bufused, used, true); return constants_1.WASI_ESUCCESS; }), path_remove_directory: wrap(function (fd, pathPtr, pathLen) { var stats = CHECK_FD(fd, constants_1.WASI_RIGHT_PATH_REMOVE_DIRECTORY); if (!stats.path) { return constants_1.WASI_EINVAL; } _this.refreshMemory(); var p = Buffer.from(_this.memory.buffer, pathPtr, pathLen).toString(); fs.rmdirSync(path.resolve(stats.path, p)); return constants_1.WASI_ESUCCESS; }), path_rename: wrap(function (oldFd, oldPath, oldPathLen, newFd, newPath, newPathLen) { var ostats = CHECK_FD(oldFd, constants_1.WASI_RIGHT_PATH_RENAME_SOURCE); var nstats = CHECK_FD(newFd, constants_1.WASI_RIGHT_PATH_RENAME_TARGET); if (!ostats.path || !nstats.path) { return constants_1.WASI_EINVAL; }