diff --git a/README.md b/README.md index c35bfc8..0489363 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,12 @@ const {instance: { exports }} = await WebAssembly.instantiateStreaming(fetch('ex wasi_snapshot_preview1.start(exports) ``` -To really unlock it's power, though, give it an `fs` instance, like from [zen-fs](https://github.com/zen-fs/core). Here is an example that will mount a zip file to /zip, in-memory storage to /tmp, and IndexedDB to /home. Note that / has the default in-memory backend. +To really unlock it's power, though, give it an `fs` instance, like from [zen-fs](https://github.com/zen-fs/core). Here is an example that will mount a zip file to `/zip`, in-memory storage to `/tmp`, and IndexedDB to `/home`. + +Things to note: + +- `/` has the default in-memory backend. +- `/mnt` is a bit special, and not traversed by file-lists, so if you want that, put it somewhere else ```js import WasiPreview1 from 'easywasi' @@ -30,13 +35,13 @@ import { Zip } from '@zenfs/zip' const res = await fetch('mydata.zip') await configure({ mounts: { - '/mnt/zip': { backend: Zip, data: await res.arrayBuffer() }, + '/zip': { backend: Zip, data: await res.arrayBuffer() }, '/tmp': InMemory, '/home': IndexedDB } }) -// here, you could use fs.writeFileSync any files you want, as well +// here, you could use fs to modify filesystem however you need (write files, make directories, etc) const wasi_snapshot_preview1 = new WasiPreview1({fs}) @@ -50,9 +55,47 @@ wasi_snapshot_preview1.start(exports) Have a look in [example](docs) to see how I fit it all together. +Keep in mind, you can eaasily override every function yourself, too, like if you want to implement the socket-API, which is the only thing I left out: + +```js +import WasiPreview1 from 'easywasi' +import defs from 'easywasi/defs' + +class WasiPreview1WithSockets = { + constructor(options={}) { + super(options) + // do somehting with optiosn to setup socket + } + + // obviously implement these however + sock_accept (fd, flags) { + return defs.ERRNO_NOSYS + } + sock_recv (fd, riData, riFlags) { + return defs.ERRNO_NOSYS + } + sock_send (fd, siData, riFlags) { + return defs.ERRNO_NOSYS + } + sock_shutdown (fd, how) { + return defs.ERRNO_NOSYS + } +} + +// usage +const wasi_snapshot_preview1 = new WasiPreview1WithSockets({fs}) + +const {instance: { exports }} = await WebAssembly.instantiateStreaming(fetch('example.wasm'), { + wasi_snapshot_preview1 +}) +wasi_snapshot_preview1.start(exports) +``` + +Have a look at [WasiPreview1](./docs/easywasi.js) to figure out how to implement it, if you want things to work differently. + ## inspiration - [this article](https://dev.to/ndesmic/building-a-minimal-wasi-polyfill-for-browsers-4nel) has some nice initial ideas - [this article](https://twdev.blog/2023/11/wasm_cpp_04/) has some good WASI imeplentations -- [browser-wasi-shim](https://github.com/bjorn3/browser_wasi_shim) has a very nice interface, and this is basically the same, but using more extensible fs. +- [browser-wasi-shim](https://github.com/bjorn3/browser_wasi_shim) has a very nice interface, and this is basically the same, but using more extensible fs, and improving a few little things. diff --git a/docs/easywasi.js b/docs/easywasi.js index 43e84c9..61d82ce 100644 --- a/docs/easywasi.js +++ b/docs/easywasi.js @@ -1,431 +1,1197 @@ -import * as defs from './defs.js' +import * as defs from "./defs.js"; -class WASIProcExit extends Error {} - -// debug function for stubs: get current function-name -function getFunctionsNameThatCalledThisFunction () { - const e = new Error('dummy') - const stack = e.stack - .split('\n')[2] - .replace(/^\s+at\s+(.+?)\s.+/g, '$1') - return stack +class WASIProcExit extends Error { + constructor(code) { + super(`Exit with code ${code}`); + this.code = code; + } } export class WasiPreview1 { - constructor (options = {}) { - this.args = options.args || [] - this.env = options.env || {} - this.fs = options.fs - - this.fds = [undefined, undefined] - - this.textDecoder = new TextDecoder() - this.textEncoder = new TextEncoder() - - // force wasi function to use this - this.args_get = this.args_get.bind(this) - this.args_sizes_get = this.args_sizes_get.bind(this) - this.environ_get = this.environ_get.bind(this) - this.environ_sizes_get = this.environ_sizes_get.bind(this) - this.clock_res_get = this.clock_res_get.bind(this) - this.clock_time_get = this.clock_time_get.bind(this) - this.fd_advise = this.fd_advise.bind(this) - this.fd_allocate = this.fd_allocate.bind(this) - this.fd_datasync = this.fd_datasync.bind(this) - this.fd_fdstat_set_flags = this.fd_fdstat_set_flags.bind(this) - this.fd_fdstat_set_rights = this.fd_fdstat_set_rights.bind(this) - this.fd_filestat_get = this.fd_filestat_get.bind(this) - this.fd_filestat_set_size = this.fd_filestat_set_size.bind(this) - this.fd_filestat_set_times = this.fd_filestat_set_times.bind(this) - this.fd_pread = this.fd_pread.bind(this) - this.fd_prestat_get = this.fd_prestat_get.bind(this) - this.fd_prestat_dir_name = this.fd_prestat_dir_name.bind(this) - this.fd_pwrite = this.fd_pwrite.bind(this) - this.fd_read = this.fd_read.bind(this) - this.fd_readdir = this.fd_readdir.bind(this) - this.fd_renumber = this.fd_renumber.bind(this) - this.fd_sync = this.fd_sync.bind(this) - this.fd_tell = this.fd_tell.bind(this) - this.fd_close = this.fd_close.bind(this) - this.fd_fdstat_get = this.fd_fdstat_get.bind(this) - this.fd_seek = this.fd_seek.bind(this) - this.path_create_directory = this.path_create_directory.bind(this) - this.path_filestat_get = this.path_filestat_get.bind(this) - this.path_filestat_set_times = this.path_filestat_set_times.bind(this) - this.path_link = this.path_link.bind(this) - this.path_open = this.path_open.bind(this) - this.path_readlink = this.path_readlink.bind(this) - this.path_remove_directory = this.path_remove_directory.bind(this) - this.path_rename = this.path_rename.bind(this) - this.path_symlink = this.path_symlink.bind(this) - this.path_unlink_file = this.path_unlink_file.bind(this) - this.poll_oneoff = this.poll_oneoff.bind(this) - this.proc_exit = this.proc_exit.bind(this) - this.sched_yield = this.sched_yield.bind(this) - this.random_get = this.random_get.bind(this) - this.sock_accept = this.sock_accept.bind(this) - this.sock_recv = this.sock_recv.bind(this) - this.sock_send = this.sock_send.bind(this) - this.sock_shutdown = this.sock_shutdown.bind(this) - this.fd_write = this.fd_write.bind(this) - } - - // start a WASI wams - start (wasm) { - this.setup(wasm) + constructor(options = {}) { + this.args = options.args || []; + this.env = options.env || {}; + this.fs = options.fs; + if (!this.fs) throw new Error("File system implementation required"); + + // Initialize file descriptors with stdin(0), stdout(1), stderr(2), / + // fd is first number + this.fds = new Map([ + [0, { type: "stdio" }], // stdin + [1, { type: "stdio" }], // stdout + [2, { type: "stdio" }], // stderr + [3, { type: "directory", preopenPath: "/" }], // root directory + ]); + + this.nextFd = this.fds.size; + this.textDecoder = new TextDecoder(); + this.textEncoder = new TextEncoder(); + + // Bind all WASI functions to maintain correct 'this' context + this.args_get = this.args_get.bind(this); + this.args_sizes_get = this.args_sizes_get.bind(this); + this.environ_get = this.environ_get.bind(this); + this.environ_sizes_get = this.environ_sizes_get.bind(this); + this.clock_res_get = this.clock_res_get.bind(this); + this.clock_time_get = this.clock_time_get.bind(this); + this.fd_close = this.fd_close.bind(this); + this.fd_seek = this.fd_seek.bind(this); + this.fd_write = this.fd_write.bind(this); + this.fd_read = this.fd_read.bind(this); + this.fd_fdstat_get = this.fd_fdstat_get.bind(this); + this.fd_fdstat_set_flags = this.fd_fdstat_set_flags.bind(this); + this.fd_prestat_get = this.fd_prestat_get.bind(this); + this.fd_prestat_dir_name = this.fd_prestat_dir_name.bind(this); + this.path_open = this.path_open.bind(this); + this.path_filestat_get = this.path_filestat_get.bind(this); + this.proc_exit = this.proc_exit.bind(this); + this.fd_advise = this.fd_advise.bind(this); + this.fd_allocate = this.fd_allocate.bind(this); + this.fd_datasync = this.fd_datasync.bind(this); + this.fd_filestat_set_size = this.fd_filestat_set_size.bind(this); + this.fd_filestat_set_times = this.fd_filestat_set_times.bind(this); + this.fd_pread = this.fd_pread.bind(this); + this.fd_pwrite = this.fd_pwrite.bind(this); + this.fd_readdir = this.fd_readdir.bind(this); + this.fd_renumber = this.fd_renumber.bind(this); + this.fd_sync = this.fd_sync.bind(this); + this.fd_tell = this.fd_tell.bind(this); + this.path_create_directory = this.path_create_directory.bind(this); + this.path_filestat_set_times = this.path_filestat_set_times.bind(this); + this.path_link = this.path_link.bind(this); + this.path_readlink = this.path_readlink.bind(this); + this.path_remove_directory = this.path_remove_directory.bind(this); + this.path_rename = this.path_rename.bind(this); + this.path_symlink = this.path_symlink.bind(this); + this.path_unlink_file = this.path_unlink_file.bind(this); + this.poll_oneoff = this.poll_oneoff.bind(this); + this.sock_accept = this.sock_accept.bind(this); + this.sock_recv = this.sock_recv.bind(this); + this.sock_send = this.sock_send.bind(this); + this.sock_shutdown = this.sock_shutdown.bind(this); + this.random_get = this.random_get.bind(this); + this.sched_yield = this.sched_yield.bind(this); + } + + // Helper methods + + // this binds the wasm to this WASI implementation + setup(wasm) { + this.wasm = wasm; + } + + // this binds the wasm to this WASI implementation + // and calls it's main()' + start(wasm) { + this.setup(wasm); try { if (wasm._start) { - wasm._start() + wasm._start(); } - return 0 + return 0; } catch (e) { if (e instanceof WASIProcExit) { - return e.code - } else { - throw e + return e.code; } + throw e; } } - // just set it up, but don't run it - setup (wasm) { - this.wasm = wasm - this.view = new DataView(wasm.memory.buffer) - this.mem = new Uint8Array(wasm.memory.buffer) + allocateFd(fileHandle, type = "file") { + const fd = this.nextFd++; + const descriptor = { type, handle: fileHandle, fd }; + this.fds.set(fd, descriptor); + return fd; } - // handle stdout messages, override if you wanna do it some other way - stdout (buffer) { - const t = this.textDecoder.decode(buffer).replace(/\n$/g, '') - if (t) { - console.info(t) - } + // Standard output handling (for fdwrite) + stdout(buffer) { + const text = this.textDecoder.decode(buffer).replace(/\n$/g, ""); + if (text) console.log(text); } - // handle stderr messages, override if you wanna do it some other way - stderr (buffer) { - const t = this.textDecoder.decode(buffer).replace(/\n$/g, '') - if (t) { - console.warn(t) - } + // Standard error handling (for fdwrite) + stderr(buffer) { + const text = this.textDecoder.decode(buffer).replace(/\n$/g, ""); + if (text) console.error(text); } - args_get (argvP, argvBufP) { + // Args functions + args_get(argvP, argvBufP) { + const view = new DataView(this.wasm.memory.buffer); + const mem = new Uint8Array(this.wasm.memory.buffer); + for (const arg of this.args) { - this.view.setUint32(argvP, argvBufP, true) - argvP += 4 - const argEncd = new TextEncoder().encode(arg) - this.mem.set(argEncd, argvBufP) - this.view.setUint8(argvBufP + arg.length, 0) - argvBufP += arg.length + 1 + view.setUint32(argvP, argvBufP, true); + argvP += 4; + const encoded = this.textEncoder.encode(arg); + mem.set(encoded, argvBufP); + mem[argvBufP + encoded.length] = 0; // null terminator + argvBufP += encoded.length + 1; } - return defs.ERRNO_SUCCESS + return defs.ERRNO_SUCCESS; } - args_sizes_get (argsP, argsLenP) { - this.view.setUint32(argsP, this.args.length, true) - let argsTotalLen = 0 - for (const arg of this.args) { - argsTotalLen += arg.length + 1 - } - this.view.setUint32(argsLenP, argsTotalLen, true) - return defs.ERRNO_SUCCESS + args_sizes_get(argcPtr, argvBufSizePtr) { + const view = new DataView(this.wasm.memory.buffer); + view.setUint32(argcPtr, this.args.length, true); + const bufSize = this.args.reduce((acc, arg) => acc + arg.length + 1, 0); + view.setUint32(argvBufSizePtr, bufSize, true); + return defs.ERRNO_SUCCESS; } - environ_get (environ, environBuf) { - for (const k of Object.keys(this.env)) { - this.view.setUint32(environ, environBuf, true) - environ += 4 - const e = this.textEncoder.encode(`${k}=${this.env[k]}\0`) - this.mem.set(e, environBuf) - environBuf += e.length + // Environment functions + environ_get(environP, environBufP) { + const view = new DataView(this.wasm.memory.buffer); + const mem = new Uint8Array(this.wasm.memory.buffer); + + for (const [key, value] of Object.entries(this.env)) { + view.setUint32(environP, environBufP, true); + environP += 4; + const entry = `${key}=${value}\0`; + const encoded = this.textEncoder.encode(entry); + mem.set(encoded, environBufP); + environBufP += encoded.length; } - return defs.ERRNO_SUCCESS + return defs.ERRNO_SUCCESS; } - environ_sizes_get (environCount, environSize) { - this.view.setUint32(environCount, Object.keys(this.env).length, true) - let bufSize = 0 - for (const k of Object.keys(this.env)) { - bufSize += `${k}=${this.env[k]}\0`.length + environ_sizes_get(environCountPtr, environBufSizePtr) { + const view = new DataView(this.wasm.memory.buffer); + const count = Object.keys(this.env).length; + view.setUint32(environCountPtr, count, true); + const bufSize = Object.entries(this.env).reduce( + (acc, [k, v]) => acc + k.length + v.length + 2, + 0, + ); + view.setUint32(environBufSizePtr, bufSize, true); + return defs.ERRNO_SUCCESS; + } + + // Clock functions + clock_res_get(id, resPtr) { + const view = new DataView(this.wasm.memory.buffer); + let resolution; + switch (id) { + case defs.CLOCKID_REALTIME: + resolution = 1_000_000n; // 1ms in nanoseconds + break; + case defs.CLOCKID_MONOTONIC: + resolution = 1_000n; // 1μs in nanoseconds + break; + default: + return defs.ERRNO_INVAL; } - this.view.setUint32(environSize, bufSize, true) - return defs.ERRNO_SUCCESS + view.setBigUint64(resPtr, resolution, true); + return defs.ERRNO_SUCCESS; } - clock_res_get (id, resP) { - let resolutionValue + clock_time_get(id, precision, timePtr) { + const view = new DataView(this.wasm.memory.buffer); + let time; switch (id) { - case defs.CLOCKID_MONOTONIC: { - resolutionValue = 5_000n // 5 microseconds - break - } case defs.CLOCKID_REALTIME: { - resolutionValue = 1_000_000n // 1 millisecond? - break + const ms = Date.now(); + time = BigInt(ms) * 1_000_000n; + break; } - default: - return defs.ERRNO_NOSYS - } - this.view.setBigUint64(resP, resolutionValue, true) - return defs.ERRNO_SUCCESS - } - - clock_time_get (id, precision, time) { - if (id === defs.CLOCKID_REALTIME) { - const d = new Date() - this.view.setBigUint64( - time, - BigInt(d.getTime() - (d.getTimezoneOffset() * 60_000)) * 1_000_000n, - true - ) - } else if (id === defs.CLOCKID_MONOTONIC) { - let monotonicTime - try { - monotonicTime = BigInt(Math.round(performance.now())) * 1_000_000n - } catch (e) { - monotonicTime = 0n + case defs.CLOCKID_MONOTONIC: { + const ns = BigInt(Math.round(performance.now() * 1_000_000)); + time = ns; + break; } - this.view.setBigUint64(time, monotonicTime, true) - } else { - this.view.setBigUint64(time, 0n, true) + default: + return defs.ERRNO_INVAL; } - return 0 + view.setBigUint64(timePtr, time, true); + return defs.ERRNO_SUCCESS; } - fd_advise (fd, offset, len, advice) { - return this.fds[fd] !== undefined ? defs.ERRNO_SUCCESS : defs.ERRNO_BADF + fd_close(fd) { + const fileDesc = this.fds.get(fd); + if (!fileDesc) return defs.ERRNO_BADF; + this.fds.delete(fd); + return defs.ERRNO_SUCCESS; } - fd_allocate (fd, offset, len) { - if (this.fds[fd] !== undefined) { - // TODO: allocate file - return defs.ERRNO_SUCCESS - } else { - return defs.ERRNO_BADF + fd_seek(fd, offset, whence, newOffsetPtr) { + const fileDesc = this.fds.get(fd); + if (!fileDesc) return defs.ERRNO_BADF; + if (fileDesc.type === "stdio") return defs.ERRNO_SPIPE; + + try { + const stats = this.fs.statSync(fileDesc.handle.path); + const size = stats.size; + let newPosition; + + switch (whence) { + case defs.WHENCE_SET: + newPosition = offset; + break; + case defs.WHENCE_CUR: + newPosition = fileDesc.handle.position + offset; + break; + case defs.WHENCE_END: + newPosition = size + offset; + break; + default: + return defs.ERRNO_INVAL; + } + + // Update position + fileDesc.handle.position = newPosition; + + const view = new DataView(this.wasm.memory.buffer); + view.setBigUint64(newOffsetPtr, BigInt(newPosition), true); + return defs.ERRNO_SUCCESS; + } catch (e) { + return defs.ERRNO_IO; } } - fd_datasync () { - console.log(getFunctionsNameThatCalledThisFunction(), arguments) - return defs.ERRNO_BADF - } + fd_write(fd, iovs, iovsLen, nwrittenPtr) { + let written = 0; + const chunks = []; + const view = new DataView(this.wasm.memory.buffer); + const mem = new Uint8Array(this.wasm.memory.buffer); - fd_fdstat_set_flags () { - console.log(getFunctionsNameThatCalledThisFunction(), arguments) - return defs.ERRNO_BADF - } + // Gather all the chunks from the vectors + for (let i = 0; i < iovsLen; i++) { + const ptr = iovs + i * 8; + const buf = view.getUint32(ptr, true); + const bufLen = view.getUint32(ptr + 4, true); + chunks.push(new Uint8Array(mem.buffer, buf, bufLen)); + written += bufLen; + } - fd_fdstat_set_rights () { - console.log(getFunctionsNameThatCalledThisFunction(), arguments) - return defs.ERRNO_BADF - } + // Concatenate chunks if needed + let buffer; + if (chunks.length === 1) { + buffer = chunks[0]; + } else { + buffer = new Uint8Array(written); + let offset = 0; + for (const chunk of chunks) { + buffer.set(chunk, offset); + offset += chunk.length; + } + } - fd_filestat_get () { - console.log(getFunctionsNameThatCalledThisFunction(), arguments) - return defs.ERRNO_BADF - } + // Handle standard streams + if (fd === 1) { + this.stdout(buffer); + } else if (fd === 2) { + this.stderr(buffer); + } else { + const fileDesc = this.fds.get(fd); + if (!fileDesc) return defs.ERRNO_BADF; - fd_filestat_set_size () { - console.log(getFunctionsNameThatCalledThisFunction(), arguments) - return defs.ERRNO_BADF - } + try { + // Write using ZenFS path-based API + this.fs.writeFileSync(fileDesc.handle.path, buffer); + } catch (e) { + return defs.ERRNO_IO; + } + } - fd_filestat_set_times () { - console.log(getFunctionsNameThatCalledThisFunction(), arguments) - return defs.ERRNO_BADF + view.setUint32(nwrittenPtr, written, true); + return defs.ERRNO_SUCCESS; } - fd_pread () { - console.log(getFunctionsNameThatCalledThisFunction(), arguments) - return defs.ERRNO_BADF + fd_read(fd, iovs, iovsLen, nreadPtr) { + if (fd === 0) return defs.ERRNO_SPIPE; // stdin not implemented + + const fileDesc = this.fds.get(fd); + if (!fileDesc) return defs.ERRNO_BADF; + + let totalRead = 0; + const view = new DataView(this.wasm.memory.buffer); + const mem = new Uint8Array(this.wasm.memory.buffer); + + try { + const content = this.fs.readFileSync(fileDesc.handle.path); + + for (let i = 0; i < iovsLen; i++) { + const ptr = iovs + i * 8; + const buf = view.getUint32(ptr, true); + const bufLen = view.getUint32(ptr + 4, true); + + const start = fileDesc.handle.position; + const end = Math.min(start + bufLen, content.length); + const bytesToRead = end - start; + + if (bytesToRead <= 0) break; + + mem.set(new Uint8Array(content.slice(start, end)), buf); + totalRead += bytesToRead; + fileDesc.handle.position += bytesToRead; + + if (bytesToRead < bufLen) break; + } + + view.setUint32(nreadPtr, totalRead, true); + return defs.ERRNO_SUCCESS; + } catch (e) { + return defs.ERRNO_IO; + } } - fd_prestat_get (fd, bufP) { - console.log(getFunctionsNameThatCalledThisFunction(), arguments) - return defs.ERRNO_BADF + path_open( + dirfd, + dirflags, + path, + pathLen, + oflags, + fsRightsBase, + fsRightsInheriting, + fdflags, + fdPtr, + ) { + const fileDesc = this.fds.get(dirfd); + if (!fileDesc) return defs.ERRNO_BADF; + + const mem = new Uint8Array(this.wasm.memory.buffer); + const pathBuffer = mem.slice(path, path + pathLen); + const pathString = this.textDecoder.decode(pathBuffer); + + try { + // Resolve path relative to the directory fd + let resolvedPath = pathString; + if (fileDesc.preopenPath) { + if (pathString.startsWith("/")) { + resolvedPath = pathString.slice(1); + } + resolvedPath = + fileDesc.preopenPath + + (fileDesc.preopenPath.endsWith("/") ? "" : "/") + + resolvedPath; + } + + // Verify file exists + const stats = this.fs.statSync(resolvedPath); + + // Store path and initial position in handle + const fd = this.allocateFd({ path: resolvedPath, position: 0 }, "file"); + + const view = new DataView(this.wasm.memory.buffer); + view.setUint32(fdPtr, fd, true); + return defs.ERRNO_SUCCESS; + } catch (e) { + return defs.ERRNO_NOENT; + } } - fd_prestat_dir_name () { - console.log(getFunctionsNameThatCalledThisFunction(), arguments) - return defs.ERRNO_BADF + proc_exit(code) { + throw new WASIProcExit(code); } - fd_pwrite () { - console.log(getFunctionsNameThatCalledThisFunction(), arguments) - return defs.ERRNO_BADF + fd_fdstat_get(fd, statPtr) { + const fileDesc = this.fds.get(fd); + if (!fileDesc) return defs.ERRNO_BADF; + + const view = new DataView(this.wasm.memory.buffer); + + // filetype - u8 + let filetype; + switch (fileDesc.type) { + case "stdio": + filetype = defs.FILETYPE_CHARACTER_DEVICE; + break; + case "directory": + filetype = defs.FILETYPE_DIRECTORY; + break; + case "file": + filetype = defs.FILETYPE_REGULAR_FILE; + break; + default: + filetype = defs.FILETYPE_UNKNOWN; + } + view.setUint8(statPtr, filetype); + + // fdflags - u16 + // For now, we'll assume basic flags + let fdflags = 0; + if (fileDesc.append) fdflags |= defs.FDFLAGS_APPEND; + view.setUint16(statPtr + 2, fdflags, true); + + // fs_rights_base - u64 + // Set basic rights depending on file type + let fsRightsBase = 0n; + if (fileDesc.type === "file") { + fsRightsBase = + defs.RIGHTS_FD_READ | + defs.RIGHTS_FD_WRITE | + defs.RIGHTS_FD_SEEK | + defs.RIGHTS_FD_TELL | + defs.RIGHTS_FD_FILESTAT_GET; + } else if (fileDesc.type === "directory") { + fsRightsBase = + defs.RIGHTS_PATH_OPEN | + defs.RIGHTS_FD_READDIR | + defs.RIGHTS_PATH_CREATE_DIRECTORY | + defs.RIGHTS_PATH_UNLINK_FILE | + defs.RIGHTS_PATH_REMOVE_DIRECTORY; + } + const bf = BigInt(fsRightsBase); + view.setBigUint64(statPtr + 8, bf, true); + + // fs_rights_inheriting - u64 + // Child files/directories inherit the same rights + view.setBigUint64(statPtr + 16, bf, true); + + return defs.ERRNO_SUCCESS; } - fd_read (fd, iovs) { - console.log(getFunctionsNameThatCalledThisFunction(), arguments) - return defs.ERRNO_BADF + fd_fdstat_set_flags(fd, flags) { + const fileDesc = this.fds.get(fd); + if (!fileDesc) return defs.ERRNO_BADF; + + // Check if flags are valid + const validFlags = + defs.FDFLAGS_APPEND | + defs.FDFLAGS_DSYNC | + defs.FDFLAGS_NONBLOCK | + defs.FDFLAGS_RSYNC | + defs.FDFLAGS_SYNC; + + if (flags & ~validFlags) { + return defs.ERRNO_INVAL; // Invalid flags specified + } + + // For stdio handles, we can't set flags + if (fileDesc.type === "stdio") { + return defs.ERRNO_NOTSUP; + } + + try { + // Update internal file descriptor state + fileDesc.append = Boolean(flags & defs.FDFLAGS_APPEND); + + // Try to apply flags to the underlying file system + // Note: Many flags might not be supported by the underlying fs + if (fileDesc.handle && typeof this.fs.setFlagsSync === "function") { + this.fs.setFlagsSync(fileDesc.handle, flags); + } + + return defs.ERRNO_SUCCESS; + } catch (e) { + return defs.ERRNO_IO; + } } - fd_readdir () { - console.log(getFunctionsNameThatCalledThisFunction(), arguments) - return defs.ERRNO_BADF + fd_prestat_get(fd, prestatPtr) { + const fileDesc = this.fds.get(fd); + if (!fileDesc) return defs.ERRNO_BADF; + + // Only directory file descriptors have prestats + if (fileDesc.type !== "directory") { + return defs.ERRNO_BADF; + } + + // Ensure we have a preopened path for this fd + if (!fileDesc.preopenPath) { + return defs.ERRNO_BADF; + } + + const view = new DataView(this.wasm.memory.buffer); + + // Write prestat struct: + // struct prestat { + // u8 type; // offset 0 + // u64 length; // offset 8 (with padding) + // } + + // Set type to PREOPENTYPE_DIR (0) + view.setUint8(prestatPtr, defs.PREOPENTYPE_DIR); + + // Get the length of the preopened directory path + const pathLength = fileDesc.preopenPath.length; + view.setUint32(prestatPtr + 4, pathLength, true); + + return defs.ERRNO_SUCCESS; } - fd_renumber () { - console.log(getFunctionsNameThatCalledThisFunction(), arguments) - return defs.ERRNO_BADF + fd_prestat_dir_name(fd, pathPtr, pathLen) { + const fileDesc = this.fds.get(fd); + if (!fileDesc) return defs.ERRNO_BADF; + + // Only directory file descriptors have prestats + if (fileDesc.type !== "directory") { + return defs.ERRNO_BADF; + } + + // Ensure we have a preopened path for this fd + if (!fileDesc.preopenPath) { + return defs.ERRNO_BADF; + } + + // Check if the provided buffer is large enough + if (pathLen < fileDesc.preopenPath.length) { + return defs.ERRNO_NAMETOOLONG; + } + + // Write the path to memory + const mem = new Uint8Array(this.wasm.memory.buffer); + const pathBytes = this.textEncoder.encode(fileDesc.preopenPath); + mem.set(pathBytes, pathPtr); + + return defs.ERRNO_SUCCESS; } - fd_sync () { - console.log(getFunctionsNameThatCalledThisFunction(), arguments) - return defs.ERRNO_BADF + path_filestat_get(fd, flags, pathPtr, pathLen, filestatPtr) { + const fileDesc = this.fds.get(fd); + if (!fileDesc) return defs.ERRNO_BADF; + + // Read the path from memory + const mem = new Uint8Array(this.wasm.memory.buffer); + const pathBytes = new Uint8Array(mem.buffer, pathPtr, pathLen); + const pathString = this.textDecoder.decode(pathBytes); + + try { + // Resolve path relative to the directory fd + let resolvedPath = pathString; + if (fileDesc.preopenPath) { + // If path starts with '/', make it relative to preopenPath + if (pathString.startsWith("/")) { + resolvedPath = pathString.slice(1); // Remove leading '/' + } + // Combine preopenPath with the relative path + resolvedPath = + fileDesc.preopenPath + + (fileDesc.preopenPath.endsWith("/") ? "" : "/") + + resolvedPath; + } + + // Get stats from filesystem + const stats = this.fs.statSync(resolvedPath, { + followSymlinks: (flags & defs.LOOKUPFLAGS_SYMLINK_FOLLOW) !== 0, + }); + + const view = new DataView(this.wasm.memory.buffer); + + // Write filestat struct: + // struct filestat { + // dev: u64, // Device ID + // ino: u64, // Inode number + // filetype: u8, // File type + // nlink: u64, // Number of hard links + // size: u64, // File size + // atim: u64, // Access time + // mtim: u64, // Modification time + // ctim: u64 // Change time + // } + + // Device ID + view.setBigUint64(filestatPtr, BigInt(stats.dev || 0), true); + + // Inode + view.setBigUint64(filestatPtr + 8, BigInt(stats.ino || 0), true); + + // Filetype + let filetype = defs.FILETYPE_UNKNOWN; + if (stats.isFile()) filetype = defs.FILETYPE_REGULAR_FILE; + else if (stats.isDirectory()) filetype = defs.FILETYPE_DIRECTORY; + else if (stats.isSymbolicLink()) filetype = defs.FILETYPE_SYMBOLIC_LINK; + else if (stats.isCharacterDevice()) + filetype = defs.FILETYPE_CHARACTER_DEVICE; + else if (stats.isBlockDevice()) filetype = defs.FILETYPE_BLOCK_DEVICE; + else if (stats.isFIFO()) filetype = defs.FILETYPE_SOCKET_STREAM; + view.setUint8(filestatPtr + 16, filetype); + + // Number of hard links + view.setBigUint64(filestatPtr + 24, BigInt(stats.nlink || 1), true); + + // File size + view.setBigUint64(filestatPtr + 32, BigInt(stats.size || 0), true); + + // Access time (in nanoseconds) + view.setBigUint64( + filestatPtr + 40, + BigInt(stats.atimeMs * 1_000_000), + true, + ); + + // Modification time (in nanoseconds) + view.setBigUint64( + filestatPtr + 48, + BigInt(stats.mtimeMs * 1_000_000), + true, + ); + + // Change time (in nanoseconds) + view.setBigUint64( + filestatPtr + 56, + BigInt(stats.ctimeMs * 1_000_000), + true, + ); + + return defs.ERRNO_SUCCESS; + } catch (e) { + if (e.code === "ENOENT") return defs.ERRNO_NOENT; + if (e.code === "EACCES") return defs.ERRNO_ACCES; + return defs.ERRNO_IO; + } } - fd_tell () { - console.log(getFunctionsNameThatCalledThisFunction(), arguments) - return defs.ERRNO_BADF + // File/Directory Operations + fd_advise(fd, offset, len, advice) { + const fileDesc = this.fds.get(fd); + if (!fileDesc) return defs.ERRNO_BADF; + if (fileDesc.type !== "file") return defs.ERRNO_BADF; + + // Most filesystems don't actually implement advisory hints, + // so we'll just return success + return defs.ERRNO_SUCCESS; } - fd_close () { - return defs.ERRNO_SUCCESS + fd_allocate(fd, offset, len) { + const fileDesc = this.fds.get(fd); + if (!fileDesc) return defs.ERRNO_BADF; + if (fileDesc.type !== "file") return defs.ERRNO_BADF; + + try { + // Attempt to extend the file to the specified size + const stats = this.fs.statSync(fileDesc.handle.path); + const newSize = Number(offset) + Number(len); + if (newSize > stats.size) { + // Create a buffer of zeros to extend the file + const zeros = new Uint8Array(newSize - stats.size); + this.fs.appendFileSync(fileDesc.handle.path, zeros); + } + return defs.ERRNO_SUCCESS; + } catch (e) { + return defs.ERRNO_IO; + } } - fd_fdstat_get (fd, fdstatP) { - if (fd < 1 || fd > 2) { - throw new Error('Unsupported file descriptor') + fd_datasync(fd) { + const fileDesc = this.fds.get(fd); + if (!fileDesc) return defs.ERRNO_BADF; + if (fileDesc.type !== "file") return defs.ERRNO_BADF; + + try { + // Most JavaScript filesystem implementations handle syncing automatically + // If your fs implementation has a specific sync method, call it here + if (typeof this.fs.fsyncSync === "function") { + this.fs.fsyncSync(fileDesc.handle.path); + } + return defs.ERRNO_SUCCESS; + } catch (e) { + return defs.ERRNO_IO; } - return defs.ERRNO_SUCCESS } - fd_seek (fd, offset, whence, offsetOutPtr) { - return defs.ERRNO_SUCCESS + fd_filestat_set_size(fd, size) { + const fileDesc = this.fds.get(fd); + if (!fileDesc) return defs.ERRNO_BADF; + if (fileDesc.type !== "file") return defs.ERRNO_BADF; + + try { + this.fs.truncateSync(fileDesc.handle.path, Number(size)); + return defs.ERRNO_SUCCESS; + } catch (e) { + return defs.ERRNO_IO; + } } - path_create_directory (fd, pathP, pathLen) { - if (this.fds[fd] !== undefined) { - const path = this.textDecoder.decode( - this.mem.slice(pathP, pathP + pathLen) - ) - // TODO: create directory - console.log(`path_create_directory: ${path}`) - return defs.ERRNO_SUCCESS - } else { - return defs.ERRNO_BADF + fd_filestat_set_times(fd, atim, mtim, fst_flags) { + const fileDesc = this.fds.get(fd); + if (!fileDesc) return defs.ERRNO_BADF; + if (fileDesc.type !== "file") return defs.ERRNO_BADF; + + try { + const times = { + atime: Number(atim) / 1_000_000_000, + mtime: Number(mtim) / 1_000_000_000, + }; + + this.fs.utimesSync(fileDesc.handle.path, times.atime, times.mtime); + return defs.ERRNO_SUCCESS; + } catch (e) { + return defs.ERRNO_IO; } } - path_filestat_get (fd, flags, pathP, pathLen, filestatP) { - if (this.fds[fd] !== undefined) { - const path = this.textDecoder.decode( - this.mem.slice(pathP, pathP + pathLen) - ) - // TODO: get filestate - console.log(`path_filestat_get: ${path}`) - return defs.ERRNO_SUCCESS - } else { - return defs.ERRNO_BADF + fd_pread(fd, iovs, iovsLen, offset, nreadPtr) { + const fileDesc = this.fds.get(fd); + if (!fileDesc) return defs.ERRNO_BADF; + if (fileDesc.type !== "file") return defs.ERRNO_BADF; + + try { + const content = this.fs.readFileSync(fileDesc.handle.path); + let totalRead = 0; + const view = new DataView(this.wasm.memory.buffer); + const mem = new Uint8Array(this.wasm.memory.buffer); + + const position = Number(offset); + + for (let i = 0; i < iovsLen; i++) { + const ptr = iovs + i * 8; + const buf = view.getUint32(ptr, true); + const bufLen = view.getUint32(ptr + 4, true); + + const start = position + totalRead; + const end = Math.min(start + bufLen, content.length); + const bytesToRead = end - start; + + if (bytesToRead <= 0) break; + + mem.set(new Uint8Array(content.slice(start, end)), buf); + totalRead += bytesToRead; + + if (bytesToRead < bufLen) break; + } + + view.setUint32(nreadPtr, totalRead, true); + return defs.ERRNO_SUCCESS; + } catch (e) { + return defs.ERRNO_IO; } } - path_filestat_set_times () { - console.log(getFunctionsNameThatCalledThisFunction(), arguments) - return defs.ERRNO_BADF + fd_pwrite(fd, iovs, iovsLen, offset, nwrittenPtr) { + const fileDesc = this.fds.get(fd); + if (!fileDesc) return defs.ERRNO_BADF; + if (fileDesc.type !== "file") return defs.ERRNO_BADF; + + try { + let written = 0; + const chunks = []; + const view = new DataView(this.wasm.memory.buffer); + const mem = new Uint8Array(this.wasm.memory.buffer); + + for (let i = 0; i < iovsLen; i++) { + const ptr = iovs + i * 8; + const buf = view.getUint32(ptr, true); + const bufLen = view.getUint32(ptr + 4, true); + chunks.push(new Uint8Array(mem.buffer, buf, bufLen)); + written += bufLen; + } + + let buffer; + if (chunks.length === 1) { + buffer = chunks[0]; + } else { + buffer = new Uint8Array(written); + let offset = 0; + for (const chunk of chunks) { + buffer.set(chunk, offset); + offset += chunk.length; + } + } + + // Read existing file content + const content = this.fs.readFileSync(fileDesc.handle.path); + const newContent = new Uint8Array( + Math.max(Number(offset) + buffer.length, content.length), + ); + + // Copy existing content + newContent.set(content); + // Write new data at specified offset + newContent.set(buffer, Number(offset)); + + // Write back to file + this.fs.writeFileSync(fileDesc.handle.path, newContent); + + view.setUint32(nwrittenPtr, written, true); + return defs.ERRNO_SUCCESS; + } catch (e) { + return defs.ERRNO_IO; + } } - path_link () { - console.log(getFunctionsNameThatCalledThisFunction(), arguments) - return defs.ERRNO_BADF + fd_readdir(fd, buf, bufLen, cookie, bufusedPtr) { + const fileDesc = this.fds.get(fd); + if (!fileDesc) return defs.ERRNO_BADF; + if (fileDesc.type !== "directory") return defs.ERRNO_NOTDIR; + + try { + const entries = this.fs.readdirSync(fileDesc.handle.path, { + withFileTypes: true, + }); + const view = new DataView(this.wasm.memory.buffer); + const mem = new Uint8Array(this.wasm.memory.buffer); + + let offset = 0; + let entriesWritten = 0; + + // Skip entries according to cookie + const startIndex = Number(cookie); + + for (let i = startIndex; i < entries.length; i++) { + const entry = entries[i]; + const name = entry.name; + const nameBytes = this.textEncoder.encode(name); + + // dirent structure size: 24 bytes + name length + const direntSize = 24 + nameBytes.length; + + if (offset + direntSize > bufLen) { + break; + } + + // Write dirent structure + view.setBigUint64(buf + offset, BigInt(i + 1), true); // d_next + view.setBigUint64(buf + offset + 8, 0n, true); // d_ino + view.setUint32(buf + offset + 16, nameBytes.length, true); // d_namlen + + // d_type + let filetype = defs.FILETYPE_UNKNOWN; + if (entry.isFile()) filetype = defs.FILETYPE_REGULAR_FILE; + else if (entry.isDirectory()) filetype = defs.FILETYPE_DIRECTORY; + view.setUint8(buf + offset + 20, filetype); + + // Write name + mem.set(nameBytes, buf + offset + 24); + + offset += direntSize; + entriesWritten++; + } + + view.setUint32(bufusedPtr, offset, true); + return defs.ERRNO_SUCCESS; + } catch (e) { + return defs.ERRNO_IO; + } } - path_open () { - console.log(getFunctionsNameThatCalledThisFunction(), arguments) - return defs.ERRNO_BADF + fd_renumber(from, to) { + const fromDesc = this.fds.get(from); + if (!fromDesc) return defs.ERRNO_BADF; + + // Close existing 'to' fd if it exists + this.fds.delete(to); + + // Move the fd + this.fds.set(to, fromDesc); + this.fds.delete(from); + + return defs.ERRNO_SUCCESS; } - path_readlink () { - console.log(getFunctionsNameThatCalledThisFunction(), arguments) - return defs.ERRNO_BADF + fd_sync(fd) { + const fileDesc = this.fds.get(fd); + if (!fileDesc) return defs.ERRNO_BADF; + if (fileDesc.type !== "file") return defs.ERRNO_BADF; + + try { + // Similar to fd_datasync, but might include metadata + if (typeof this.fs.fsyncSync === "function") { + this.fs.fsyncSync(fileDesc.handle.path); + } + return defs.ERRNO_SUCCESS; + } catch (e) { + return defs.ERRNO_IO; + } } - path_remove_directory () { - console.log(getFunctionsNameThatCalledThisFunction(), arguments) - return defs.ERRNO_BADF + fd_tell(fd, offsetPtr) { + const fileDesc = this.fds.get(fd); + if (!fileDesc) return defs.ERRNO_BADF; + if (fileDesc.type !== "file") return defs.ERRNO_BADF; + + const view = new DataView(this.wasm.memory.buffer); + view.setBigUint64(offsetPtr, BigInt(fileDesc.handle.position), true); + return defs.ERRNO_SUCCESS; } - path_rename () { - console.log(getFunctionsNameThatCalledThisFunction(), arguments) - return defs.ERRNO_BADF + // Path Operations + path_create_directory(fd, path, pathLen) { + const fileDesc = this.fds.get(fd); + if (!fileDesc) return defs.ERRNO_BADF; + + const pathString = this.textDecoder.decode( + new Uint8Array(this.wasm.memory.buffer, path, pathLen), + ); + + try { + let resolvedPath = pathString; + if (fileDesc.preopenPath) { + if (pathString.startsWith("/")) { + resolvedPath = pathString.slice(1); + } + resolvedPath = fileDesc.preopenPath + "/" + resolvedPath; + } + + this.fs.mkdirSync(resolvedPath); + return defs.ERRNO_SUCCESS; + } catch (e) { + return defs.ERRNO_IO; + } } - path_symlink () { - console.log(getFunctionsNameThatCalledThisFunction(), arguments) - return defs.ERRNO_BADF + path_filestat_set_times(fd, flags, path, pathLen, atim, mtim, fst_flags) { + const fileDesc = this.fds.get(fd); + if (!fileDesc) return defs.ERRNO_BADF; + + const pathString = this.textDecoder.decode( + new Uint8Array(this.wasm.memory.buffer, path, pathLen), + ); + + try { + let resolvedPath = pathString; + if (fileDesc.preopenPath) { + if (pathString.startsWith("/")) { + resolvedPath = pathString.slice(1); + } + resolvedPath = fileDesc.preopenPath + "/" + resolvedPath; + } + + const times = { + atime: Number(atim) / 1_000_000_000, + mtime: Number(mtim) / 1_000_000_000, + }; + + this.fs.utimesSync(resolvedPath, times.atime, times.mtime); + return defs.ERRNO_SUCCESS; + } catch (e) { + return defs.ERRNO_IO; + } } - path_unlink_file () { - console.log(getFunctionsNameThatCalledThisFunction(), arguments) - return defs.ERRNO_BADF + path_link( + old_fd, + old_flags, + old_path, + old_path_len, + new_fd, + new_path, + new_path_len, + ) { + const oldFileDesc = this.fds.get(old_fd); + const newFileDesc = this.fds.get(new_fd); + if (!oldFileDesc || !newFileDesc) return defs.ERRNO_BADF; + + const oldPathString = this.textDecoder.decode( + new Uint8Array(this.wasm.memory.buffer, old_path, old_path_len), + ); + const newPathString = this.textDecoder.decode( + new Uint8Array(this.wasm.memory.buffer, new_path, new_path_len), + ); + + try { + let resolvedOldPath = oldPathString; + let resolvedNewPath = newPathString; + + if (oldFileDesc.preopenPath) { + if (oldPathString.startsWith("/")) { + resolvedOldPath = oldPathString.slice(1); + } + resolvedOldPath = oldFileDesc.preopenPath + "/" + resolvedOldPath; + } + + if (newFileDesc.preopenPath) { + if (newPathString.startsWith("/")) { + resolvedNewPath = newPathString.slice(1); + } + resolvedNewPath = newFileDesc.preopenPath + "/" + resolvedNewPath; + } + + this.fs.linkSync(resolvedOldPath, resolvedNewPath); + return defs.ERRNO_SUCCESS; + } catch (e) { + return defs.ERRNO_IO; + } } - poll_oneoff (in_, out, nsubscriptions) { - // throw new Error('async io not supported') - return defs.ERRNO_NOSYS + path_readlink(fd, path, path_len, buf, buf_len, bufused) { + const fileDesc = this.fds.get(fd); + if (!fileDesc) return defs.ERRNO_BADF; + + const pathString = this.textDecoder.decode( + new Uint8Array(this.wasm.memory.buffer, path, path_len), + ); + + try { + let resolvedPath = pathString; + if (fileDesc.preopenPath) { + if (pathString.startsWith("/")) { + resolvedPath = pathString.slice(1); + } + resolvedPath = fileDesc.preopenPath + "/" + resolvedPath; + } + + const linkString = this.fs.readlinkSync(resolvedPath); + const linkBytes = this.textEncoder.encode(linkString); + + if (linkBytes.length > buf_len) { + return defs.ERRNO_OVERFLOW; + } + + const mem = new Uint8Array(this.wasm.memory.buffer); + mem.set(linkBytes, buf); + + const view = new DataView(this.wasm.memory.buffer); + view.setUint32(bufused, linkBytes.length, true); + + return defs.ERRNO_SUCCESS; + } catch (e) { + return defs.ERRNO_IO; + } } - proc_exit (exitCode) { - const e = new WASIProcExit(`Exit status: ${exitCode}`) - e.code = exitCode - throw e + path_remove_directory(fd, path, path_len) { + const fileDesc = this.fds.get(fd); + if (!fileDesc) return defs.ERRNO_BADF; + + const pathString = this.textDecoder.decode( + new Uint8Array(this.wasm.memory.buffer, path, path_len), + ); + + try { + let resolvedPath = pathString; + if (fileDesc.preopenPath) { + if (pathString.startsWith("/")) { + resolvedPath = pathString.slice(1); + } + resolvedPath = fileDesc.preopenPath + "/" + resolvedPath; + } + + this.fs.rmdirSync(resolvedPath); + return defs.ERRNO_SUCCESS; + } catch (e) { + return defs.ERRNO_IO; + } } - sched_yield () {} + path_rename(old_fd, old_path, old_path_len, new_fd, new_path, new_path_len) { + const oldFileDesc = this.fds.get(old_fd); + const newFileDesc = this.fds.get(new_fd); + if (!oldFileDesc || !newFileDesc) return defs.ERRNO_BADF; - random_get (buf, bufLen) { - const buffer8 = this.mem.subarray(buf, buf + bufLen) - for (let i = 0; i < bufLen; i++) { - buffer8[i] = (Math.random() * 256) | 0 + const oldPathString = this.textDecoder.decode( + new Uint8Array(this.wasm.memory.buffer, old_path, old_path_len), + ); + const newPathString = this.textDecoder.decode( + new Uint8Array(this.wasm.memory.buffer, new_path, new_path_len), + ); + + try { + let resolvedOldPath = oldPathString; + let resolvedNewPath = newPathString; + + if (oldFileDesc.preopenPath) { + if (oldPathString.startsWith("/")) { + resolvedOldPath = oldPathString.slice(1); + } + resolvedOldPath = oldFileDesc.preopenPath + "/" + resolvedOldPath; + } + + if (newFileDesc.preopenPath) { + if (newPathString.startsWith("/")) { + resolvedNewPath = newPathString.slice(1); + } + resolvedNewPath = newFileDesc.preopenPath + "/" + resolvedNewPath; + } + + this.fs.renameSync(resolvedOldPath, resolvedNewPath); + return defs.ERRNO_SUCCESS; + } catch (e) { + return defs.ERRNO_IO; } } - sock_accept (fd, flags) { - // throw new Error('Network sockets not supported') - return defs.ERRNO_NOSYS + path_symlink(old_path, old_path_len, fd, new_path, new_path_len) { + const fileDesc = this.fds.get(fd); + if (!fileDesc) return defs.ERRNO_BADF; + + const oldPathString = this.textDecoder.decode( + new Uint8Array(this.wasm.memory.buffer, old_path, old_path_len), + ); + const newPathString = this.textDecoder.decode( + new Uint8Array(this.wasm.memory.buffer, new_path, new_path_len), + ); + + try { + let resolvedNewPath = newPathString; + if (fileDesc.preopenPath) { + if (newPathString.startsWith("/")) { + resolvedNewPath = newPathString.slice(1); + } + resolvedNewPath = fileDesc.preopenPath + "/" + resolvedNewPath; + } + + this.fs.symlinkSync(oldPathString, resolvedNewPath); + return defs.ERRNO_SUCCESS; + } catch (e) { + return defs.ERRNO_IO; + } } - sock_recv (fd, riData, riFlags) { - // throw new Error('Network sockets not supported') - return defs.ERRNO_NOSYS + path_unlink_file(fd, path, path_len) { + const fileDesc = this.fds.get(fd); + if (!fileDesc) return defs.ERRNO_BADF; + + const pathString = this.textDecoder.decode( + new Uint8Array(this.wasm.memory.buffer, path, path_len), + ); + + try { + let resolvedPath = pathString; + if (fileDesc.preopenPath) { + if (pathString.startsWith("/")) { + resolvedPath = pathString.slice(1); + } + resolvedPath = fileDesc.preopenPath + "/" + resolvedPath; + } + + this.fs.unlinkSync(resolvedPath); + return defs.ERRNO_SUCCESS; + } catch (e) { + return defs.ERRNO_IO; + } } - sock_send (fd, siData, riFlags) { - // throw new Error('Network sockets not supported') - return defs.ERRNO_NOSYS + // Poll Operations + poll_oneoff(in_, out, nsubscriptions, nevents) { + // Basic implementation that just processes all subscriptions immediately + const view = new DataView(this.wasm.memory.buffer); + let numEvents = 0; + + for (let i = 0; i < nsubscriptions; i++) { + const subPtr = in_ + i * 48; // size of subscription struct + const userdata = view.getBigUint64(subPtr, true); + const type = view.getUint8(subPtr + 8); + + // Write event + const eventPtr = out + numEvents * 32; // size of event struct + view.setBigUint64(eventPtr, userdata, true); + view.setUint8(eventPtr + 8, type); + view.setUint8(eventPtr + 9, defs.EVENTRWFLAGS_FD_READWRITE_HANGUP); + view.setUint16(eventPtr + 10, 0, true); // error + + numEvents++; + } + + view.setUint32(nevents, numEvents, true); + return defs.ERRNO_SUCCESS; } - sock_shutdown (fd, how) { - // throw new Error('Network sockets not supported') - return defs.ERRNO_NOSYS + // Random Number Generation + random_get(buf, buf_len) { + const bytes = new Uint8Array(this.wasm.memory.buffer, buf, buf_len); + crypto.getRandomValues(bytes); + return defs.ERRNO_SUCCESS; } - fd_write (fd, iovsPtr, iovsLength, bytesWrittenPtr) { - const iovs = new Uint32Array( - this.wasm.memory.buffer, - iovsPtr, - iovsLength * 2 - ) - // TODO: which is faster? make a Uint8Array out of an array (like I do here) or merge multiple ArrayBuffers, or soemthing else? - const out = [] - for (let i = 0; i < iovsLength * 2; i += 2) { - const offsetWasm = iovs[i] - const length = iovs[i + 1] - out.push(...this.mem.slice(offsetWasm, offsetWasm + length)) - } - const bytes = new Uint8Array(out) - this.view.setInt32(bytesWrittenPtr, bytes.byteLength, true) + // Scheduling Operations + sched_yield() { + // Can't really yield in JavaScript, just return success + return defs.ERRNO_SUCCESS; + } - if (fd === 1) { - this.stdout(bytes) - } else if (fd === 2) { - this.stderr(bytes) - } else { - console.log('fd_write:', fd) - } - return defs.ERRNO_SUCCESS + // STUB + sock_accept(fd, flags) { + return defs.ERRNO_NOSYS; + } + sock_recv(fd, riData, riFlags) { + return defs.ERRNO_NOSYS; + } + sock_send(fd, siData, riFlags) { + return defs.ERRNO_NOSYS; + } + sock_shutdown(fd, how) { + return defs.ERRNO_NOSYS; } } -export default WasiPreview1 diff --git a/docs/index.html b/docs/index.html index 6e7f836..069ab69 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,57 +1,63 @@
-