UNPKG

@csound/browser

Version:

[![npm (scoped with tag)](https://shields.shivering-isles.com/npm/v/@csound/browser/latest)](https://www.npmjs.com/package/@csound/browser) [![GitHub Workflow Status](https://shields.shivering-isles.com/github/workflow/status/csound/csound/csound_wasm)](h

1,303 lines (1,139 loc) 31.5 kB
import { encoder, decoder } from "../utils/text-encoders.js"; import * as constants from "./constants.js"; const googPath = goog.require("goog.string.path"); /** @define {boolean} */ const DEBUG_WASI = goog.define("DEBUG_WASI", false); function removeLeadingSlash(path) { return path.replace(/^\//g, ""); } function splitPathSegments(path) { if (!path) { return []; } return path .split("/") .filter((segment) => segment.length > 0 && segment !== "."); } function normalizeAbsolutePath(path) { if (!path) { return "/"; } const segments = splitPathSegments(path); const resolved = []; segments.forEach((segment) => { if (segment === "..") { if (resolved.length > 0) { resolved.pop(); } } else { resolved.push(segment); } }); return resolved.length > 0 ? `/${resolved.join("/")}` : "/"; } function ensureAbsolutePath(basePath, path) { if (!path || path === ".") { return normalizeAbsolutePath(basePath || "/"); } if (/^\//.test(path)) { return normalizeAbsolutePath(path); } const baseSegments = splitPathSegments(basePath || "/"); const relativeSegments = path.split("/"); const resolvedSegments = [...baseSegments]; relativeSegments.forEach((segment) => { if (!segment || segment === ".") { return; } if (segment === "..") { if (resolvedSegments.length > 0) { resolvedSegments.pop(); } return; } resolvedSegments.push(segment); }); return resolvedSegments.length > 0 ? `/${resolvedSegments.join("/")}` : "/"; } function shouldOpenReader(rights) { /** @suppress {checkTypes} */ const bor = constants.WASI_RIGHT_FD_READ | constants.WASI_RIGHT_FD_READDIR; /** @suppress {suspiciousCode} */ const result = (rights & bor) !== goog.global.BigInt(0); return result; } function performanceNowPoly() { /* eslint-disable-next-line unicorn/no-typeof-undefined */ if (typeof performance === "undefined" || typeof performance.now === "undefined") { const nowOffset = Date.now(); return Date.now() - nowOffset; } else { return performance.now(); } } function concatUint8Arrays(arrays) { // sum of individual array lengths const totalLength = arrays.reduce((accumulator, value) => accumulator + value.length, 0); if (arrays.length === 0) return; const result = new Uint8Array(totalLength); // for each array - copy it over result // next array is copied right after the previous one let length = 0; for (const array of arrays) { result.set(array, length); length += array.length; } return result; } /** * @constructor * @this {WasiThis} */ export const WASI = function ({ preopens }) { this.fd = Array.from({ length: 4 }); this.fd[0] = { fd: 0, path: "/dev/stdin", seekPos: goog.global.BigInt(0), buffers: [] }; this.fd[1] = { fd: 1, path: "/dev/stdout", seekPos: goog.global.BigInt(0), buffers: [] }; this.fd[2] = { fd: 2, path: "/dev/stderr", seekPos: goog.global.BigInt(0), buffers: [] }; this.fd[3] = { fd: 3, path: "/", seekPos: goog.global.BigInt(0), buffers: [], type: "dir" }; this.getMemory = this.getMemory.bind(this); this.CPUTIME_START = 0; this.cwd = "/"; this.preopens = preopens || {}; }; /** * @function * @param {!WasmInst} instance */ WASI.prototype.start = function (instance) { this.CPUTIME_START = performanceNowPoly(); const exports = instance["exports"]; exports["_start"](); }; /** * @function * @param {!WebAssembly.Module} module */ WASI.prototype.getImports = function (module) { const options = {}; const neededImports = WebAssembly.Module.imports(module); for (const neededImport of neededImports) { if (neededImport["kind"] === "function" && neededImport.module.startsWith("wasi_")) { if (typeof options[neededImport["module"]] !== "object") { options[neededImport["module"]] = {}; } options[neededImport["module"]][neededImport["name"]] = this[neededImport["name"]].bind(this); } } return options; }; /** * @function * @param {!WebAssembly.Memory} memory */ WASI.prototype.setMemory = function (memory) { this.memory = memory; }; /** * @function * @return {DataView} */ WASI.prototype.getMemory = function () { if (!this.view || !this.view.buffer || !this.view.buffer.byteLength) { this.view = new DataView(this.memory.buffer); } return this.view; }; /** * @function * @param {string} path * @return {string} */ WASI.prototype.resolvePath = function (path) { return ensureAbsolutePath(this.cwd, path); }; /** * @function * @param {string} filePath * @return {?Object} */ WASI.prototype.findEntry = function (filePath) { const normalized = normalizeAbsolutePath(filePath); const entries = Object.values(this.fd); for (const entry of entries) { if (entry && entry.path === normalized) { return entry; } } // eslint-disable-next-line unicorn/no-null return null; }; /** * @function * @param {string} path * @return {number} */ WASI.prototype.chdir = function (path) { const targetPath = normalizeAbsolutePath(ensureAbsolutePath(this.cwd, path)); if (targetPath === "/") { this.cwd = "/"; if (this.fd[3]) { this.fd[3].path = "/"; this.fd[3].type = "dir"; } return constants.WASI_ESUCCESS; } const entry = this.findEntry(targetPath); if (!entry) { if (DEBUG_WASI) { console.warn(`chdir: path ${targetPath} does not exist`); } return constants.WASI_ENOENT; } if (entry.type && entry.type !== "dir") { if (DEBUG_WASI) { console.warn(`chdir: path ${targetPath} not a directory`); } return constants.WASI_ENOTDIR; } this.cwd = targetPath; if (this.fd[3]) { this.fd[3].path = targetPath; this.fd[3].type = "dir"; } return constants.WASI_ESUCCESS; }; WASI.prototype.msToNs = function (ms) { const msInt = Math.trunc(ms); const decimal = goog.global.BigInt(Math.round((ms - msInt) * 1000000)); const ns = goog.global.BigInt(msInt) * goog.global.BigInt(1000000); return ns + decimal; }; WASI.prototype.now = function (clockId) { switch (clockId) { case constants.WASI_CLOCK_MONOTONIC: { return Math.floor(performanceNowPoly()); } case constants.WASI_CLOCK_REALTIME: { return this.msToNs(Date.now()); } case constants.WASI_CLOCK_PROCESS_CPUTIME_ID: case constants.WASI_CLOCK_THREAD_CPUTIME_ID: { return Math.floor(performanceNowPoly() - this.CPUTIME_START); } default: { return 0; } } }; /** * @export * @return {number} */ WASI.prototype.args_get = function (argv, argvBuf) { if (DEBUG_WASI) { console.log("args_get", argv, argvBuf, constants); } return constants.WASI_ESUCCESS; }; /** * @export * @return {number} */ WASI.prototype.args_sizes_get = function (argc, argvBufSize) { if (DEBUG_WASI) { console.log("args_sizes_get", argc, argvBufSize, arguments); } return constants.WASI_ESUCCESS; }; /** * @export * @return {number} */ WASI.prototype.clock_res_get = function (clockId, resolution) { if (DEBUG_WASI) { console.log("args_get", clockId, resolution, arguments); } return constants.WASI_ESUCCESS; }; /** * @export * @return {number} */ WASI.prototype.clock_time_get = function (clockId, precision, time) { if (DEBUG_WASI) { console.log("clock_time_get", clockId, precision, time, arguments); } const memory = this.getMemory(); const nextTime = this.now(clockId); memory.setBigUint64(time, goog.global.BigInt(nextTime), true); return constants.WASI_ESUCCESS; }; /** * @export * @return {number} */ WASI.prototype.environ_get = function (environ, environBuf) { if (DEBUG_WASI) { console.log("environ_get", environ, environBuf, arguments); } return constants.WASI_ESUCCESS; }; /** * @export * @return {number} */ WASI.prototype.environ_sizes_get = function (environCount, environBufSize) { if (DEBUG_WASI) { console.log("environ_sizes_get", environCount, environBufSize, arguments); } return constants.WASI_ESUCCESS; }; /** * @export * @return {number} */ WASI.prototype.fd_advise = function (fd, offset, length_, advice) { if (DEBUG_WASI) { console.log("fd_advise", fd, offset, length_, advice, arguments); } return constants.WASI_ENOSYS; }; /** * @export * @return {number} */ WASI.prototype.fd_allocate = function (fd, offset, length_) { if (DEBUG_WASI) { console.log("fd_allocate", fd, offset, length_, arguments); } return constants.WASI_ENOSYS; }; /** * @export * @return {number} */ WASI.prototype.fd_close = function (fd) { if (DEBUG_WASI) { console.log("fd_close", fd, arguments); } return constants.WASI_ESUCCESS; }; /** * @export * @return {number} */ WASI.prototype.fd_datasync = function (fd) { if (DEBUG_WASI) { console.log("fd_datasync", fd, arguments); } return constants.WASI_ESUCCESS; }; // always write access in browser scope /** * @export * @return {number} */ WASI.prototype.fd_fdstat_get = function (fd, bufPtr) { if (DEBUG_WASI) { console.log("fd_fdstat_get", fd, bufPtr, arguments); } const memory = this.getMemory(); memory.setUint8(bufPtr + 4, constants.WASI_FILETYPE_REGULAR_FILE); memory.setUint16(bufPtr + 2, 0, true); memory.setUint16(bufPtr + 4, 0, true); memory.setBigUint64(bufPtr + 8, goog.global.BigInt(constants.RIGHTS_REGULAR_FILE_BASE), true); memory.setBigUint64( bufPtr + 8 + 8, goog.global.BigInt(constants.RIGHTS_REGULAR_FILE_INHERITING), true, ); return constants.WASI_ESUCCESS; }; /** * @export * @return {number} */ WASI.prototype.fd_fdstat_set_flags = function (fd, flags) { if (DEBUG_WASI) { console.log("fd_fdstat_set_flags", fd, flags, arguments); } return constants.WASI_ENOSYS; }; /** * @export * @return {number} */ WASI.prototype.fd_fdstat_set_rights = function (fd, fsRightsBase, fsRightsInheriting) { if (DEBUG_WASI) { console.log("fd_fdstat_set_rights", fd, fsRightsBase, fsRightsInheriting, arguments); } return constants.WASI_ESUCCESS; }; /** * @export * @return {number} */ WASI.prototype.fd_filestat_get = function (fd, bufPtr) { if (DEBUG_WASI) { console.log("fd_filestat_get", fd, bufPtr, arguments); } let filesize = 0; if (this.fd[fd]) { filesize = this.fd[fd].buffers.reduce(function (accumulator, uintArray) { return accumulator + uintArray?.byteLength ? uintArray?.byteLength : 0; }, 0); } const memory = this.getMemory(); memory.setBigUint64(bufPtr, goog.global.BigInt(fd), true); bufPtr += 8; memory.setBigUint64(bufPtr, goog.global.BigInt(fd), true); bufPtr += 8; memory.setUint8(bufPtr, constants.WASI_FILETYPE_REGULAR_FILE); bufPtr += 8; memory.setBigUint64(bufPtr, goog.global.BigInt(1), true); bufPtr += 8; memory.setBigUint64(bufPtr, goog.global.BigInt(filesize), true); bufPtr += 8; memory.setBigUint64(bufPtr, this.msToNs(this.CPUTIME_START), true); bufPtr += 8; memory.setBigUint64(bufPtr, this.msToNs(this.CPUTIME_START), true); bufPtr += 8; memory.setBigUint64(bufPtr, this.msToNs(this.CPUTIME_START), true); return constants.WASI_ESUCCESS; }; /** * @export * @return {number} */ WASI.prototype.fd_filestat_set_size = function (fd, newSize) { if (DEBUG_WASI) { console.log("fd_filestat_set_size", fd, newSize, arguments); } return constants.WASI_ESUCCESS; }; /** * @export * @return {number} */ WASI.prototype.fd_filestat_set_times = function (fd, stAtim, stMtim, filestatFags) { if (DEBUG_WASI) { console.log("fd_filestat_set_times", fd, stAtim, stMtim, filestatFags, arguments); } return constants.WASI_ESUCCESS; }; /** * @export * @return {number} */ WASI.prototype.fd_pread = function (fd, iovs, iovsLength, offset, nread) { if (DEBUG_WASI) { console.log("fd_pread", fd, iovs, iovsLength, offset, nread, arguments); } return constants.WASI_ESUCCESS; }; /** * @export * @return {number} */ WASI.prototype.fd_prestat_dir_name = function (fd, pathPtr, pathLength) { if (DEBUG_WASI) { console.log("fd_prestat_dir_name", fd, pathPtr, pathLength, this.fd[fd]); } if (!this.fd[fd] && !this.fd[fd - 1]) { return constants.WASI_EBADF; } const { path: directoryName } = this.fd[fd]; const memory = this.getMemory(); const directoryNameBuffer = encoder.encode(directoryName); new Uint8Array(memory.buffer).set(directoryNameBuffer, pathPtr); return constants.WASI_ESUCCESS; }; /** * @export * @return {number} */ WASI.prototype.fd_prestat_get = function (fd, bufPtr) { if (DEBUG_WASI) { console.log("fd_prestat_get", fd, bufPtr, this.fd[fd]); } if (!this.fd[fd]) { return constants.WASI_EBADF; } const { path: directoryName } = this.fd[fd]; const memory = this.getMemory(); const directoryNameBuffer = encoder.encode(directoryName); memory.setUint8(bufPtr, constants.WASI_PREOPENTYPE_DIR); memory.setUint32(bufPtr + 4, directoryNameBuffer.byteLength, true); return constants.WASI_ESUCCESS; }; /** * @export * @return {number} */ WASI.prototype.fd_pwrite = function (fd, iovs, iovsLength, offset, nwritten) { console.log("fd_pwrite", fd, iovs, iovsLength, offset, nwritten, arguments); return constants.WASI_ESUCCESS; }; /** * @export * @return {(number | undefined)} */ WASI.prototype.fd_read = function (fd, iovs, iovsLength, nread) { if (DEBUG_WASI) { console.log("fd_read", fd, iovs, iovsLength, nread, arguments); } const memory = this.getMemory(); const entry = this.fd[fd]; if (!entry || !Array.isArray(entry.buffers)) { if (DEBUG_WASI) { console.error("fd_read: non-existent file descriptor", fd, entry); } memory.setUint32(nread, 0, true); return constants.WASI_EBADF; } const buffers = entry.buffers; if (buffers.length === 0) { memory.setUint32(nread, 0, true); entry.seekPos = goog.global.BigInt(0); return constants.WASI_ESUCCESS; } const totalBuffersLength = buffers.reduce((accumulator, b) => accumulator + b.length, 0); let read = Number(entry.seekPos); let thisRead = 0; let reduced = false; // check for EOF if (read >= totalBuffersLength) { const buf = memory.getUint32(iovs, true); memory.setUint8(buf, 0); memory.setUint32(nread, 0, true); return constants.WASI_ESUCCESS; } for (let index = 0; index < iovsLength; index++) { const ptr = iovs + index * 8; const buf = memory.getUint32(ptr, true); const bufLength = memory.getUint32(ptr + 4, true); if (!reduced) { thisRead += bufLength; Array.from({ length: bufLength }, (_, index) => index).reduce( (accumulator, currentRead) => { if (reduced) { return accumulator; } const [chunkIndex, chunkOffset] = accumulator; let currentChunkIndex = 0; let currentChunkOffset = 0; let found = false; let leadup = 0; let currentBufferChunkLength = buffers[currentChunkIndex] ? buffers[currentChunkIndex].byteLength : 0; if (currentRead === 0) { while (!found) { currentBufferChunkLength = buffers[currentChunkIndex] ? buffers[currentChunkIndex].byteLength : 0; if (leadup <= read && currentBufferChunkLength + leadup > read) { found = true; currentChunkOffset = read - leadup; } else { leadup += currentBufferChunkLength; currentChunkIndex += 1; } } } else { currentChunkIndex = chunkIndex; currentChunkOffset = chunkOffset; } if (buffers[currentChunkIndex]) { memory.setUint8(buf + currentRead, buffers[currentChunkIndex][currentChunkOffset]); if (currentChunkOffset + 1 >= buffers[currentChunkIndex].byteLength) { currentChunkIndex = chunkIndex + 1; currentChunkOffset = 0; } else { currentChunkOffset += 1; } } else { memory.setUint8(buf + currentRead, 0); read += currentRead; reduced = true; } return [currentChunkIndex, currentChunkOffset]; }, [0, 0], ); if (!reduced) { read += bufLength; } } } entry.seekPos = goog.global.BigInt(read); memory.setUint32(nread, thisRead, true); return constants.WASI_ESUCCESS; }; /** * @export * @return {number} */ WASI.prototype.fd_readdir = function (fd, bufPtr, bufLength, cookie, bufusedPtr) { if (DEBUG_WASI) { console.log("fd_readdir", fd, bufPtr, bufLength, cookie, bufusedPtr, arguments); } return constants.WASI_ESUCCESS; }; /** * @export * @return {number} */ WASI.prototype.fd_renumber = function (from, to) { if (DEBUG_WASI) { console.log("fd_renumber", from, to, arguments); } return constants.WASI_ESUCCESS; }; /** * @export * @return {number} */ WASI.prototype.fd_seek = function (fd, offset, whence, newOffsetPtr) { if (DEBUG_WASI) { console.log("fd_seek", fd, offset, whence, newOffsetPtr, arguments); } const memory = this.getMemory(); switch (whence) { case constants.WASI_WHENCE_CUR: { /** @suppress {checkTypes} */ this.fd[fd].seekPos = (this.fd[fd].seekPos ?? goog.global.BigInt(0)) + goog.global.BigInt(offset); break; } case constants.WASI_WHENCE_END: { const currentLength = (this.fd[fd].buffers || []).reduce( (accumulator, value) => accumulator + value.length, 0, ); this.fd[fd].seekPos = BigInt(currentLength) + BigInt(offset); break; } case constants.WASI_WHENCE_SET: { this.fd[fd].seekPos = BigInt(offset); break; } } memory.setBigUint64(newOffsetPtr, this.fd[fd].seekPos, true); return constants.WASI_ESUCCESS; }; /** * @export * @return {number} */ WASI.prototype.fd_sync = function (fd) { if (DEBUG_WASI) { console.log("fd_sync", fd, arguments); } return constants.WASI_ESUCCESS; }; /** * @export * @return {number} */ WASI.prototype.fd_tell = function (fd, offsetPtr) { if (DEBUG_WASI) { console.log("fd_tell", fd, offsetPtr, arguments); } const memory = this.getMemory(); if (!this.fd[fd].seekPos) { this.fd[fd].seekPos = goog.global.BigInt(0); } memory.setBigUint64(offsetPtr, this.fd[fd].seekPos, true); return constants.WASI_ESUCCESS; }; /** * @export * @return {number} */ WASI.prototype.fd_write = function (fd, iovs, iovsLength, nwritten) { if (DEBUG_WASI) { console.log("fd_write", { fd, iovs, iovsLength, nwritten }); } let append = false; const memory = this.getMemory(); this.fd[fd].buffers = this.fd[fd].buffers || []; // append-only, if starting new write from beginning if (this.fd[fd].seekPos === goog.global.BigInt(0) && this.fd[fd].buffers.length > 0) { append = true; } let written = 0; for (let index = 0; index < iovsLength; index++) { const ptr = iovs + index * 8; const buf = memory.getUint32(ptr, true); const bufLength = memory.getUint32(ptr + 4, true); written += bufLength; const chunk = new Uint8Array(memory.buffer, buf, bufLength); if (append) { this.fd[fd].buffers.unshift(chunk.slice(0, bufLength)); } else { this.fd[fd].buffers.push(chunk.slice(0, bufLength)); } } /** @suppress {checkTypes} */ this.fd[fd].seekPos += goog.global.BigInt(written); memory.setUint32(nwritten, written, true); if ([1, 2].includes(fd)) { console.log(decoder.decode(concatUint8Arrays(this.fd[fd].buffers))); } return constants.WASI_ESUCCESS; }; /** * @export * @return {number} */ WASI.prototype.path_create_directory = function (fd, pathPtr, pathLength) { if (DEBUG_WASI) { console.log("path_create_directory", fd, pathPtr, pathLength, arguments); } return constants.WASI_ESUCCESS; }; /** * @export * @return {number} */ WASI.prototype.path_filestat_get = function (fd, flags, pathPtr, pathLength, bufPtr) { if (DEBUG_WASI) { console.log("path_filestat_get", fd, flags, pathPtr, pathLength, bufPtr, arguments); } return constants.WASI_ESUCCESS; }; /** * @export * @return {number} */ WASI.prototype.path_filestat_set_times = function ( fd, dirflags, pathPtr, pathLength, stAtim, stMtim, fstflags, ) { if (DEBUG_WASI) { console.log( "path_filestat_set_times", fd, dirflags, pathPtr, pathLength, stAtim, stMtim, fstflags, arguments, ); } return constants.WASI_ESUCCESS; }; /** * @export * @return {number} */ WASI.prototype.path_link = function ( oldFd, oldFlags, oldPath, oldPathLength, newFd, newPath, newPathLength, ) { if (DEBUG_WASI) { console.log( "path_link", oldFd, oldFlags, oldPath, oldPathLength, newFd, newPath, newPathLength, arguments, ); } return constants.WASI_ESUCCESS; }; /** * @export * @return {number} */ WASI.prototype.path_open = function ( dirfd, dirflags, pathPtr, pathLength, oflags, fsRightsBase, fsRightsInheriting, fsFlags, fd, ) { if (DEBUG_WASI) { console.log( "path_open", dirfd, dirflags, pathPtr, pathLength, oflags, fsRightsBase, fsRightsInheriting, fsFlags, fd, arguments, ); } const memory = this.getMemory(); const directoryPath = (this.fd[dirfd] || { path: this.cwd }).path; const pathOpenBytes = new Uint8Array(memory.buffer, pathPtr, pathLength); const pathOpenString = decoder.decode(pathOpenBytes); let pathOpen; if (dirfd === 3) { // Opening relative to the preopen root (cwd) pathOpen = this.resolvePath(pathOpenString); } else { // Opening relative to a specific directory fd const joined = googPath.join(directoryPath, pathOpenString); pathOpen = normalizeAbsolutePath(joined); } if (pathOpen.startsWith("/..") || pathOpen === "/._" || pathOpen === "/.AppleDouble") { return constants.WASI_EBADF; } const wantsDirectory = (oflags & constants.WASI_O_DIRECTORY) !== 0; const allowCreate = (oflags & constants.WASI_O_CREAT) !== 0; const existingEntry = this.findEntry(pathOpen); if (DEBUG_WASI) { console.log(";; path_open:", pathOpen, "from dirfd", dirfd); console.log(" withReader:", shouldOpenReader(fsRightsBase)); console.log(" oflags:", oflags.toString(16), "fsRightsBase:", fsRightsBase.toString()); console.log(" allowCreate:", allowCreate, "wantsDirectory:", wantsDirectory); console.log(" existingEntry:", existingEntry ? "exists" : "does not exist"); } if (existingEntry && existingEntry.type === "dir" && !wantsDirectory) { return constants.WASI_EISDIR; } if (!existingEntry && wantsDirectory) { return constants.WASI_ENOENT; } // Check if file doesn't exist and shouldn't be created if (!existingEntry && !allowCreate && !wantsDirectory) { // File doesn't exist - write invalid fd and return ENOENT if (DEBUG_WASI) { console.warn(`path_open: file not found: ${pathOpen}`); } // Write maximum unsigned 32-bit value (-1 as signed) to indicate bad fd memory.setUint32(fd, 0xFFFFFFFF, true); return constants.WASI_ENOENT; } const actualFd = existingEntry ? existingEntry.fd : this.fd.length; if (!existingEntry && this.fd[actualFd] === undefined) { this.fd[actualFd] = { fd: actualFd }; } const entryTemplate = existingEntry || this.fd[actualFd] || { fd: actualFd }; this.fd[actualFd] = { ...entryTemplate, fd: actualFd, path: pathOpen, type: wantsDirectory ? "dir" : entryTemplate.type || "file", seekPos: goog.global.BigInt(0), buffers: Array.isArray(entryTemplate.buffers) ? entryTemplate.buffers : [], }; if ((oflags & constants.WASI_O_TRUNC) !== 0 && !wantsDirectory) { this.fd[actualFd].buffers.length = 0; } if (shouldOpenReader(fsRightsBase) && DEBUG_WASI) { console.log("should open a read handle for", pathOpen); } memory.setUint32(fd, actualFd, true); return constants.WASI_ESUCCESS; }; /** * @export * @return {number} */ WASI.prototype.path_readlink = function (fd, pathPtr, pathLength, buf, bufLength, bufused) { if (DEBUG_WASI) { console.log("path_readlink", fd, pathPtr, pathLength, buf, bufLength, bufused, arguments); } return constants.WASI_ESUCCESS; }; /** * @export * @return {number} */ WASI.prototype.path_remove_directory = function (fd, pathPtr, pathLength) { if (DEBUG_WASI) { console.log("path_remove_directory", fd, pathPtr, pathLength); } return constants.WASI_ESUCCESS; }; /** * @export * @return {number} */ WASI.prototype.path_rename = function ( oldFd, oldPath, oldPathLength, newFd, newPath, newPathLength, ) { if (DEBUG_WASI) { console.log( "path_rename", oldFd, oldPath, oldPathLength, newFd, newPath, newPathLength, arguments, ); } return constants.WASI_ESUCCESS; }; /** * @export * @return {number} */ WASI.prototype.path_symlink = function (oldPath, oldPathLength, fd, newPath, newPathLength) { if (DEBUG_WASI) { console.log("path_symlink", oldPath, oldPathLength, fd, newPath, newPathLength, arguments); } return constants.WASI_ESUCCESS; }; /** * @export * @return {number} */ WASI.prototype.path_unlink_file = function (fd, pathPtr, pathLength) { if (fd > 3 && DEBUG_WASI) { console.log("path_unlink_file", fd, pathPtr, pathLength, arguments); } // actual file removal goes here return constants.WASI_ESUCCESS; }; /** * @export * @return {number} */ WASI.prototype.poll_oneoff = function (sin, sout, nsubscriptions, nevents) { if (DEBUG_WASI) { console.log("poll_oneoff", sin, sout, nsubscriptions, nevents, arguments); } return constants.WASI_ESUCCESS; }; /** * @export * @return {number} */ WASI.prototype.proc_exit = function (rval) { if (DEBUG_WASI) { console.log("proc_exit", rval, arguments); } return constants.WASI_ESUCCESS; }; /** * @export * @return {number} */ WASI.prototype.proc_raise = function (sig) { if (DEBUG_WASI) { console.log("proc_raise", sig, arguments); } return constants.WASI_ESUCCESS; }; /** * @export * @return {number} */ WASI.prototype.random_get = function (bufPtr, bufLength) { if (DEBUG_WASI) { console.log("random_get", bufPtr, bufLength); } return constants.WASI_ESUCCESS; }; /** * @export * @return {number} */ WASI.prototype.sched_yield = function () { if (DEBUG_WASI) { console.log("sched_yield", arguments); } return constants.WASI_ESUCCESS; }; /** * @export * @return {number} */ WASI.prototype.sock_recv = function () { if (DEBUG_WASI) { console.log("sock_recv", arguments); } return constants.WASI_ENOSYS; }; /** * @export * @return {number} */ WASI.prototype.sock_send = function () { if (DEBUG_WASI) { console.log("sock_send", arguments); } return constants.WASI_ENOSYS; }; /** * @export * @return {number} */ WASI.prototype.sock_shutdown = function () { if (DEBUG_WASI) { console.log("sock_shutdown", arguments); } return constants.WASI_ENOSYS; }; // helpers WASI.prototype.findBuffers = function (filePath /* string */) { const maybeFd = Object.values(this.fd).find(({ path }) => path === filePath); return maybeFd && maybeFd.buffers; }; // fs api WASI.prototype.readdir = function (dirname /* string */) { const absoluteDir = this.resolvePath(dirname); const prefixPath = absoluteDir === "/" ? "/" : `${absoluteDir}/`; const files = []; Object.values(this.fd).forEach((entry) => { if (!entry || !entry.path) { return; } const { path } = entry; if (!path.startsWith(prefixPath)) { return; } const rest = path.slice(prefixPath.length); if (rest.length === 0) { return; } if (!/\//g.test(rest)) { files.push(path); } }); return files .map((p) => removeLeadingSlash(p.replace(prefixPath, ""))) .filter((p) => !!p); }; WASI.prototype.writeFile = function (fname /* string */, data /* Uint8Array */) { const filePath = this.resolvePath(fname); const nextFd = Object.keys(this.fd).length; const maybeOldFd = Object.values(this.fd).find(({ path }) => path === filePath); this.fd[nextFd] = { fd: nextFd, path: filePath, seekPos: goog.global.BigInt(0), buffers: [data], type: "file", }; if (maybeOldFd) { delete this.fd[maybeOldFd]; } }; WASI.prototype.appendFile = function (fname /* string */, data /* Uint8Array */) { const filePath = this.resolvePath(fname); const buffers = this.findBuffers(filePath); if (buffers) { buffers.push(data); } else { console.error(`Can't append to non-existing file ${fname}`); } }; WASI.prototype.readFile = function (fname /* string */) { const filePath = this.resolvePath(fname); const buffers = this.findBuffers(filePath); if (buffers) { return concatUint8Arrays(buffers); } }; WASI.prototype.readStdOut = function () { const maybeFd = Object.values(this.fd[0]); const buffers = (maybeFd && maybeFd.buffers) || []; return concatUint8Arrays(buffers); }; WASI.prototype.unlink = function (fname /* string */) { const filePath = this.resolvePath(fname); const maybeFd = Object.values(this.fd).find(({ path }) => path === filePath); if (maybeFd) { delete this.fd[maybeFd.fd]; } else { console.error(`While trying to unlink ${filePath}, path not found`); } }; WASI.prototype.mkdir = function (dirname /* string */) { const cleanPath = this.resolvePath(dirname); const files = []; Object.values(this.fd).forEach(({ path }) => { return path.startsWith(cleanPath) && files.push(path); }); const alreadyExist = files.length > 0; if (alreadyExist) { console.warn(`mkdir: path ${dirname} already exists`); } else { const nextFd = Object.keys(this.fd).length; this.fd[nextFd] = { fd: nextFd, path: cleanPath, type: "dir", }; } }; WASI.prototype.stat = function (fname /* string */) { const filePath = this.resolvePath(fname); const maybeFd = Object.values(this.fd).find(({ path }) => path === filePath); if (!maybeFd) { return undefined; } const buffers = maybeFd.buffers || []; const size = buffers.reduce((accumulator, buffer) => { return accumulator + (buffer?.byteLength || 0); }, 0); const isDirectory = maybeFd.type === "dir"; return { dev: 0, ino: maybeFd.fd, mode: isDirectory ? 16877 : 33188, // 0o40755 for dir, 0o100644 for file nlink: 1, uid: 0, gid: 0, rdev: 0, size, blksize: 4096, blocks: Math.ceil(size / 512), atimeMs: this.CPUTIME_START, mtimeMs: this.CPUTIME_START, ctimeMs: this.CPUTIME_START, birthtimeMs: this.CPUTIME_START, atime: new Date(this.CPUTIME_START), mtime: new Date(this.CPUTIME_START), ctime: new Date(this.CPUTIME_START), birthtime: new Date(this.CPUTIME_START), isFile: !isDirectory, isDirectory, isBlockDevice: false, isCharacterDevice: false, isSymbolicLink: false, isFIFO: false, isSocket: false, }; }; WASI.prototype.pathExists = function (fname /* string */) { const filePath = this.resolvePath(fname); const maybeFd = Object.values(this.fd).find(({ path }) => path === filePath); return !!maybeFd; };