From 7fa93ee7b50fb83c4df5c9032d367477afffa0d7 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Sat, 23 Dec 2023 15:34:57 +0100 Subject: [PATCH 01/19] Use Map instead of Object in Directory This avoids issues when the wasm module uses names conflicting with builtin javascript properties. --- examples/rustc.html | 44 +++++++++++------------ src/fs_core.ts | 56 +++++++++++++++++------------ test/adapters/browser/run-test.html | 8 ++--- test/adapters/browser/run-wasi.mjs | 6 ++-- test/adapters/node/run-wasi.mjs | 5 +-- test/adapters/shared/walkFs.mjs | 6 ++-- 6 files changed, 69 insertions(+), 56 deletions(-) diff --git a/examples/rustc.html b/examples/rustc.html index e760820..b6cfe1f 100644 --- a/examples/rustc.html +++ b/examples/rustc.html @@ -75,16 +75,16 @@ new XtermStdio(term), new XtermStdio(term), new XtermStdio(term), - new PreopenDirectory("/tmp", {}), - new PreopenDirectory("/sysroot", { - "lib": new Directory({ - "rustlib": new Directory({ - "wasm32-wasi": new Directory({ - "lib": new Directory({}), - }), - "x86_64-unknown-linux-gnu": new Directory({ - "lib": new Directory(await (async function () { - let dir = {}; + new PreopenDirectory("/tmp", []), + new PreopenDirectory("/sysroot", [ + ["lib", new Directory([ + ["rustlib", new Directory([ + ["wasm32-wasi", new Directory([ + ["lib", new Directory([])], + ])], + ["x86_64-unknown-linux-gnu", new Directory([ + ["lib", new Directory(await (async function () { + let dir = new Map(); for (let file of [ "libaddr2line-3368a2ecf632bfc6.rlib", "libadler-16845f650eeea12c.rlib", @@ -115,17 +115,17 @@ "libunicode_width-d55ce9c674fbd422.rlib", "libunwind-8ca3e01a84805f9e.rlib" ]) { - dir[file] = await load_external_file("/examples/wasm-rustc/lib/rustlib/x86_64-unknown-linux-gnu/lib/" + file); + dir.set(file, await load_external_file("/examples/wasm-rustc/lib/rustlib/x86_64-unknown-linux-gnu/lib/" + file)); } return dir; - })()), - }), - }), - }), - }), - new PreopenDirectory("/", { - "hello.rs": new File(new TextEncoder("utf-8").encode(`fn main() { println!("Hello World!"); }`)), - }), + })())], + ])], + ])], + ])], + ]), + new PreopenDirectory("/", [ + ["hello.rs", new File(new TextEncoder("utf-8").encode(`fn main() { println!("Hello World!"); }`))], + ]), ]; let w = new WASI(args, env, fds, { debug: true }); @@ -140,9 +140,9 @@ console.log(fds); console.log(fds[5].dir); - console.log(fds[5].dir.contents["hello.hello.2490b9cce2492134-cgu.0.rcgu.o"].data); - document.querySelector("#downloads").innerHTML += "
Download object"; - document.querySelector("#downloads").innerHTML += "
Download allocator shim"; + console.log(fds[5].dir.contents.get("hello.hello.2490b9cce2492134-cgu.0.rcgu.o").data); + document.querySelector("#downloads").innerHTML += "
Download object"; + document.querySelector("#downloads").innerHTML += "
Download allocator shim"; })(); diff --git a/src/fs_core.ts b/src/fs_core.ts index ad0f17d..23cff7c 100644 --- a/src/fs_core.ts +++ b/src/fs_core.ts @@ -299,15 +299,15 @@ export class OpenDirectory extends Fd { } { if (debug.enabled) { debug.log("readdir_single", cookie); - debug.log(cookie, Object.keys(this.dir.contents)); + debug.log(cookie, this.dir.contents.keys()); } - debug.log(cookie, Object.keys(this.dir.contents).slice(Number(cookie))); - if (cookie >= BigInt(Object.keys(this.dir.contents).length)) { + if (cookie >= BigInt(this.dir.contents.size)) { return { ret: 0, dirent: null }; } - const name = Object.keys(this.dir.contents)[Number(cookie)]; - const entry = this.dir.contents[name]; + const [name, entry] = Array.from(this.dir.contents.entries())[ + Number(cookie) + ]; return { ret: 0, @@ -399,7 +399,7 @@ export class OpenDirectory extends Fd { if (entry.stat().filetype === wasi.FILETYPE_DIRECTORY) { return wasi.ERRNO_ISDIR; } - delete parentDirEntry.contents[fileName]; + parentDirEntry.contents.delete(fileName); return wasi.ERRNO_SUCCESS; } @@ -420,13 +420,12 @@ export class OpenDirectory extends Fd { ) { return wasi.ERRNO_NOTDIR; } - if (Object.keys(entry.contents).length !== 0) { + if (entry.contents.size !== 0) { return wasi.ERRNO_NOTEMPTY; } - if (parentDirEntry.contents[fileName] === undefined) { + if (!parentDirEntry.contents.delete(fileName)) { return wasi.ERRNO_NOENT; } - delete parentDirEntry.contents[fileName]; return wasi.ERRNO_SUCCESS; } @@ -443,7 +442,7 @@ export class PreopenDirectory extends OpenDirectory { constructor( name: string, - contents: { [key: string]: File | Directory | SyncOPFSFile }, + contents: Map, ) { super(new Directory(contents)); this.prestat_name = new TextEncoder().encode(name); @@ -550,11 +549,19 @@ export class SyncOPFSFile { } export class Directory { - contents: { [key: string]: File | Directory | SyncOPFSFile }; + contents: Map; readonly = false; // FIXME implement, like marking all files within readonly? - constructor(contents: { [key: string]: File | Directory | SyncOPFSFile }) { - this.contents = contents; + constructor( + contents: + | Map + | [string, File | Directory | SyncOPFSFile][], + ) { + if (contents instanceof Array) { + this.contents = new Map(contents); + } else { + this.contents = contents; + } } // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -574,8 +581,9 @@ export class Directory { if (!(entry instanceof Directory)) { return null; } - if (entry.contents[component] != undefined) { - entry = entry.contents[component]; + const child = entry.contents.get(component); + if (child !== undefined) { + entry = child; } else { debug.log(component); return null; @@ -595,12 +603,13 @@ export class Directory { debug.log(entry); return null; } - if (entry.contents[component] === undefined) { + const child = entry.contents.get(component); + if (child === undefined) { debug.log(component); return null; } parentEntry = entry; - entry = entry.contents[component]; + entry = child; } return parentEntry; } @@ -619,16 +628,19 @@ export class Directory { if (!(entry instanceof Directory)) { break; } - if (entry.contents[component] != undefined) { - entry = entry.contents[component]; + const child = entry.contents.get(component); + if (child !== undefined) { + entry = child; } else { debug.log("create", component); + let new_child; if (i == components.length - 1 && !is_dir) { - entry.contents[component] = new File(new ArrayBuffer(0)); + new_child = new File(new ArrayBuffer(0)); } else { - entry.contents[component] = new Directory({}); + new_child = new Directory(new Map()); } - entry = entry.contents[component]; + entry.contents.set(component, new_child); + entry = new_child; } } diff --git a/test/adapters/browser/run-test.html b/test/adapters/browser/run-test.html index 1c87557..34e36da 100644 --- a/test/adapters/browser/run-test.html +++ b/test/adapters/browser/run-test.html @@ -6,9 +6,9 @@ const rawPreopens = await window.bindingDerivePreopens(dirs) function transform(entry) { if (entry.kind === "dir") { - const contents = {}; + const contents = new Map(); for (const [name, child] of Object.entries(entry.contents)) { - contents[name] = transform(child); + contents.set(name, transform(child)); } return new Directory(contents); } else if (entry.kind === "file") { @@ -20,9 +20,9 @@ const preopens = [] for (const preopen of rawPreopens) { const { dir, contents } = preopen; - const newContents = {}; + const newContents = new Map(); for (const [name, child] of Object.entries(contents)) { - newContents[name] = transform(child); + newContents.set(name, transform(child)); } preopens.push(new PreopenDirectory(dir, newContents)); } diff --git a/test/adapters/browser/run-wasi.mjs b/test/adapters/browser/run-wasi.mjs index e35845b..b66cb92 100644 --- a/test/adapters/browser/run-wasi.mjs +++ b/test/adapters/browser/run-wasi.mjs @@ -15,7 +15,7 @@ async function derivePreopens(dirs) { entry.buffer = Array.from(entry.buffer); } return { ...out, [name]: entry }; - }, {}); + }, () => {}); preopens.push({ dir, contents }); } return preopens; @@ -24,8 +24,8 @@ async function derivePreopens(dirs) { /** * Configure routes for the browser harness. * - * @param {import('playwright').BrowserContext} context - * @param {string} harnessURL + * @param {import('playwright').BrowserContext} context + * @param {string} harnessURL */ async function configureRoutes(context, harnessURL) { diff --git a/test/adapters/node/run-wasi.mjs b/test/adapters/node/run-wasi.mjs index ab9fd3f..ac31367 100644 --- a/test/adapters/node/run-wasi.mjs +++ b/test/adapters/node/run-wasi.mjs @@ -50,8 +50,9 @@ async function derivePreopens(dirs) { default: throw new Error(`Unexpected entry kind: ${entry.kind}`); } - return { ...out, [name]: entry} - }, {}) + out.set(name, entry); + return out; + }, () => new Map()) const preopen = new PreopenDirectory(dir, contents); preopens.push(preopen); } diff --git a/test/adapters/shared/walkFs.mjs b/test/adapters/shared/walkFs.mjs index 16fc548..8d625e0 100644 --- a/test/adapters/shared/walkFs.mjs +++ b/test/adapters/shared/walkFs.mjs @@ -6,12 +6,12 @@ import path from 'path'; * using the given reducer function. * * @typedef {{ kind: "dir", contents: any } | { kind: "file", buffer: Buffer }} Entry - * @param {string} dir + * @param {string} dir * @param {(name: string, entry: Entry, out: any) => any} nextPartialResult - * @param {any} initial + * @param {() => any} initial */ export async function walkFs(dir, nextPartialResult, initial) { - let result = { ...initial } + let result = initial(); const srcContents = await fs.readdir(dir, { withFileTypes: true }); for (let entry of srcContents) { const entryPath = path.join(dir, entry.name); From 69ed8c7a663cad6a3bc81f9b90d356900f8c2a21 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Sat, 23 Dec 2023 16:39:03 +0100 Subject: [PATCH 02/19] Add Inode class --- src/fs_core.ts | 52 ++++++++++++++++++++++---------------------------- src/index.ts | 1 + src/inode.ts | 15 +++++++++++++++ 3 files changed, 39 insertions(+), 29 deletions(-) create mode 100644 src/inode.ts diff --git a/src/fs_core.ts b/src/fs_core.ts index 23cff7c..d283b9c 100644 --- a/src/fs_core.ts +++ b/src/fs_core.ts @@ -1,6 +1,7 @@ import { debug } from "./debug.js"; import * as wasi from "./wasi_defs.js"; import { Fd } from "./fd.js"; +import { Inode } from "./inode.js"; export class OpenFile extends Fd { file: File; @@ -373,7 +374,7 @@ export class OpenDirectory extends Fd { const ret = entry.truncate(); if (ret != wasi.ERRNO_SUCCESS) return { ret, fd_obj: null }; } - return { ret: wasi.ERRNO_SUCCESS, fd_obj: entry.open(fd_flags) }; + return entry.path_open(fd_flags); } path_create_directory(path: string): number { @@ -440,10 +441,7 @@ export class OpenDirectory extends Fd { export class PreopenDirectory extends OpenDirectory { prestat_name: Uint8Array; - constructor( - name: string, - contents: Map, - ) { + constructor(name: string, contents: Map) { super(new Directory(contents)); this.prestat_name = new TextEncoder().encode(name); } @@ -468,7 +466,7 @@ type FileOptions = Partial<{ readonly: boolean; }>; -export class File { +export class File extends Inode { data: Uint8Array; readonly: boolean; @@ -476,14 +474,15 @@ export class File { data: ArrayBuffer | SharedArrayBuffer | Uint8Array | Array, options?: FileOptions, ) { + super(); this.data = new Uint8Array(data); this.readonly = !!options?.readonly; } - open(fd_flags: number) { + path_open(fd_flags: number) { const file = new OpenFile(this); if (fd_flags & wasi.FDFLAGS_APPEND) file.fd_seek(0n, wasi.WHENCE_END); - return file; + return { ret: wasi.ERRNO_SUCCESS, fd_obj: file }; } get size(): bigint { @@ -517,20 +516,21 @@ export interface FileSystemSyncAccessHandle { // Synchronous access to an individual file in the origin private file system. // Only allowed inside a WebWorker. -export class SyncOPFSFile { +export class SyncOPFSFile extends Inode { handle: FileSystemSyncAccessHandle; readonly: boolean; // FIXME needs a close() method to be called after start() to release the underlying handle constructor(handle: FileSystemSyncAccessHandle, options?: FileOptions) { + super(); this.handle = handle; this.readonly = !!options?.readonly; } - open(fd_flags: number) { + path_open(fd_flags: number) { const file = new OpenSyncOPFSFile(this); if (fd_flags & wasi.FDFLAGS_APPEND) file.fd_seek(0n, wasi.WHENCE_END); - return file; + return { ret: wasi.ERRNO_SUCCESS, fd_obj: file }; } get size(): bigint { @@ -548,15 +548,12 @@ export class SyncOPFSFile { } } -export class Directory { - contents: Map; +export class Directory extends Inode { + contents: Map; readonly = false; // FIXME implement, like marking all files within readonly? - constructor( - contents: - | Map - | [string, File | Directory | SyncOPFSFile][], - ) { + constructor(contents: Map | [string, Inode][]) { + super(); if (contents instanceof Array) { this.contents = new Map(contents); } else { @@ -565,16 +562,16 @@ export class Directory { } // eslint-disable-next-line @typescript-eslint/no-unused-vars - open(fd_flags: number) { - return new OpenDirectory(this); + path_open(fd_flags: number) { + return { ret: wasi.ERRNO_SUCCESS, fd_obj: new OpenDirectory(this) }; } stat(): wasi.Filestat { return new wasi.Filestat(wasi.FILETYPE_DIRECTORY, 0n); } - get_entry_for_path(path: string): File | Directory | SyncOPFSFile | null { - let entry: File | Directory | SyncOPFSFile = this; + get_entry_for_path(path: string): Inode | null { + let entry: Inode = this; for (const component of path.split("/")) { if (component == "") break; if (component == ".") continue; @@ -594,8 +591,8 @@ export class Directory { get_parent_dir_for_path(path: string): Directory | null { if (path === "") return null; - let entry: File | Directory | SyncOPFSFile = this; - let parentEntry: File | Directory | SyncOPFSFile = entry; + let entry: Inode = this; + let parentEntry: Directory = this; for (const component of path.split("/")) { if (component === "") break; if (component === ".") continue; @@ -614,11 +611,8 @@ export class Directory { return parentEntry; } - create_entry_for_path( - path: string, - is_dir: boolean, - ): File | Directory | SyncOPFSFile { - let entry: File | Directory | SyncOPFSFile = this; + create_entry_for_path(path: string, is_dir: boolean): Inode { + let entry: Inode = this; const components: Array = path .split("/") diff --git a/src/index.ts b/src/index.ts index bd4236f..b69ef7f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ import WASI, { WASIProcExit } from "./wasi.js"; export { WASI, WASIProcExit }; export { Fd } from "./fd.js"; +export { Inode } from "./inode.js"; export { File, SyncOPFSFile, diff --git a/src/inode.ts b/src/inode.ts new file mode 100644 index 0000000..3ea801c --- /dev/null +++ b/src/inode.ts @@ -0,0 +1,15 @@ +/* eslint @typescript-eslint/no-unused-vars:0 */ +import * as wasi from "./wasi_defs.js"; +import { Fd } from "./fd.js"; + +export abstract class Inode { + abstract readonly: boolean; + + abstract path_open(fd_flags: number): { ret: number; fd_obj: Fd | null }; + + abstract stat(): wasi.Filestat; + + truncate(): number { + return wasi.ERRNO_NOTSUP; + } +} From 1424b5b9e0bb5173c8ac948379a42baf22c82145 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Sat, 23 Dec 2023 17:19:05 +0100 Subject: [PATCH 03/19] Move part of OpenDirectory.open_path into the Inode instances --- src/fs_core.ts | 65 +++++++++++++++++++++++++------------------------- src/inode.ts | 12 ++++------ 2 files changed, 37 insertions(+), 40 deletions(-) diff --git a/src/fs_core.ts b/src/fs_core.ts index d283b9c..7a01c34 100644 --- a/src/fs_core.ts +++ b/src/fs_core.ts @@ -358,23 +358,7 @@ export class OpenDirectory extends Fd { // expected a directory but the file is not a directory return { ret: wasi.ERRNO_NOTDIR, fd_obj: null }; } - if ( - entry.readonly && - (fs_rights_base & BigInt(wasi.RIGHTS_FD_WRITE)) == - BigInt(wasi.RIGHTS_FD_WRITE) - ) { - // no write permission to file - return { ret: wasi.ERRNO_PERM, fd_obj: null }; - } - if ( - !(entry instanceof Directory) && - (oflags & wasi.OFLAGS_TRUNC) == wasi.OFLAGS_TRUNC - ) { - // truncate existing file first - const ret = entry.truncate(); - if (ret != wasi.ERRNO_SUCCESS) return { ret, fd_obj: null }; - } - return entry.path_open(fd_flags); + return entry.path_open(oflags, fs_rights_base, fd_flags); } path_create_directory(path: string): number { @@ -479,7 +463,21 @@ export class File extends Inode { this.readonly = !!options?.readonly; } - path_open(fd_flags: number) { + path_open(oflags: number, fs_rights_base: bigint, fd_flags: number) { + if ( + this.readonly && + (fs_rights_base & BigInt(wasi.RIGHTS_FD_WRITE)) == + BigInt(wasi.RIGHTS_FD_WRITE) + ) { + // no write permission to file + return { ret: wasi.ERRNO_PERM, fd_obj: null }; + } + + if ((oflags & wasi.OFLAGS_TRUNC) == wasi.OFLAGS_TRUNC) { + if (this.readonly) return { ret: wasi.ERRNO_PERM, fd_obj: null }; + this.data = new Uint8Array([]); + } + const file = new OpenFile(this); if (fd_flags & wasi.FDFLAGS_APPEND) file.fd_seek(0n, wasi.WHENCE_END); return { ret: wasi.ERRNO_SUCCESS, fd_obj: file }; @@ -492,12 +490,6 @@ export class File extends Inode { stat(): wasi.Filestat { return new wasi.Filestat(wasi.FILETYPE_REGULAR_FILE, this.size); } - - truncate(): number { - if (this.readonly) return wasi.ERRNO_PERM; - this.data = new Uint8Array([]); - return wasi.ERRNO_SUCCESS; - } } // Shim for https://developer.mozilla.org/en-US/docs/Web/API/FileSystemSyncAccessHandle @@ -527,7 +519,21 @@ export class SyncOPFSFile extends Inode { this.readonly = !!options?.readonly; } - path_open(fd_flags: number) { + path_open(oflags: number, fs_rights_base: bigint, fd_flags: number) { + if ( + this.readonly && + (fs_rights_base & BigInt(wasi.RIGHTS_FD_WRITE)) == + BigInt(wasi.RIGHTS_FD_WRITE) + ) { + // no write permission to file + return { ret: wasi.ERRNO_PERM, fd_obj: null }; + } + + if ((oflags & wasi.OFLAGS_TRUNC) == wasi.OFLAGS_TRUNC) { + if (this.readonly) return { ret: wasi.ERRNO_PERM, fd_obj: null }; + this.handle.truncate(0); + } + const file = new OpenSyncOPFSFile(this); if (fd_flags & wasi.FDFLAGS_APPEND) file.fd_seek(0n, wasi.WHENCE_END); return { ret: wasi.ERRNO_SUCCESS, fd_obj: file }; @@ -540,17 +546,10 @@ export class SyncOPFSFile extends Inode { stat(): wasi.Filestat { return new wasi.Filestat(wasi.FILETYPE_REGULAR_FILE, this.size); } - - truncate(): number { - if (this.readonly) return wasi.ERRNO_PERM; - this.handle.truncate(0); - return wasi.ERRNO_SUCCESS; - } } export class Directory extends Inode { contents: Map; - readonly = false; // FIXME implement, like marking all files within readonly? constructor(contents: Map | [string, Inode][]) { super(); @@ -562,7 +561,7 @@ export class Directory extends Inode { } // eslint-disable-next-line @typescript-eslint/no-unused-vars - path_open(fd_flags: number) { + path_open(oflags: number, fs_rights_base: bigint, fd_flags: number) { return { ret: wasi.ERRNO_SUCCESS, fd_obj: new OpenDirectory(this) }; } diff --git a/src/inode.ts b/src/inode.ts index 3ea801c..af04ff2 100644 --- a/src/inode.ts +++ b/src/inode.ts @@ -3,13 +3,11 @@ import * as wasi from "./wasi_defs.js"; import { Fd } from "./fd.js"; export abstract class Inode { - abstract readonly: boolean; - - abstract path_open(fd_flags: number): { ret: number; fd_obj: Fd | null }; + abstract path_open( + oflags: number, + fs_rights_base: bigint, + fd_flags: number, + ): { ret: number; fd_obj: Fd | null }; abstract stat(): wasi.Filestat; - - truncate(): number { - return wasi.ERRNO_NOTSUP; - } } From 0f6f185e16f69d04df4d69fb86ae2942bf370ea5 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Sat, 23 Dec 2023 19:59:36 +0100 Subject: [PATCH 04/19] Introduce new Path class for parsing of paths --- src/fs_core.ts | 260 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 177 insertions(+), 83 deletions(-) diff --git a/src/fs_core.ts b/src/fs_core.ts index 7a01c34..b202146 100644 --- a/src/fs_core.ts +++ b/src/fs_core.ts @@ -318,31 +318,49 @@ export class OpenDirectory extends Fd { path_filestat_get( flags: number, - path: string, + path_str: string, ): { ret: number; filestat: wasi.Filestat | null } { - const entry = this.dir.get_entry_for_path(path); + const { ret: path_err, path } = Path.from(path_str); + if (path == null) { + return { ret: path_err, filestat: null }; + } + + const { ret, entry } = this.dir.get_entry_for_path(path); if (entry == null) { - return { ret: wasi.ERRNO_NOENT, filestat: null }; + return { ret, filestat: null }; } + return { ret: 0, filestat: entry.stat() }; } path_open( dirflags: number, - path: string, + path_str: string, oflags: number, fs_rights_base: bigint, fs_rights_inheriting: bigint, fd_flags: number, ): { ret: number; fd_obj: Fd | null } { - let entry = this.dir.get_entry_for_path(path); + let { ret: path_ret, path } = Path.from(path_str); + if (path == null) { + return { ret: path_ret, fd_obj: null }; + } + + let { ret, entry } = this.dir.get_entry_for_path(path); if (entry == null) { + if (ret != wasi.ERRNO_NOENT) { + return { ret, fd_obj: null }; + } if ((oflags & wasi.OFLAGS_CREAT) == wasi.OFLAGS_CREAT) { // doesn't exist, but shall be created - entry = this.dir.create_entry_for_path( - path, + let { ret, entry: new_entry } = this.dir.create_entry_for_path( + path_str, (oflags & wasi.OFLAGS_DIRECTORY) == wasi.OFLAGS_DIRECTORY, ); + if (new_entry == null) { + return { ret, fd_obj: null }; + } + entry = new_entry; } else { // doesn't exist, no such file return { ret: wasi.ERRNO_NOENT, fd_obj: null }; @@ -372,33 +390,44 @@ export class OpenDirectory extends Fd { ).ret; } - path_unlink_file(path: string): number { - path = this.clean_path(path); - const parentDirEntry = this.dir.get_parent_dir_for_path(path); - const pathComponents = path.split("/"); - const fileName = pathComponents[pathComponents.length - 1]; - const entry = this.dir.get_entry_for_path(path); - if (entry === null) { - return wasi.ERRNO_NOENT; + path_unlink_file(path_str: string): number { + let { ret: path_ret, path } = Path.from(path_str); + if (path == null) { + return path_ret; + } + + const { + ret: parent_ret, + parent_entry, + filename, + entry, + } = this.dir.get_parent_dir_and_entry_for_path(path, false); + if (parent_entry == null || filename == null || entry == null) { + return parent_ret; } if (entry.stat().filetype === wasi.FILETYPE_DIRECTORY) { return wasi.ERRNO_ISDIR; } - parentDirEntry.contents.delete(fileName); + parent_entry.contents.delete(filename); return wasi.ERRNO_SUCCESS; } - path_remove_directory(path: string): number { - path = this.clean_path(path); - const parentDirEntry = this.dir.get_parent_dir_for_path(path); - const pathComponents = path.split("/"); - const fileName = pathComponents[pathComponents.length - 1]; - - const entry = this.dir.get_entry_for_path(path); + path_remove_directory(path_str: string): number { + let { ret: path_ret, path } = Path.from(path_str); + if (path == null) { + return path_ret; + } - if (entry === null) { - return wasi.ERRNO_NOENT; + const { + ret: parent_ret, + parent_entry, + filename, + entry, + } = this.dir.get_parent_dir_and_entry_for_path(path, false); + if (parent_entry == null || filename == null || entry == null) { + return parent_ret; } + if ( !(entry instanceof Directory) || entry.stat().filetype !== wasi.FILETYPE_DIRECTORY @@ -408,18 +437,11 @@ export class OpenDirectory extends Fd { if (entry.contents.size !== 0) { return wasi.ERRNO_NOTEMPTY; } - if (!parentDirEntry.contents.delete(fileName)) { + if (!parent_entry.contents.delete(filename)) { return wasi.ERRNO_NOENT; } return wasi.ERRNO_SUCCESS; } - - clean_path(path: string): string { - while (path.length > 0 && path[path.length - 1] === "/") { - path = path.slice(0, path.length - 1); - } - return path; - } } export class PreopenDirectory extends OpenDirectory { @@ -548,6 +570,41 @@ export class SyncOPFSFile extends Inode { } } +class Path { + parts: string[] = []; + is_dir: boolean = false; + + static from(path: string): { ret: number; path: Path | null } { + let self = new Path(); + self.is_dir = path.endsWith("/"); + + if (path.startsWith("/")) { + return { ret: wasi.ERRNO_NOTCAPABLE, path: null }; + } + + for (const component of path.split("/")) { + if (component === "" || component === ".") { + continue; + } + if (component === "..") { + self.parts.pop(); + continue; + } + self.parts.push(component); + } + + return { ret: wasi.ERRNO_SUCCESS, path: self }; + } + + to_path_string(): string { + let s = this.parts.join("/"); + if (this.is_dir) { + s += "/"; + } + return s; + } +} + export class Directory extends Inode { contents: Map; @@ -569,75 +626,112 @@ export class Directory extends Inode { return new wasi.Filestat(wasi.FILETYPE_DIRECTORY, 0n); } - get_entry_for_path(path: string): Inode | null { + get_entry_for_path(path: Path): { ret: number; entry: Inode | null } { let entry: Inode = this; - for (const component of path.split("/")) { - if (component == "") break; - if (component == ".") continue; + for (const component of path.parts) { if (!(entry instanceof Directory)) { - return null; + return { ret: wasi.ERRNO_NOTDIR, entry: null }; } const child = entry.contents.get(component); if (child !== undefined) { entry = child; } else { debug.log(component); - return null; + return { ret: wasi.ERRNO_NOENT, entry: null }; } } - return entry; + return { ret: wasi.ERRNO_SUCCESS, entry }; } - get_parent_dir_for_path(path: string): Directory | null { - if (path === "") return null; - let entry: Inode = this; - let parentEntry: Directory = this; - for (const component of path.split("/")) { - if (component === "") break; - if (component === ".") continue; - if (!(entry instanceof Directory)) { - debug.log(entry); - return null; - } - const child = entry.contents.get(component); - if (child === undefined) { - debug.log(component); - return null; + get_parent_dir_and_entry_for_path( + path: Path, + allow_undefined: boolean, + ): { + ret: number; + parent_entry: Directory | null; + filename: string | null; + entry: Inode | null; + } { + let filename = path.parts.pop(); + path.is_dir = true; + + if (filename === undefined) { + return { + ret: wasi.ERRNO_INVAL, + parent_entry: null, + filename: null, + entry: null, + }; + } + + const { ret: entry_ret, entry: parent_entry } = + this.get_entry_for_path(path); + if (parent_entry == null) { + return { + ret: entry_ret, + parent_entry: null, + filename: null, + entry: null, + }; + } + if (!(parent_entry instanceof Directory)) { + return { + ret: wasi.ERRNO_NOTDIR, + parent_entry: null, + filename: null, + entry: null, + }; + } + let entry: Inode | undefined | null = parent_entry.contents.get(filename); + if (entry === undefined) { + if (!allow_undefined) { + return { + ret: wasi.ERRNO_NOENT, + parent_entry: null, + filename: null, + entry: null, + }; + } else { + return { ret: wasi.ERRNO_SUCCESS, parent_entry, filename, entry: null }; } - parentEntry = entry; - entry = child; } - return parentEntry; + return { ret: wasi.ERRNO_SUCCESS, parent_entry, filename, entry }; } - create_entry_for_path(path: string, is_dir: boolean): Inode { - let entry: Inode = this; + create_entry_for_path( + path_str: string, + is_dir: boolean, + ): { ret: number; entry: Inode | null } { + let { ret: path_ret, path } = Path.from(path_str); + if (path == null) { + return { ret: path_ret, entry: null }; + } - const components: Array = path - .split("/") - .filter((component) => component != "/"); - for (let i = 0; i < components.length; i++) { - const component = components[i]; - if (!(entry instanceof Directory)) { - break; - } - const child = entry.contents.get(component); - if (child !== undefined) { - entry = child; - } else { - debug.log("create", component); - let new_child; - if (i == components.length - 1 && !is_dir) { - new_child = new File(new ArrayBuffer(0)); - } else { - new_child = new Directory(new Map()); - } - entry.contents.set(component, new_child); - entry = new_child; - } + let { + ret: parent_ret, + parent_entry, + filename, + entry, + } = this.get_parent_dir_and_entry_for_path(path, true); + if (parent_entry == null || filename == null) { + return { ret: parent_ret, entry: null }; + } + + if (entry != null) { + return { ret: wasi.ERRNO_EXIST, entry: null }; + } + + debug.log("create", path); + let new_child; + if (!is_dir) { + new_child = new File(new ArrayBuffer(0)); + } else { + new_child = new Directory(new Map()); } + parent_entry.contents.set(filename, new_child); + entry = new_child; - return entry; + return { ret: wasi.ERRNO_SUCCESS, entry }; } } From 61f5fa5d36b1bd3a8f2624080eba361b200924fc Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Sat, 23 Dec 2023 20:10:06 +0100 Subject: [PATCH 05/19] Fix interesting_paths test --- src/fs_core.ts | 27 +++++++++++++++++++++++++-- test/skip.json | 3 +-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/fs_core.ts b/src/fs_core.ts index b202146..78d0475 100644 --- a/src/fs_core.ts +++ b/src/fs_core.ts @@ -581,13 +581,18 @@ class Path { if (path.startsWith("/")) { return { ret: wasi.ERRNO_NOTCAPABLE, path: null }; } + if (path.includes("\0")) { + return { ret: wasi.ERRNO_INVAL, path: null }; + } for (const component of path.split("/")) { if (component === "" || component === ".") { continue; } if (component === "..") { - self.parts.pop(); + if (self.parts.pop() == undefined) { + return { ret: wasi.ERRNO_NOTCAPABLE, path: null }; + } continue; } self.parts.push(component); @@ -640,6 +645,13 @@ export class Directory extends Inode { return { ret: wasi.ERRNO_NOENT, entry: null }; } } + + if (path.is_dir) { + if (entry.stat().filetype != wasi.FILETYPE_DIRECTORY) { + return { ret: wasi.ERRNO_NOTDIR, entry: null }; + } + } + return { ret: wasi.ERRNO_SUCCESS, entry }; } @@ -653,7 +665,6 @@ export class Directory extends Inode { entry: Inode | null; } { let filename = path.parts.pop(); - path.is_dir = true; if (filename === undefined) { return { @@ -695,6 +706,18 @@ export class Directory extends Inode { return { ret: wasi.ERRNO_SUCCESS, parent_entry, filename, entry: null }; } } + + if (path.is_dir) { + if (entry.stat().filetype != wasi.FILETYPE_DIRECTORY) { + return { + ret: wasi.ERRNO_NOTDIR, + parent_entry: null, + filename: null, + entry: null, + }; + } + } + return { ret: wasi.ERRNO_SUCCESS, parent_entry, filename, entry }; } diff --git a/test/skip.json b/test/skip.json index 5bf1aa6..0eda596 100644 --- a/test/skip.json +++ b/test/skip.json @@ -32,7 +32,6 @@ "path_open_preopen": "fail", "fd_readdir": "fail", "symlink_filestat": "fail", - "symlink_loop": "fail", - "interesting_paths": "fail" + "symlink_loop": "fail" } } From cfc728da0d51c217e535c9128a63bcee7d6b57f7 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Sat, 23 Dec 2023 20:19:28 +0100 Subject: [PATCH 06/19] Fix fd_readdir test --- src/fs_core.ts | 21 +++++++++++++++++++-- src/wasi_defs.ts | 2 +- test/skip.json | 2 -- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/fs_core.ts b/src/fs_core.ts index 78d0475..da8ace4 100644 --- a/src/fs_core.ts +++ b/src/fs_core.ts @@ -302,12 +302,25 @@ export class OpenDirectory extends Fd { debug.log("readdir_single", cookie); debug.log(cookie, this.dir.contents.keys()); } - if (cookie >= BigInt(this.dir.contents.size)) { + + if (cookie == 0n) { + return { + ret: wasi.ERRNO_SUCCESS, + dirent: new wasi.Dirent(1n, ".", wasi.FILETYPE_DIRECTORY), + }; + } else if (cookie == 1n) { + return { + ret: wasi.ERRNO_SUCCESS, + dirent: new wasi.Dirent(2n, "..", wasi.FILETYPE_DIRECTORY), + }; + } + + if (cookie >= BigInt(this.dir.contents.size) + 2n) { return { ret: 0, dirent: null }; } const [name, entry] = Array.from(this.dir.contents.entries())[ - Number(cookie) + Number(cookie - 2n) ]; return { @@ -442,6 +455,10 @@ export class OpenDirectory extends Fd { } return wasi.ERRNO_SUCCESS; } + + fd_filestat_get(): { ret: number; filestat: wasi.Filestat } { + return { ret: 0, filestat: this.dir.stat() }; + } } export class PreopenDirectory extends OpenDirectory { diff --git a/src/wasi_defs.ts b/src/wasi_defs.ts index 52f4b5a..b41aed1 100644 --- a/src/wasi_defs.ts +++ b/src/wasi_defs.ts @@ -184,7 +184,7 @@ export const FILETYPE_SYMBOLIC_LINK = 7; export class Dirent { d_next: bigint; - d_ino: bigint = 1n; + d_ino: bigint = 0n; d_namlen: number; d_type: number; dir_name: Uint8Array; diff --git a/test/skip.json b/test/skip.json index 0eda596..3287e29 100644 --- a/test/skip.json +++ b/test/skip.json @@ -27,10 +27,8 @@ "poll_oneoff_stdio": "fail", "dangling_symlink": "fail", "dir_fd_op_failures": "fail", - "file_allocate": "fail", "nofollow_errors": "fail", "path_open_preopen": "fail", - "fd_readdir": "fail", "symlink_filestat": "fail", "symlink_loop": "fail" } From 0d8cb3351772c305541e34df3c61116a18181cae Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Sat, 23 Dec 2023 21:00:01 +0100 Subject: [PATCH 07/19] Fix dir_fd_op_failures test --- src/fs_core.ts | 48 +++++++++++++++++++++++++++++++++++++++++++----- src/wasi.ts | 13 +++++++++---- test/skip.json | 3 --- 3 files changed, 52 insertions(+), 12 deletions(-) diff --git a/src/fs_core.ts b/src/fs_core.ts index da8ace4..9428830 100644 --- a/src/fs_core.ts +++ b/src/fs_core.ts @@ -271,10 +271,6 @@ export class OpenSyncOPFSFile extends Fd { fd_sync(): number { return this.fd_datasync(); } - - fd_close(): number { - return wasi.ERRNO_SUCCESS; - } } export class OpenDirectory extends Fd { @@ -287,7 +283,15 @@ export class OpenDirectory extends Fd { // eslint-disable-next-line @typescript-eslint/no-unused-vars fd_seek(offset: bigint, whence: number): { ret: number; offset: bigint } { - return { ret: wasi.ERRNO_ISDIR, offset: 0n }; + return { ret: wasi.ERRNO_BADF, offset: 0n }; + } + + fd_tell(): { ret: number; offset: bigint } { + return { ret: wasi.ERRNO_BADF, offset: 0n }; + } + + fd_allocate(offset: bigint, len: bigint): number { + return wasi.ERRNO_BADF; } fd_fdstat_get(): { ret: number; fdstat: wasi.Fdstat | null } { @@ -459,6 +463,40 @@ export class OpenDirectory extends Fd { fd_filestat_get(): { ret: number; filestat: wasi.Filestat } { return { ret: 0, filestat: this.dir.stat() }; } + + fd_filestat_set_size(size: bigint): number { + return wasi.ERRNO_BADF; + } + + fd_read( + view8: Uint8Array, + iovs: wasi.Iovec[], + ): { ret: number; nread: number } { + return { ret: wasi.ERRNO_BADF, nread: 0 }; + } + + fd_pread( + view8: Uint8Array, + iovs: wasi.Iovec[], + offset: bigint, + ): { ret: number; nread: number } { + return { ret: wasi.ERRNO_BADF, nread: 0 }; + } + + fd_write( + view8: Uint8Array, + iovs: wasi.Ciovec[], + ): { ret: number; nwritten: number } { + return { ret: wasi.ERRNO_BADF, nwritten: 0 }; + } + + fd_pwrite( + view8: Uint8Array, + iovs: wasi.Ciovec[], + offset: bigint, + ): { ret: number; nwritten: number } { + return { ret: wasi.ERRNO_BADF, nwritten: 0 }; + } } export class PreopenDirectory extends OpenDirectory { diff --git a/src/wasi.ts b/src/wasi.ts index abb4e33..67aceef 100644 --- a/src/wasi.ts +++ b/src/wasi.ts @@ -329,11 +329,16 @@ export default class WASI { // FIXME don't ignore path_len if (self.fds[fd] != undefined) { const { ret, prestat_dir_name } = self.fds[fd].fd_prestat_dir_name(); - if (prestat_dir_name != null) { - const buffer8 = new Uint8Array(self.inst.exports.memory.buffer); - buffer8.set(prestat_dir_name, path_ptr); + if (prestat_dir_name == null) { + return ret; } - return ret; + + const buffer8 = new Uint8Array(self.inst.exports.memory.buffer); + buffer8.set(prestat_dir_name.slice(0, path_len), path_ptr); + + return prestat_dir_name.byteLength > path_len + ? wasi.ERRNO_NAMETOOLONG + : wasi.ERRNO_SUCCESS; } else { return wasi.ERRNO_BADF; } diff --git a/test/skip.json b/test/skip.json index 3287e29..7ebf4d6 100644 --- a/test/skip.json +++ b/test/skip.json @@ -8,13 +8,11 @@ "fdopendir-with-access": "fail" }, "WASI Rust tests": { - "sched_yield": "not implemented yet", "path_rename": "fail", "path_exists": "fail", "path_open_dirfd_not_dir": "fail", "fd_filestat_set": "fail", "symlink_create": "fail", - "overwrite_preopen": "fail", "path_open_read_write": "fail", "path_rename_dir_trailing_slashes": "fail", "fd_flags_set": "fail", @@ -26,7 +24,6 @@ "path_symlink_trailing_slashes": "fail", "poll_oneoff_stdio": "fail", "dangling_symlink": "fail", - "dir_fd_op_failures": "fail", "nofollow_errors": "fail", "path_open_preopen": "fail", "symlink_filestat": "fail", From f565cfc248e955dbb637cdcda53e50c6be7bada5 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Sat, 23 Dec 2023 21:22:09 +0100 Subject: [PATCH 08/19] Implement path_link --- src/fd.ts | 23 ++++++++++++++++------ src/fs_core.ts | 53 ++++++++++++++++++++++++++++++++++++++++++++++++-- src/index.ts | 3 +-- src/inode.ts | 13 ------------- src/wasi.ts | 10 ++++++---- test/skip.json | 1 - 6 files changed, 75 insertions(+), 28 deletions(-) delete mode 100644 src/inode.ts diff --git a/src/fd.ts b/src/fd.ts index 435685a..771fbe2 100644 --- a/src/fd.ts +++ b/src/fd.ts @@ -100,14 +100,15 @@ export abstract class Fd { ): number { return wasi.ERRNO_NOTSUP; } - path_link( - old_fd: number, - old_flags: number, - old_path: string, - new_path: string, - ): number { + path_link(path: string, inode: Inode): number { return wasi.ERRNO_NOTSUP; } + path_lookup( + path: string, + dirflags: number, + ): { ret: number; inode_obj: Inode | null } { + return { ret: wasi.ERRNO_NOTSUP, inode_obj: null }; + } path_open( dirflags: number, path: string, @@ -134,3 +135,13 @@ export abstract class Fd { return wasi.ERRNO_NOTSUP; } } + +export abstract class Inode { + abstract path_open( + oflags: number, + fs_rights_base: bigint, + fd_flags: number, + ): { ret: number; fd_obj: Fd | null }; + + abstract stat(): wasi.Filestat; +} diff --git a/src/fs_core.ts b/src/fs_core.ts index 9428830..b36f093 100644 --- a/src/fs_core.ts +++ b/src/fs_core.ts @@ -1,7 +1,6 @@ import { debug } from "./debug.js"; import * as wasi from "./wasi_defs.js"; -import { Fd } from "./fd.js"; -import { Inode } from "./inode.js"; +import { Fd, Inode } from "./fd.js"; export class OpenFile extends Fd { file: File; @@ -350,6 +349,23 @@ export class OpenDirectory extends Fd { return { ret: 0, filestat: entry.stat() }; } + path_lookup( + path_str: string, + dirflags: number, + ): { ret: number; inode_obj: Inode | null } { + let { ret: path_ret, path } = Path.from(path_str); + if (path == null) { + return { ret: path_ret, inode_obj: null }; + } + + let { ret, entry } = this.dir.get_entry_for_path(path); + if (entry == null) { + return { ret, inode_obj: null }; + } + + return { ret: wasi.ERRNO_SUCCESS, inode_obj: entry }; + } + path_open( dirflags: number, path_str: string, @@ -407,6 +423,39 @@ export class OpenDirectory extends Fd { ).ret; } + path_link(path_str: string, inode: Inode): number { + let { ret: path_ret, path } = Path.from(path_str); + if (path_str == null) { + return path_ret; + } + + if (path.is_dir) { + return wasi.ERRNO_NOENT; + } + + const { + ret: parent_ret, + parent_entry, + filename, + entry, + } = this.dir.get_parent_dir_and_entry_for_path(path, true); + if (parent_entry == null || filename == null) { + return parent_ret; + } + + if (entry != null) { + return wasi.ERRNO_EXIST; + } + + if (inode.stat().filetype == wasi.FILETYPE_DIRECTORY) { + return wasi.ERRNO_PERM; + } + + parent_entry.contents.set(filename, inode); + + return wasi.ERRNO_SUCCESS; + } + path_unlink_file(path_str: string): number { let { ret: path_ret, path } = Path.from(path_str); if (path == null) { diff --git a/src/index.ts b/src/index.ts index b69ef7f..272076b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,7 @@ import WASI, { WASIProcExit } from "./wasi.js"; export { WASI, WASIProcExit }; -export { Fd } from "./fd.js"; -export { Inode } from "./inode.js"; +export { Fd, Inode } from "./fd.js"; export { File, SyncOPFSFile, diff --git a/src/inode.ts b/src/inode.ts deleted file mode 100644 index af04ff2..0000000 --- a/src/inode.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* eslint @typescript-eslint/no-unused-vars:0 */ -import * as wasi from "./wasi_defs.js"; -import { Fd } from "./fd.js"; - -export abstract class Inode { - abstract path_open( - oflags: number, - fs_rights_base: bigint, - fd_flags: number, - ): { ret: number; fd_obj: Fd | null }; - - abstract stat(): wasi.Filestat; -} diff --git a/src/wasi.ts b/src/wasi.ts index 67aceef..0d2ce25 100644 --- a/src/wasi.ts +++ b/src/wasi.ts @@ -594,12 +594,14 @@ export default class WASI { const new_path = new TextDecoder("utf-8").decode( buffer8.slice(new_path_ptr, new_path_ptr + new_path_len), ); - return self.fds[new_fd].path_link( - old_fd, - old_flags, + const { ret, inode_obj } = self.fds[old_fd].path_lookup( old_path, - new_path, + old_flags, ); + if (inode_obj == null) { + return ret; + } + return self.fds[new_fd].path_link(new_path, inode_obj); } else { return wasi.ERRNO_BADF; } diff --git a/test/skip.json b/test/skip.json index 7ebf4d6..fdc8d6b 100644 --- a/test/skip.json +++ b/test/skip.json @@ -20,7 +20,6 @@ "path_link": "fail", "fd_fdstat_set_rights": "fail", "readlink": "fail", - "unlink_file_trailing_slashes": "fail", "path_symlink_trailing_slashes": "fail", "poll_oneoff_stdio": "fail", "dangling_symlink": "fail", From cdb9dfc0d55989d5414c5e377dee7684ee62cbc1 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Sat, 23 Dec 2023 21:30:58 +0100 Subject: [PATCH 09/19] Fix lint warnings --- src/fs_core.ts | 42 +++++++++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/src/fs_core.ts b/src/fs_core.ts index b36f093..5808afe 100644 --- a/src/fs_core.ts +++ b/src/fs_core.ts @@ -198,6 +198,7 @@ export class OpenSyncOPFSFile extends Fd { }; } + // eslint-disable-next-line @typescript-eslint/no-unused-vars fd_filestat_set_size(size: bigint): number { this.file.handle.truncate(Number(size)); return wasi.ERRNO_SUCCESS; @@ -289,6 +290,7 @@ export class OpenDirectory extends Fd { return { ret: wasi.ERRNO_BADF, offset: 0n }; } + // eslint-disable-next-line @typescript-eslint/no-unused-vars fd_allocate(offset: bigint, len: bigint): number { return wasi.ERRNO_BADF; } @@ -351,14 +353,15 @@ export class OpenDirectory extends Fd { path_lookup( path_str: string, + // eslint-disable-next-line @typescript-eslint/no-unused-vars dirflags: number, ): { ret: number; inode_obj: Inode | null } { - let { ret: path_ret, path } = Path.from(path_str); + const { ret: path_ret, path } = Path.from(path_str); if (path == null) { return { ret: path_ret, inode_obj: null }; } - let { ret, entry } = this.dir.get_entry_for_path(path); + const { ret, entry } = this.dir.get_entry_for_path(path); if (entry == null) { return { ret, inode_obj: null }; } @@ -367,18 +370,21 @@ export class OpenDirectory extends Fd { } path_open( + // eslint-disable-next-line @typescript-eslint/no-unused-vars dirflags: number, path_str: string, oflags: number, fs_rights_base: bigint, + // eslint-disable-next-line @typescript-eslint/no-unused-vars fs_rights_inheriting: bigint, fd_flags: number, ): { ret: number; fd_obj: Fd | null } { - let { ret: path_ret, path } = Path.from(path_str); + const { ret: path_ret, path } = Path.from(path_str); if (path == null) { return { ret: path_ret, fd_obj: null }; } + // eslint-disable-next-line prefer-const let { ret, entry } = this.dir.get_entry_for_path(path); if (entry == null) { if (ret != wasi.ERRNO_NOENT) { @@ -386,7 +392,7 @@ export class OpenDirectory extends Fd { } if ((oflags & wasi.OFLAGS_CREAT) == wasi.OFLAGS_CREAT) { // doesn't exist, but shall be created - let { ret, entry: new_entry } = this.dir.create_entry_for_path( + const { ret, entry: new_entry } = this.dir.create_entry_for_path( path_str, (oflags & wasi.OFLAGS_DIRECTORY) == wasi.OFLAGS_DIRECTORY, ); @@ -424,7 +430,7 @@ export class OpenDirectory extends Fd { } path_link(path_str: string, inode: Inode): number { - let { ret: path_ret, path } = Path.from(path_str); + const { ret: path_ret, path } = Path.from(path_str); if (path_str == null) { return path_ret; } @@ -457,7 +463,7 @@ export class OpenDirectory extends Fd { } path_unlink_file(path_str: string): number { - let { ret: path_ret, path } = Path.from(path_str); + const { ret: path_ret, path } = Path.from(path_str); if (path == null) { return path_ret; } @@ -479,7 +485,7 @@ export class OpenDirectory extends Fd { } path_remove_directory(path_str: string): number { - let { ret: path_ret, path } = Path.from(path_str); + const { ret: path_ret, path } = Path.from(path_str); if (path == null) { return path_ret; } @@ -513,35 +519,46 @@ export class OpenDirectory extends Fd { return { ret: 0, filestat: this.dir.stat() }; } + // eslint-disable-next-line @typescript-eslint/no-unused-vars fd_filestat_set_size(size: bigint): number { return wasi.ERRNO_BADF; } fd_read( + // eslint-disable-next-line @typescript-eslint/no-unused-vars view8: Uint8Array, + // eslint-disable-next-line @typescript-eslint/no-unused-vars iovs: wasi.Iovec[], ): { ret: number; nread: number } { return { ret: wasi.ERRNO_BADF, nread: 0 }; } fd_pread( + // eslint-disable-next-line @typescript-eslint/no-unused-vars view8: Uint8Array, + // eslint-disable-next-line @typescript-eslint/no-unused-vars iovs: wasi.Iovec[], + // eslint-disable-next-line @typescript-eslint/no-unused-vars offset: bigint, ): { ret: number; nread: number } { return { ret: wasi.ERRNO_BADF, nread: 0 }; } fd_write( + // eslint-disable-next-line @typescript-eslint/no-unused-vars view8: Uint8Array, + // eslint-disable-next-line @typescript-eslint/no-unused-vars iovs: wasi.Ciovec[], ): { ret: number; nwritten: number } { return { ret: wasi.ERRNO_BADF, nwritten: 0 }; } fd_pwrite( + // eslint-disable-next-line @typescript-eslint/no-unused-vars view8: Uint8Array, + // eslint-disable-next-line @typescript-eslint/no-unused-vars iovs: wasi.Ciovec[], + // eslint-disable-next-line @typescript-eslint/no-unused-vars offset: bigint, ): { ret: number; nwritten: number } { return { ret: wasi.ERRNO_BADF, nwritten: 0 }; @@ -679,7 +696,7 @@ class Path { is_dir: boolean = false; static from(path: string): { ret: number; path: Path | null } { - let self = new Path(); + const self = new Path(); self.is_dir = path.endsWith("/"); if (path.startsWith("/")) { @@ -768,7 +785,7 @@ export class Directory extends Inode { filename: string | null; entry: Inode | null; } { - let filename = path.parts.pop(); + const filename = path.parts.pop(); if (filename === undefined) { return { @@ -797,7 +814,7 @@ export class Directory extends Inode { entry: null, }; } - let entry: Inode | undefined | null = parent_entry.contents.get(filename); + const entry: Inode | undefined | null = parent_entry.contents.get(filename); if (entry === undefined) { if (!allow_undefined) { return { @@ -829,14 +846,17 @@ export class Directory extends Inode { path_str: string, is_dir: boolean, ): { ret: number; entry: Inode | null } { - let { ret: path_ret, path } = Path.from(path_str); + const { ret: path_ret, path } = Path.from(path_str); if (path == null) { return { ret: path_ret, entry: null }; } let { + // eslint-disable-next-line prefer-const ret: parent_ret, + // eslint-disable-next-line prefer-const parent_entry, + // eslint-disable-next-line prefer-const filename, entry, } = this.get_parent_dir_and_entry_for_path(path, true); From 8e35e1a11ebca06abf0fa3cb5fedcec095965326 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Sat, 23 Dec 2023 22:05:45 +0100 Subject: [PATCH 10/19] Implement path_rename --- src/fd.ts | 7 +++++-- src/fs_core.ts | 56 ++++++++++++++++++++++++++++++++++++++++++++++---- src/wasi.ts | 35 +++++++++++++++++++++++-------- test/skip.json | 2 -- 4 files changed, 84 insertions(+), 16 deletions(-) diff --git a/src/fd.ts b/src/fd.ts index 771fbe2..b192ce9 100644 --- a/src/fd.ts +++ b/src/fd.ts @@ -100,9 +100,12 @@ export abstract class Fd { ): number { return wasi.ERRNO_NOTSUP; } - path_link(path: string, inode: Inode): number { + path_link(path: string, inode: Inode, allow_dir: boolean): number { return wasi.ERRNO_NOTSUP; } + path_unlink(path: string): { ret: number; inode_obj: Inode | null } { + return { ret: wasi.ERRNO_NOTSUP, inode_obj: null }; + } path_lookup( path: string, dirflags: number, @@ -117,7 +120,7 @@ export abstract class Fd { fs_rights_inheriting: bigint, fd_flags: number, ): { ret: number; fd_obj: Fd | null } { - return { ret: wasi.ERRNO_NOTSUP, fd_obj: null }; + return { ret: wasi.ERRNO_NOTDIR, fd_obj: null }; } path_readlink(path: string): { ret: number; data: string | null } { return { ret: wasi.ERRNO_NOTSUP, data: null }; diff --git a/src/fs_core.ts b/src/fs_core.ts index 5808afe..59f4d95 100644 --- a/src/fs_core.ts +++ b/src/fs_core.ts @@ -429,9 +429,9 @@ export class OpenDirectory extends Fd { ).ret; } - path_link(path_str: string, inode: Inode): number { + path_link(path_str: string, inode: Inode, allow_dir: boolean): number { const { ret: path_ret, path } = Path.from(path_str); - if (path_str == null) { + if (path == null) { return path_ret; } @@ -450,10 +450,33 @@ export class OpenDirectory extends Fd { } if (entry != null) { - return wasi.ERRNO_EXIST; + const source_is_dir = inode.stat().filetype == wasi.FILETYPE_DIRECTORY; + const target_is_dir = entry.stat().filetype == wasi.FILETYPE_DIRECTORY; + if (source_is_dir && target_is_dir) { + if (allow_dir && entry instanceof Directory) { + if (entry.contents.size == 0) { + // Allow overwriting empty directories + } else { + return wasi.ERRNO_NOTEMPTY; + } + } else { + return wasi.ERRNO_EXIST; + } + } else if (source_is_dir && !target_is_dir) { + return wasi.ERRNO_NOTDIR; + } else if (!source_is_dir && target_is_dir) { + return wasi.ERRNO_ISDIR; + } else if ( + inode.stat().filetype == wasi.FILETYPE_REGULAR_FILE && + entry.stat().filetype == wasi.FILETYPE_REGULAR_FILE + ) { + // Overwriting regular files is fine + } else { + return wasi.ERRNO_EXIST; + } } - if (inode.stat().filetype == wasi.FILETYPE_DIRECTORY) { + if (!allow_dir && inode.stat().filetype == wasi.FILETYPE_DIRECTORY) { return wasi.ERRNO_PERM; } @@ -462,6 +485,31 @@ export class OpenDirectory extends Fd { return wasi.ERRNO_SUCCESS; } + path_unlink(path_str: string): { ret: number; inode_obj: Inode | null } { + const { ret: path_ret, path } = Path.from(path_str); + if (path == null) { + return { ret: path_ret, inode_obj: null }; + } + + const { + ret: parent_ret, + parent_entry, + filename, + entry, + } = this.dir.get_parent_dir_and_entry_for_path(path, true); + if (parent_entry == null || filename == null) { + return { ret: parent_ret, inode_obj: null }; + } + + if (entry == null) { + return { ret: wasi.ERRNO_NOENT, inode_obj: null }; + } + + parent_entry.contents.delete(filename); + + return { ret: wasi.ERRNO_SUCCESS, inode_obj: entry }; + } + path_unlink_file(path_str: string): number { const { ret: path_ret, path } = Path.from(path_str); if (path == null) { diff --git a/src/wasi.ts b/src/wasi.ts index 0d2ce25..9da80f9 100644 --- a/src/wasi.ts +++ b/src/wasi.ts @@ -601,7 +601,7 @@ export default class WASI { if (inode_obj == null) { return ret; } - return self.fds[new_fd].path_link(new_path, inode_obj); + return self.fds[new_fd].path_link(new_path, inode_obj, false); } else { return wasi.ERRNO_BADF; } @@ -690,20 +690,39 @@ export default class WASI { } }, path_rename( - // eslint-disable-next-line @typescript-eslint/no-unused-vars fd: number, - // eslint-disable-next-line @typescript-eslint/no-unused-vars old_path_ptr: number, - // eslint-disable-next-line @typescript-eslint/no-unused-vars old_path_len: number, - // eslint-disable-next-line @typescript-eslint/no-unused-vars new_fd: number, - // eslint-disable-next-line @typescript-eslint/no-unused-vars new_path_ptr: number, - // eslint-disable-next-line @typescript-eslint/no-unused-vars new_path_len: number, ): number { - throw "FIXME what is the best abstraction for this?"; + const buffer8 = new Uint8Array(self.inst.exports.memory.buffer); + if (self.fds[fd] != undefined && self.fds[new_fd] != undefined) { + const old_path = new TextDecoder("utf-8").decode( + buffer8.slice(old_path_ptr, old_path_ptr + old_path_len), + ); + const new_path = new TextDecoder("utf-8").decode( + buffer8.slice(new_path_ptr, new_path_ptr + new_path_len), + ); + // eslint-disable-next-line prefer-const + let { ret, inode_obj } = self.fds[fd].path_unlink(old_path); + if (inode_obj == null) { + return ret; + } + ret = self.fds[new_fd].path_link(new_path, inode_obj, true); + if (ret != wasi.ERRNO_SUCCESS) { + if ( + self.fds[fd].path_link(old_path, inode_obj, true) != + wasi.ERRNO_SUCCESS + ) { + throw "path_link should always return success when relinking an inode back to the original place"; + } + } + return ret; + } else { + return wasi.ERRNO_BADF; + } }, path_symlink( old_path_ptr: number, diff --git a/test/skip.json b/test/skip.json index fdc8d6b..5b75508 100644 --- a/test/skip.json +++ b/test/skip.json @@ -8,9 +8,7 @@ "fdopendir-with-access": "fail" }, "WASI Rust tests": { - "path_rename": "fail", "path_exists": "fail", - "path_open_dirfd_not_dir": "fail", "fd_filestat_set": "fail", "symlink_create": "fail", "path_open_read_write": "fail", From b1f26fa169d8762a3cc282e606653c4467c6a78c Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Sun, 24 Dec 2023 20:37:12 +0100 Subject: [PATCH 11/19] Move origin private file system support to a separate module --- src/fs_core.ts | 173 +------------------------------------------------ src/fs_opfs.ts | 171 ++++++++++++++++++++++++++++++++++++++++++++++++ src/index.ts | 3 +- 3 files changed, 175 insertions(+), 172 deletions(-) create mode 100644 src/fs_opfs.ts diff --git a/src/fs_core.ts b/src/fs_core.ts index 59f4d95..4cea8f0 100644 --- a/src/fs_core.ts +++ b/src/fs_core.ts @@ -165,114 +165,6 @@ export class OpenFile extends Fd { } } -export class OpenSyncOPFSFile extends Fd { - file: SyncOPFSFile; - position: bigint = 0n; - - constructor(file: SyncOPFSFile) { - super(); - this.file = file; - } - - fd_allocate(offset: bigint, len: bigint): number { - if (BigInt(this.file.handle.getSize()) > offset + len) { - // already big enough - } else { - // extend - this.file.handle.truncate(Number(offset + len)); - } - return wasi.ERRNO_SUCCESS; - } - - fd_fdstat_get(): { ret: number; fdstat: wasi.Fdstat | null } { - return { ret: 0, fdstat: new wasi.Fdstat(wasi.FILETYPE_REGULAR_FILE, 0) }; - } - - fd_filestat_get(): { ret: number; filestat: wasi.Filestat } { - return { - ret: 0, - filestat: new wasi.Filestat( - wasi.FILETYPE_REGULAR_FILE, - BigInt(this.file.handle.getSize()), - ), - }; - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - fd_filestat_set_size(size: bigint): number { - this.file.handle.truncate(Number(size)); - return wasi.ERRNO_SUCCESS; - } - - fd_read( - view8: Uint8Array, - iovs: Array, - ): { ret: number; nread: number } { - let nread = 0; - for (const iovec of iovs) { - if (this.position < this.file.handle.getSize()) { - const buf = new Uint8Array(view8.buffer, iovec.buf, iovec.buf_len); - const n = this.file.handle.read(buf, { at: Number(this.position) }); - this.position += BigInt(n); - nread += n; - } else { - break; - } - } - return { ret: 0, nread }; - } - - fd_seek( - offset: number | bigint, - whence: number, - ): { ret: number; offset: bigint } { - let calculated_offset: bigint; - switch (whence) { - case wasi.WHENCE_SET: - calculated_offset = BigInt(offset); - break; - case wasi.WHENCE_CUR: - calculated_offset = this.position + BigInt(offset); - break; - case wasi.WHENCE_END: - calculated_offset = BigInt(this.file.handle.getSize()) + BigInt(offset); - break; - default: - return { ret: wasi.ERRNO_INVAL, offset: 0n }; - } - if (calculated_offset < 0) { - return { ret: wasi.ERRNO_INVAL, offset: 0n }; - } - this.position = calculated_offset; - return { ret: wasi.ERRNO_SUCCESS, offset: this.position }; - } - - fd_write( - view8: Uint8Array, - iovs: Array, - ): { ret: number; nwritten: number } { - let nwritten = 0; - if (this.file.readonly) return { ret: wasi.ERRNO_BADF, nwritten }; - for (const iovec of iovs) { - const buf = new Uint8Array(view8.buffer, iovec.buf, iovec.buf_len); - // don't need to extend file manually, just write - const n = this.file.handle.write(buf, { at: Number(this.position) }); - this.position += BigInt(n); - nwritten += n; - } - return { ret: wasi.ERRNO_SUCCESS, nwritten }; - } - - fd_datasync(): number { - this.file.handle.flush(); - return wasi.ERRNO_SUCCESS; - } - - fd_sync(): number { - return this.fd_datasync(); - } -} - export class OpenDirectory extends Fd { dir: Directory; @@ -636,18 +528,15 @@ export class PreopenDirectory extends OpenDirectory { } } -// options that can be passed to Files and SyncOPFSFiles -type FileOptions = Partial<{ - readonly: boolean; -}>; - export class File extends Inode { data: Uint8Array; readonly: boolean; constructor( data: ArrayBuffer | SharedArrayBuffer | Uint8Array | Array, - options?: FileOptions, + options?: Partial<{ + readonly: boolean; + }>, ) { super(); this.data = new Uint8Array(data); @@ -683,62 +572,6 @@ export class File extends Inode { } } -// Shim for https://developer.mozilla.org/en-US/docs/Web/API/FileSystemSyncAccessHandle -// This is not part of the public interface. -export interface FileSystemSyncAccessHandle { - close(): void; - flush(): void; - getSize(): number; - read(buffer: ArrayBuffer | ArrayBufferView, options?: { at: number }): number; - truncate(to: number): void; - write( - buffer: ArrayBuffer | ArrayBufferView, - options?: { at: number }, - ): number; -} - -// Synchronous access to an individual file in the origin private file system. -// Only allowed inside a WebWorker. -export class SyncOPFSFile extends Inode { - handle: FileSystemSyncAccessHandle; - readonly: boolean; - - // FIXME needs a close() method to be called after start() to release the underlying handle - constructor(handle: FileSystemSyncAccessHandle, options?: FileOptions) { - super(); - this.handle = handle; - this.readonly = !!options?.readonly; - } - - path_open(oflags: number, fs_rights_base: bigint, fd_flags: number) { - if ( - this.readonly && - (fs_rights_base & BigInt(wasi.RIGHTS_FD_WRITE)) == - BigInt(wasi.RIGHTS_FD_WRITE) - ) { - // no write permission to file - return { ret: wasi.ERRNO_PERM, fd_obj: null }; - } - - if ((oflags & wasi.OFLAGS_TRUNC) == wasi.OFLAGS_TRUNC) { - if (this.readonly) return { ret: wasi.ERRNO_PERM, fd_obj: null }; - this.handle.truncate(0); - } - - const file = new OpenSyncOPFSFile(this); - if (fd_flags & wasi.FDFLAGS_APPEND) file.fd_seek(0n, wasi.WHENCE_END); - return { ret: wasi.ERRNO_SUCCESS, fd_obj: file }; - } - - get size(): bigint { - return BigInt(this.handle.getSize()); - } - - stat(): wasi.Filestat { - return new wasi.Filestat(wasi.FILETYPE_REGULAR_FILE, this.size); - } -} - class Path { parts: string[] = []; is_dir: boolean = false; diff --git a/src/fs_opfs.ts b/src/fs_opfs.ts new file mode 100644 index 0000000..df43876 --- /dev/null +++ b/src/fs_opfs.ts @@ -0,0 +1,171 @@ +import * as wasi from "./wasi_defs.js"; +import { Fd, Inode } from "./fd.js"; + +// Shim for https://developer.mozilla.org/en-US/docs/Web/API/FileSystemSyncAccessHandle +// This is not part of the public interface. +export interface FileSystemSyncAccessHandle { + close(): void; + flush(): void; + getSize(): number; + read(buffer: ArrayBuffer | ArrayBufferView, options?: { at: number }): number; + truncate(to: number): void; + write( + buffer: ArrayBuffer | ArrayBufferView, + options?: { at: number }, + ): number; +} + +// Synchronous access to an individual file in the origin private file system. +// Only allowed inside a WebWorker. +export class SyncOPFSFile extends Inode { + handle: FileSystemSyncAccessHandle; + readonly: boolean; + + // FIXME needs a close() method to be called after start() to release the underlying handle + constructor( + handle: FileSystemSyncAccessHandle, + options?: Partial<{ + readonly: boolean; + }>, + ) { + super(); + this.handle = handle; + this.readonly = !!options?.readonly; + } + + path_open(oflags: number, fs_rights_base: bigint, fd_flags: number) { + if ( + this.readonly && + (fs_rights_base & BigInt(wasi.RIGHTS_FD_WRITE)) == + BigInt(wasi.RIGHTS_FD_WRITE) + ) { + // no write permission to file + return { ret: wasi.ERRNO_PERM, fd_obj: null }; + } + + if ((oflags & wasi.OFLAGS_TRUNC) == wasi.OFLAGS_TRUNC) { + if (this.readonly) return { ret: wasi.ERRNO_PERM, fd_obj: null }; + this.handle.truncate(0); + } + + const file = new OpenSyncOPFSFile(this); + if (fd_flags & wasi.FDFLAGS_APPEND) file.fd_seek(0n, wasi.WHENCE_END); + return { ret: wasi.ERRNO_SUCCESS, fd_obj: file }; + } + + get size(): bigint { + return BigInt(this.handle.getSize()); + } + + stat(): wasi.Filestat { + return new wasi.Filestat(wasi.FILETYPE_REGULAR_FILE, this.size); + } +} + +export class OpenSyncOPFSFile extends Fd { + file: SyncOPFSFile; + position: bigint = 0n; + + constructor(file: SyncOPFSFile) { + super(); + this.file = file; + } + + fd_allocate(offset: bigint, len: bigint): number { + if (BigInt(this.file.handle.getSize()) > offset + len) { + // already big enough + } else { + // extend + this.file.handle.truncate(Number(offset + len)); + } + return wasi.ERRNO_SUCCESS; + } + + fd_fdstat_get(): { ret: number; fdstat: wasi.Fdstat | null } { + return { ret: 0, fdstat: new wasi.Fdstat(wasi.FILETYPE_REGULAR_FILE, 0) }; + } + + fd_filestat_get(): { ret: number; filestat: wasi.Filestat } { + return { + ret: 0, + filestat: new wasi.Filestat( + wasi.FILETYPE_REGULAR_FILE, + BigInt(this.file.handle.getSize()), + ), + }; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + fd_filestat_set_size(size: bigint): number { + this.file.handle.truncate(Number(size)); + return wasi.ERRNO_SUCCESS; + } + + fd_read( + view8: Uint8Array, + iovs: Array, + ): { ret: number; nread: number } { + let nread = 0; + for (const iovec of iovs) { + if (this.position < this.file.handle.getSize()) { + const buf = new Uint8Array(view8.buffer, iovec.buf, iovec.buf_len); + const n = this.file.handle.read(buf, { at: Number(this.position) }); + this.position += BigInt(n); + nread += n; + } else { + break; + } + } + return { ret: 0, nread }; + } + + fd_seek( + offset: number | bigint, + whence: number, + ): { ret: number; offset: bigint } { + let calculated_offset: bigint; + switch (whence) { + case wasi.WHENCE_SET: + calculated_offset = BigInt(offset); + break; + case wasi.WHENCE_CUR: + calculated_offset = this.position + BigInt(offset); + break; + case wasi.WHENCE_END: + calculated_offset = BigInt(this.file.handle.getSize()) + BigInt(offset); + break; + default: + return { ret: wasi.ERRNO_INVAL, offset: 0n }; + } + if (calculated_offset < 0) { + return { ret: wasi.ERRNO_INVAL, offset: 0n }; + } + this.position = calculated_offset; + return { ret: wasi.ERRNO_SUCCESS, offset: this.position }; + } + + fd_write( + view8: Uint8Array, + iovs: Array, + ): { ret: number; nwritten: number } { + let nwritten = 0; + if (this.file.readonly) return { ret: wasi.ERRNO_BADF, nwritten }; + for (const iovec of iovs) { + const buf = new Uint8Array(view8.buffer, iovec.buf, iovec.buf_len); + // don't need to extend file manually, just write + const n = this.file.handle.write(buf, { at: Number(this.position) }); + this.position += BigInt(n); + nwritten += n; + } + return { ret: wasi.ERRNO_SUCCESS, nwritten }; + } + + fd_datasync(): number { + this.file.handle.flush(); + return wasi.ERRNO_SUCCESS; + } + + fd_sync(): number { + return this.fd_datasync(); + } +} diff --git a/src/index.ts b/src/index.ts index 272076b..4eb1193 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,13 +4,12 @@ export { WASI, WASIProcExit }; export { Fd, Inode } from "./fd.js"; export { File, - SyncOPFSFile, Directory, OpenFile, OpenDirectory, - OpenSyncOPFSFile, PreopenDirectory, ConsoleStdout, } from "./fs_core.js"; +export { SyncOPFSFile, OpenSyncOPFSFile } from "./fs_opfs.js"; export { strace } from "./strace.js"; export * as wasi from "./wasi_defs.js"; From a510ef91ac907a5e53204e06d1af098e77c8aba8 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Sun, 24 Dec 2023 20:39:54 +0100 Subject: [PATCH 12/19] Rename fs_core.ts to fs_mem.ts --- src/{fs_core.ts => fs_mem.ts} | 0 src/index.ts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename src/{fs_core.ts => fs_mem.ts} (100%) diff --git a/src/fs_core.ts b/src/fs_mem.ts similarity index 100% rename from src/fs_core.ts rename to src/fs_mem.ts diff --git a/src/index.ts b/src/index.ts index 4eb1193..5f80030 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,7 +9,7 @@ export { OpenDirectory, PreopenDirectory, ConsoleStdout, -} from "./fs_core.js"; +} from "./fs_mem.js"; export { SyncOPFSFile, OpenSyncOPFSFile } from "./fs_opfs.js"; export { strace } from "./strace.js"; export * as wasi from "./wasi_defs.js"; From 647ce67cf252c72ee1463be602128e8d6d5d434d Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Sat, 20 Jan 2024 14:27:40 +0100 Subject: [PATCH 13/19] Bump to v0.3.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7cb8f6d..6891208 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bjorn3/browser_wasi_shim", - "version": "0.2.21", + "version": "0.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bjorn3/browser_wasi_shim", - "version": "0.2.21", + "version": "0.3.0", "license": "MIT OR Apache-2.0", "devDependencies": { "@swc/cli": "^0.1.62", diff --git a/package.json b/package.json index 433fcda..bab05a6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bjorn3/browser_wasi_shim", - "version": "0.2.21", + "version": "0.3.0", "license": "MIT OR Apache-2.0", "description": "A pure javascript shim for WASI", "type": "module", From 434f20eabb8cb0ee8ac5b9d088c3bc310383cd06 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Tue, 9 Apr 2024 18:21:01 +0200 Subject: [PATCH 14/19] Pass Uint8Array from/to fd_read/fd_write This way individual Fd implementations don't have to handle vectorized reads/writes and don't need direct access to the wasm linear memory. --- examples/rustc.html | 12 +-- src/fd.ts | 23 ++--- src/fs_mem.ts | 162 +++++++++----------------------- src/fs_opfs.ts | 42 +++------ src/wasi.ts | 74 ++++++++++++--- test/adapters/node/run-wasi.mjs | 11 +-- 6 files changed, 134 insertions(+), 190 deletions(-) diff --git a/examples/rustc.html b/examples/rustc.html index b6cfe1f..3e46b63 100644 --- a/examples/rustc.html +++ b/examples/rustc.html @@ -47,15 +47,11 @@ super(); this.term = term; } - fd_write(view8/*: Uint8Array*/, iovs/*: [wasi.Iovec]*/)/*: {ret: number, nwritten: number}*/ { + fd_write(data/*: Uint8Array*/)/*: {ret: number, nwritten: number}*/ { let nwritten = 0; - for (let iovec of iovs) { - console.log(iovec.buf_len, iovec.buf_len, view8.slice(iovec.buf, iovec.buf + iovec.buf_len)); - let buffer = view8.slice(iovec.buf, iovec.buf + iovec.buf_len); - this.term.writeUtf8(buffer); - nwritten += iovec.buf_len; - } - return { ret: 0, nwritten }; + console.log(data); + this.term.writeUtf8(data); + return { ret: 0, nwritten: data.byteLength }; } } diff --git a/src/fd.ts b/src/fd.ts index b192ce9..2227ab6 100644 --- a/src/fd.ts +++ b/src/fd.ts @@ -35,12 +35,8 @@ export abstract class Fd { fd_filestat_set_times(atim: bigint, mtim: bigint, fst_flags: number): number { return wasi.ERRNO_NOTSUP; } - fd_pread( - view8: Uint8Array, - iovs: Array, - offset: bigint, - ): { ret: number; nread: number } { - return { ret: wasi.ERRNO_NOTSUP, nread: 0 }; + fd_pread(size: number, offset: bigint): { ret: number; data: Uint8Array } { + return { ret: wasi.ERRNO_NOTSUP, data: new Uint8Array() }; } fd_prestat_get(): { ret: number; prestat: wasi.Prestat | null } { return { ret: wasi.ERRNO_NOTSUP, prestat: null }; @@ -49,17 +45,13 @@ export abstract class Fd { return { ret: wasi.ERRNO_NOTSUP, prestat_dir_name: null }; } fd_pwrite( - view8: Uint8Array, - iovs: Array, + data: Uint8Array, offset: bigint, ): { ret: number; nwritten: number } { return { ret: wasi.ERRNO_NOTSUP, nwritten: 0 }; } - fd_read( - view8: Uint8Array, - iovs: Array, - ): { ret: number; nread: number } { - return { ret: wasi.ERRNO_NOTSUP, nread: 0 }; + fd_read(size: number): { ret: number; data: Uint8Array } { + return { ret: wasi.ERRNO_NOTSUP, data: new Uint8Array() }; } fd_readdir_single(cookie: bigint): { ret: number; @@ -76,10 +68,7 @@ export abstract class Fd { fd_tell(): { ret: number; offset: bigint } { return { ret: wasi.ERRNO_NOTSUP, offset: 0n }; } - fd_write( - view8: Uint8Array, - iovs: Array, - ): { ret: number; nwritten: number } { + fd_write(data: Uint8Array): { ret: number; nwritten: number } { return { ret: wasi.ERRNO_NOTSUP, nwritten: 0 }; } path_create_directory(path: string): number { diff --git a/src/fs_mem.ts b/src/fs_mem.ts index 4cea8f0..d089bb6 100644 --- a/src/fs_mem.ts +++ b/src/fs_mem.ts @@ -42,47 +42,21 @@ export class OpenFile extends Fd { return wasi.ERRNO_SUCCESS; } - fd_read( - view8: Uint8Array, - iovs: Array, - ): { ret: number; nread: number } { - let nread = 0; - for (const iovec of iovs) { - if (this.file_pos < this.file.data.byteLength) { - const slice = this.file.data.slice( - Number(this.file_pos), - Number(this.file_pos + BigInt(iovec.buf_len)), - ); - view8.set(slice, iovec.buf); - this.file_pos += BigInt(slice.length); - nread += slice.length; - } else { - break; - } - } - return { ret: 0, nread }; + fd_read(size: number): { ret: number; data: Uint8Array } { + const slice = this.file.data.slice( + Number(this.file_pos), + Number(this.file_pos + BigInt(size)), + ); + this.file_pos += BigInt(slice.length); + return { ret: 0, data: slice }; } - fd_pread( - view8: Uint8Array, - iovs: Array, - offset: bigint, - ): { ret: number; nread: number } { - let nread = 0; - for (const iovec of iovs) { - if (offset < this.file.data.byteLength) { - const slice = this.file.data.slice( - Number(offset), - Number(offset + BigInt(iovec.buf_len)), - ); - view8.set(slice, iovec.buf); - offset += BigInt(slice.length); - nread += slice.length; - } else { - break; - } - } - return { ret: 0, nread }; + fd_pread(size: number, offset: bigint): { ret: number; data: Uint8Array } { + const slice = this.file.data.slice( + Number(offset), + Number(offset + BigInt(size)), + ); + return { ret: 0, data: slice }; } fd_seek(offset: bigint, whence: number): { ret: number; offset: bigint } { @@ -113,51 +87,33 @@ export class OpenFile extends Fd { return { ret: 0, offset: this.file_pos }; } - fd_write( - view8: Uint8Array, - iovs: Array, - ): { ret: number; nwritten: number } { - let nwritten = 0; - if (this.file.readonly) return { ret: wasi.ERRNO_BADF, nwritten }; - for (const iovec of iovs) { - const buffer = view8.slice(iovec.buf, iovec.buf + iovec.buf_len); - if (this.file_pos + BigInt(buffer.byteLength) > this.file.size) { - const old = this.file.data; - this.file.data = new Uint8Array( - Number(this.file_pos + BigInt(buffer.byteLength)), - ); - this.file.data.set(old); - } - this.file.data.set( - buffer.slice(0, Number(this.file.size - this.file_pos)), - Number(this.file_pos), + fd_write(data: Uint8Array): { ret: number; nwritten: number } { + if (this.file.readonly) return { ret: wasi.ERRNO_BADF, nwritten: 0 }; + + if (this.file_pos + BigInt(data.byteLength) > this.file.size) { + const old = this.file.data; + this.file.data = new Uint8Array( + Number(this.file_pos + BigInt(data.byteLength)), ); - this.file_pos += BigInt(buffer.byteLength); - nwritten += iovec.buf_len; + this.file.data.set(old); } - return { ret: 0, nwritten }; + + this.file.data.set(data, Number(this.file_pos)); + this.file_pos += BigInt(data.byteLength); + return { ret: 0, nwritten: data.byteLength }; } - fd_pwrite(view8: Uint8Array, iovs: Array, offset: bigint) { - let nwritten = 0; - if (this.file.readonly) return { ret: wasi.ERRNO_BADF, nwritten }; - for (const iovec of iovs) { - const buffer = view8.slice(iovec.buf, iovec.buf + iovec.buf_len); - if (offset + BigInt(buffer.byteLength) > this.file.size) { - const old = this.file.data; - this.file.data = new Uint8Array( - Number(offset + BigInt(buffer.byteLength)), - ); - this.file.data.set(old); - } - this.file.data.set( - buffer.slice(0, Number(this.file.size - offset)), - Number(offset), - ); - offset += BigInt(buffer.byteLength); - nwritten += iovec.buf_len; + fd_pwrite(data: Uint8Array, offset: bigint) { + if (this.file.readonly) return { ret: wasi.ERRNO_BADF, nwritten: 0 }; + + if (offset + BigInt(data.byteLength) > this.file.size) { + const old = this.file.data; + this.file.data = new Uint8Array(Number(offset + BigInt(data.byteLength))); + this.file.data.set(old); } - return { ret: 0, nwritten }; + + this.file.data.set(data, Number(offset)); + return { ret: 0, nwritten: data.byteLength }; } fd_filestat_get(): { ret: number; filestat: wasi.Filestat } { @@ -464,40 +420,24 @@ export class OpenDirectory extends Fd { return wasi.ERRNO_BADF; } - fd_read( - // eslint-disable-next-line @typescript-eslint/no-unused-vars - view8: Uint8Array, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - iovs: wasi.Iovec[], - ): { ret: number; nread: number } { - return { ret: wasi.ERRNO_BADF, nread: 0 }; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + fd_read(size: number): { ret: number; data: Uint8Array } { + return { ret: wasi.ERRNO_BADF, data: new Uint8Array() }; } - fd_pread( - // eslint-disable-next-line @typescript-eslint/no-unused-vars - view8: Uint8Array, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - iovs: wasi.Iovec[], - // eslint-disable-next-line @typescript-eslint/no-unused-vars - offset: bigint, - ): { ret: number; nread: number } { - return { ret: wasi.ERRNO_BADF, nread: 0 }; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + fd_pread(size: number, offset: bigint): { ret: number; data: Uint8Array } { + return { ret: wasi.ERRNO_BADF, data: new Uint8Array() }; } - fd_write( - // eslint-disable-next-line @typescript-eslint/no-unused-vars - view8: Uint8Array, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - iovs: wasi.Ciovec[], - ): { ret: number; nwritten: number } { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + fd_write(data: Uint8Array): { ret: number; nwritten: number } { return { ret: wasi.ERRNO_BADF, nwritten: 0 }; } fd_pwrite( // eslint-disable-next-line @typescript-eslint/no-unused-vars - view8: Uint8Array, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - iovs: wasi.Ciovec[], + data: Uint8Array, // eslint-disable-next-line @typescript-eslint/no-unused-vars offset: bigint, ): { ret: number; nwritten: number } { @@ -785,17 +725,9 @@ export class ConsoleStdout extends Fd { return { ret: 0, fdstat }; } - fd_write( - view8: Uint8Array, - iovs: Array, - ): { ret: number; nwritten: number } { - let nwritten = 0; - for (const iovec of iovs) { - const buffer = view8.slice(iovec.buf, iovec.buf + iovec.buf_len); - this.write(buffer); - nwritten += iovec.buf_len; - } - return { ret: 0, nwritten }; + fd_write(data: Uint8Array): { ret: number; nwritten: number } { + this.write(data); + return { ret: 0, nwritten: data.byteLength }; } static lineBuffered(write: (line: string) => void): ConsoleStdout { diff --git a/src/fs_opfs.ts b/src/fs_opfs.ts index df43876..d1fe42a 100644 --- a/src/fs_opfs.ts +++ b/src/fs_opfs.ts @@ -101,22 +101,11 @@ export class OpenSyncOPFSFile extends Fd { return wasi.ERRNO_SUCCESS; } - fd_read( - view8: Uint8Array, - iovs: Array, - ): { ret: number; nread: number } { - let nread = 0; - for (const iovec of iovs) { - if (this.position < this.file.handle.getSize()) { - const buf = new Uint8Array(view8.buffer, iovec.buf, iovec.buf_len); - const n = this.file.handle.read(buf, { at: Number(this.position) }); - this.position += BigInt(n); - nread += n; - } else { - break; - } - } - return { ret: 0, nread }; + fd_read(size: number): { ret: number; data: Uint8Array } { + const buf = new Uint8Array(size); + const n = this.file.handle.read(buf, { at: Number(this.position) }); + this.position += BigInt(n); + return { ret: 0, data: buf.slice(0, n) }; } fd_seek( @@ -144,20 +133,13 @@ export class OpenSyncOPFSFile extends Fd { return { ret: wasi.ERRNO_SUCCESS, offset: this.position }; } - fd_write( - view8: Uint8Array, - iovs: Array, - ): { ret: number; nwritten: number } { - let nwritten = 0; - if (this.file.readonly) return { ret: wasi.ERRNO_BADF, nwritten }; - for (const iovec of iovs) { - const buf = new Uint8Array(view8.buffer, iovec.buf, iovec.buf_len); - // don't need to extend file manually, just write - const n = this.file.handle.write(buf, { at: Number(this.position) }); - this.position += BigInt(n); - nwritten += n; - } - return { ret: wasi.ERRNO_SUCCESS, nwritten }; + fd_write(data: Uint8Array): { ret: number; nwritten: number } { + if (this.file.readonly) return { ret: wasi.ERRNO_BADF, nwritten: 0 }; + + // don't need to extend file manually, just write + const n = this.file.handle.write(data, { at: Number(this.position) }); + this.position += BigInt(n); + return { ret: wasi.ERRNO_SUCCESS, nwritten: n }; } fd_datasync(): number { diff --git a/src/wasi.ts b/src/wasi.ts index 9da80f9..9c906b0 100644 --- a/src/wasi.ts +++ b/src/wasi.ts @@ -300,9 +300,22 @@ export default class WASI { iovs_ptr, iovs_len, ); - const { ret, nread } = self.fds[fd].fd_pread(buffer8, iovecs, offset); + let nread = 0; + for (const iovec of iovecs) { + const { ret, data } = self.fds[fd].fd_pread(iovec.buf_len, offset); + if (ret != wasi.ERRNO_SUCCESS) { + buffer.setUint32(nread_ptr, nread, true); + return ret; + } + buffer8.set(data, iovec.buf); + nread += data.length; + offset += BigInt(data.length); + if (data.length != iovec.buf_len) { + break; + } + } buffer.setUint32(nread_ptr, nread, true); - return ret; + return wasi.ERRNO_SUCCESS; } else { return wasi.ERRNO_BADF; } @@ -358,13 +371,25 @@ export default class WASI { iovs_ptr, iovs_len, ); - const { ret, nwritten } = self.fds[fd].fd_pwrite( - buffer8, - iovecs, - offset, - ); + let nwritten = 0; + for (const iovec of iovecs) { + const data = buffer8.slice(iovec.buf, iovec.buf + iovec.buf_len); + const { ret, nwritten: nwritten_part } = self.fds[fd].fd_pwrite( + data, + offset, + ); + if (ret != wasi.ERRNO_SUCCESS) { + buffer.setUint32(nwritten_ptr, nwritten, true); + return ret; + } + nwritten += nwritten_part; + offset += BigInt(nwritten_part); + if (nwritten_part != data.byteLength) { + break; + } + } buffer.setUint32(nwritten_ptr, nwritten, true); - return ret; + return wasi.ERRNO_SUCCESS; } else { return wasi.ERRNO_BADF; } @@ -383,9 +408,21 @@ export default class WASI { iovs_ptr, iovs_len, ); - const { ret, nread } = self.fds[fd].fd_read(buffer8, iovecs); + let nread = 0; + for (const iovec of iovecs) { + const { ret, data } = self.fds[fd].fd_read(iovec.buf_len); + if (ret != wasi.ERRNO_SUCCESS) { + buffer.setUint32(nread_ptr, nread, true); + return ret; + } + buffer8.set(data, iovec.buf); + nread += data.length; + if (data.length != iovec.buf_len) { + break; + } + } buffer.setUint32(nread_ptr, nread, true); - return ret; + return wasi.ERRNO_SUCCESS; } else { return wasi.ERRNO_BADF; } @@ -510,9 +547,22 @@ export default class WASI { iovs_ptr, iovs_len, ); - const { ret, nwritten } = self.fds[fd].fd_write(buffer8, iovecs); + let nwritten = 0; + for (const iovec of iovecs) { + const data = buffer8.slice(iovec.buf, iovec.buf + iovec.buf_len); + const { ret, nwritten: nwritten_part } = + self.fds[fd].fd_write(data); + if (ret != wasi.ERRNO_SUCCESS) { + buffer.setUint32(nwritten_ptr, nwritten, true); + return ret; + } + nwritten += nwritten_part; + if (nwritten_part != data.byteLength) { + break; + } + } buffer.setUint32(nwritten_ptr, nwritten, true); - return ret; + return wasi.ERRNO_SUCCESS; } else { return wasi.ERRNO_BADF; } diff --git a/test/adapters/node/run-wasi.mjs b/test/adapters/node/run-wasi.mjs index ac31367..4a82d0d 100644 --- a/test/adapters/node/run-wasi.mjs +++ b/test/adapters/node/run-wasi.mjs @@ -25,14 +25,9 @@ class NodeStdout extends Fd { return { ret: 0, fdstat }; } - fd_write(view8, iovs) { - let nwritten = 0; - for (let iovec of iovs) { - let buffer = view8.slice(iovec.buf, iovec.buf + iovec.buf_len); - this.out.write(buffer); - nwritten += iovec.buf_len; - } - return { ret: 0, nwritten }; + fd_write(data) { + this.out.write(data); + return { ret: 0, nwritten: data.byteLength }; } } From e03d88ae4f6cb430778d473a11eabdecc8b867c0 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Tue, 9 Apr 2024 18:27:17 +0200 Subject: [PATCH 15/19] Move UTF-8 encoding of prestat dir name to wasi.ts --- src/fd.ts | 2 +- src/fs_mem.ts | 6 +++--- src/wasi.ts | 7 +++++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/fd.ts b/src/fd.ts index 2227ab6..65c1465 100644 --- a/src/fd.ts +++ b/src/fd.ts @@ -41,7 +41,7 @@ export abstract class Fd { fd_prestat_get(): { ret: number; prestat: wasi.Prestat | null } { return { ret: wasi.ERRNO_NOTSUP, prestat: null }; } - fd_prestat_dir_name(): { ret: number; prestat_dir_name: Uint8Array | null } { + fd_prestat_dir_name(): { ret: number; prestat_dir_name: string | null } { return { ret: wasi.ERRNO_NOTSUP, prestat_dir_name: null }; } fd_pwrite( diff --git a/src/fs_mem.ts b/src/fs_mem.ts index d089bb6..5735918 100644 --- a/src/fs_mem.ts +++ b/src/fs_mem.ts @@ -446,11 +446,11 @@ export class OpenDirectory extends Fd { } export class PreopenDirectory extends OpenDirectory { - prestat_name: Uint8Array; + prestat_name: string; constructor(name: string, contents: Map) { super(new Directory(contents)); - this.prestat_name = new TextEncoder().encode(name); + this.prestat_name = name; } fd_prestat_get(): { ret: number; prestat: wasi.Prestat | null } { @@ -460,7 +460,7 @@ export class PreopenDirectory extends OpenDirectory { }; } - fd_prestat_dir_name(): { ret: number; prestat_dir_name: Uint8Array } { + fd_prestat_dir_name(): { ret: number; prestat_dir_name: string } { return { ret: 0, prestat_dir_name: this.prestat_name, diff --git a/src/wasi.ts b/src/wasi.ts index 9c906b0..4af8e1a 100644 --- a/src/wasi.ts +++ b/src/wasi.ts @@ -345,11 +345,14 @@ export default class WASI { if (prestat_dir_name == null) { return ret; } + const encoded_prestat_dir_name = new TextEncoder().encode( + prestat_dir_name, + ); const buffer8 = new Uint8Array(self.inst.exports.memory.buffer); - buffer8.set(prestat_dir_name.slice(0, path_len), path_ptr); + buffer8.set(encoded_prestat_dir_name.slice(0, path_len), path_ptr); - return prestat_dir_name.byteLength > path_len + return encoded_prestat_dir_name.byteLength > path_len ? wasi.ERRNO_NAMETOOLONG : wasi.ERRNO_SUCCESS; } else { From a5d4136da620f253723bfabbd70b27402b580096 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Tue, 9 Apr 2024 18:35:09 +0200 Subject: [PATCH 16/19] Less verbose exception message in rustc.html --- examples/rustc.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/rustc.html b/examples/rustc.html index 3e46b63..902e2ec 100644 --- a/examples/rustc.html +++ b/examples/rustc.html @@ -131,7 +131,7 @@ }); term.writeln("\x1B[93mExecuting\x1B[0m"); console.log(inst.exports); - try { w.start(inst); } catch(e) { term.writeln("Exception: " + e.message); term.writeln("backtrace:"); term.writeln(e.stack); throw e; } + try { w.start(inst); } catch(e) { term.writeln("Exception: " + e.message); /*term.writeln("backtrace:"); term.writeln(e.stack);*/ } term.writeln("\x1B[92mDone\x1B[0m"); console.log(fds); From d224e9dff26150cafd681cdb0aef7763109aab28 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Tue, 9 Apr 2024 18:45:07 +0200 Subject: [PATCH 17/19] Add missing BADF return in path_create_directory --- src/wasi.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/wasi.ts b/src/wasi.ts index 4af8e1a..9309a05 100644 --- a/src/wasi.ts +++ b/src/wasi.ts @@ -581,6 +581,8 @@ export default class WASI { buffer8.slice(path_ptr, path_ptr + path_len), ); return self.fds[fd].path_create_directory(path); + } else { + return wasi.ERRNO_BADF; } }, path_filestat_get( From bbdfeeee112051f28a23f2e7dfc2b2c29c117504 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Tue, 9 Apr 2024 18:54:17 +0200 Subject: [PATCH 18/19] Remove a couple of methods from Fd Either their signature will likely change once they are actually implemented in one of the Fd impls or they are not strictly necessary to exist. --- src/fd.ts | 9 --------- src/fs_opfs.ts | 6 +----- src/wasi.ts | 11 ++++++++--- 3 files changed, 9 insertions(+), 17 deletions(-) diff --git a/src/fd.ts b/src/fd.ts index 65c1465..658565a 100644 --- a/src/fd.ts +++ b/src/fd.ts @@ -2,18 +2,12 @@ import * as wasi from "./wasi_defs.js"; export abstract class Fd { - fd_advise(offset: bigint, len: bigint, advice: number): number { - return wasi.ERRNO_SUCCESS; - } fd_allocate(offset: bigint, len: bigint): number { return wasi.ERRNO_NOTSUP; } fd_close(): number { return 0; } - fd_datasync(): number { - return wasi.ERRNO_NOTSUP; - } fd_fdstat_get(): { ret: number; fdstat: wasi.Fdstat | null } { return { ret: wasi.ERRNO_NOTSUP, fdstat: null }; } @@ -120,9 +114,6 @@ export abstract class Fd { path_rename(old_path: string, new_fd: number, new_path: string): number { return wasi.ERRNO_NOTSUP; } - path_symlink(old_path: string, new_path: string): number { - return wasi.ERRNO_NOTSUP; - } path_unlink_file(path: string): number { return wasi.ERRNO_NOTSUP; } diff --git a/src/fs_opfs.ts b/src/fs_opfs.ts index d1fe42a..7cdde8d 100644 --- a/src/fs_opfs.ts +++ b/src/fs_opfs.ts @@ -142,12 +142,8 @@ export class OpenSyncOPFSFile extends Fd { return { ret: wasi.ERRNO_SUCCESS, nwritten: n }; } - fd_datasync(): number { + fd_sync(): number { this.file.handle.flush(); return wasi.ERRNO_SUCCESS; } - - fd_sync(): number { - return this.fd_datasync(); - } } diff --git a/src/wasi.ts b/src/wasi.ts index 9309a05..c8aaa4e 100644 --- a/src/wasi.ts +++ b/src/wasi.ts @@ -184,12 +184,15 @@ export default class WASI { fd_advise( fd: number, + // eslint-disable-next-line @typescript-eslint/no-unused-vars offset: bigint, + // eslint-disable-next-line @typescript-eslint/no-unused-vars len: bigint, + // eslint-disable-next-line @typescript-eslint/no-unused-vars advice: number, ): number { if (self.fds[fd] != undefined) { - return self.fds[fd].fd_advise(offset, len, advice); + return wasi.ERRNO_SUCCESS; } else { return wasi.ERRNO_BADF; } @@ -212,7 +215,7 @@ export default class WASI { }, fd_datasync(fd: number): number { if (self.fds[fd] != undefined) { - return self.fds[fd].fd_datasync(); + return self.fds[fd].fd_sync(); } else { return wasi.ERRNO_BADF; } @@ -788,13 +791,15 @@ export default class WASI { ): number { const buffer8 = new Uint8Array(self.inst.exports.memory.buffer); if (self.fds[fd] != undefined) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars const old_path = new TextDecoder("utf-8").decode( buffer8.slice(old_path_ptr, old_path_ptr + old_path_len), ); + // eslint-disable-next-line @typescript-eslint/no-unused-vars const new_path = new TextDecoder("utf-8").decode( buffer8.slice(new_path_ptr, new_path_ptr + new_path_len), ); - return self.fds[fd].path_symlink(old_path, new_path); + return wasi.ERRNO_NOTSUP; } else { return wasi.ERRNO_BADF; } From 33243db33da2542290ccd5883088831019cec9bf Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Tue, 9 Apr 2024 18:57:45 +0200 Subject: [PATCH 19/19] Fuse fd_prestat_dir_name into fd_prestat_get --- src/fd.ts | 3 --- src/fs_mem.ts | 9 +-------- src/wasi.ts | 12 +++++------- src/wasi_defs.ts | 12 ++++++------ 4 files changed, 12 insertions(+), 24 deletions(-) diff --git a/src/fd.ts b/src/fd.ts index 658565a..4e3b6f5 100644 --- a/src/fd.ts +++ b/src/fd.ts @@ -35,9 +35,6 @@ export abstract class Fd { fd_prestat_get(): { ret: number; prestat: wasi.Prestat | null } { return { ret: wasi.ERRNO_NOTSUP, prestat: null }; } - fd_prestat_dir_name(): { ret: number; prestat_dir_name: string | null } { - return { ret: wasi.ERRNO_NOTSUP, prestat_dir_name: null }; - } fd_pwrite( data: Uint8Array, offset: bigint, diff --git a/src/fs_mem.ts b/src/fs_mem.ts index 5735918..6f540ee 100644 --- a/src/fs_mem.ts +++ b/src/fs_mem.ts @@ -456,14 +456,7 @@ export class PreopenDirectory extends OpenDirectory { fd_prestat_get(): { ret: number; prestat: wasi.Prestat | null } { return { ret: 0, - prestat: wasi.Prestat.dir(this.prestat_name.length), - }; - } - - fd_prestat_dir_name(): { ret: number; prestat_dir_name: string } { - return { - ret: 0, - prestat_dir_name: this.prestat_name, + prestat: wasi.Prestat.dir(this.prestat_name), }; } } diff --git a/src/wasi.ts b/src/wasi.ts index c8aaa4e..a3e4439 100644 --- a/src/wasi.ts +++ b/src/wasi.ts @@ -344,18 +344,16 @@ export default class WASI { ): number { // FIXME don't ignore path_len if (self.fds[fd] != undefined) { - const { ret, prestat_dir_name } = self.fds[fd].fd_prestat_dir_name(); - if (prestat_dir_name == null) { + const { ret, prestat } = self.fds[fd].fd_prestat_get(); + if (prestat == null) { return ret; } - const encoded_prestat_dir_name = new TextEncoder().encode( - prestat_dir_name, - ); + const prestat_dir_name = prestat.inner.pr_name; const buffer8 = new Uint8Array(self.inst.exports.memory.buffer); - buffer8.set(encoded_prestat_dir_name.slice(0, path_len), path_ptr); + buffer8.set(prestat_dir_name.slice(0, path_len), path_ptr); - return encoded_prestat_dir_name.byteLength > path_len + return prestat_dir_name.byteLength > path_len ? wasi.ERRNO_NAMETOOLONG : wasi.ERRNO_SUCCESS; } else { diff --git a/src/wasi_defs.ts b/src/wasi_defs.ts index b41aed1..db0b37c 100644 --- a/src/wasi_defs.ts +++ b/src/wasi_defs.ts @@ -341,14 +341,14 @@ export const SDFLAGS_WR = 1 << 1; export const PREOPENTYPE_DIR = 0; export class PrestatDir { - pr_name_len: number; + pr_name: Uint8Array; - constructor(name_len: number) { - this.pr_name_len = name_len; + constructor(name: string) { + this.pr_name = new TextEncoder().encode(name); } write_bytes(view: DataView, ptr: number) { - view.setUint32(ptr, this.pr_name_len, true); + view.setUint32(ptr, this.pr_name.byteLength, true); } } @@ -358,10 +358,10 @@ export class Prestat { //@ts-ignore strictPropertyInitialization inner: PrestatDir; - static dir(name_len: number): Prestat { + static dir(name: string): Prestat { const prestat = new Prestat(); prestat.tag = PREOPENTYPE_DIR; - prestat.inner = new PrestatDir(name_len); + prestat.inner = new PrestatDir(name); return prestat; }