Skip to content

Commit

Permalink
Merge pull request bjorn3#34 from ansemjo/opfs-sync-file
Browse files Browse the repository at this point in the history
Add support for files in OPFS
  • Loading branch information
bjorn3 authored Jun 30, 2023
2 parents ee4f287 + ba73046 commit 8056e62
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 17 deletions.
56 changes: 52 additions & 4 deletions src/fs_core.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { OpenDirectory, OpenFile, OpenSyncOPFSFile } from "./fs_fd.js";
import * as wasi from "./wasi_defs.js";

export class File {
Expand All @@ -8,6 +9,12 @@ export class File {
this.data = new Uint8Array(data);
}

open(fd_flags: number) {
let file = new OpenFile(this);
if (fd_flags & wasi.FDFLAGS_APPEND) file.fd_seek(0n, wasi.WHENCE_END);
return file;
}

get size(): bigint {
return BigInt(this.data.byteLength);
}
Expand All @@ -21,19 +28,60 @@ export class File {
}
}

// 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 {
// FIXME needs a close() method to be called after start() to release the underlying handle
constructor(public handle: FileSystemSyncAccessHandle) { }

open(fd_flags: number) {
let file = new OpenSyncOPFSFile(this);
if (fd_flags & wasi.FDFLAGS_APPEND) file.fd_seek(0n, wasi.WHENCE_END);
return file;
}

get size(): bigint {
return BigInt(this.handle.getSize());
}

stat(): wasi.Filestat {
return new wasi.Filestat(wasi.FILETYPE_REGULAR_FILE, this.size);
}

truncate() {
return this.handle.truncate(0);
}

}

export class Directory {
contents: { [key: string]: File | Directory };
contents: { [key: string]: File | Directory | SyncOPFSFile };

constructor(contents: { [key: string]: File | Directory }) {
constructor(contents: { [key: string]: File | Directory | SyncOPFSFile }) {
this.contents = contents;
}

open(fd_flags: number) {
return new OpenDirectory(this);
}

stat(): wasi.Filestat {
return new wasi.Filestat(wasi.FILETYPE_DIRECTORY, 0n);
}

get_entry_for_path(path: string): File | Directory | null {
let entry: File | Directory = this;
get_entry_for_path(path: string): File | Directory | SyncOPFSFile | null {
let entry: File | Directory | SyncOPFSFile = this;
for (let component of path.split("/")) {
if (component == "") break;
if (component == ".") continue;
Expand Down
95 changes: 84 additions & 11 deletions src/fs_fd.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as wasi from "./wasi_defs.js";
import { File, Directory } from "./fs_core.js";
import { File, Directory, SyncOPFSFile, FileSystemSyncAccessHandle } from "./fs_core.js";
import { Fd } from "./fd.js";

declare var TextEncoder: {
Expand Down Expand Up @@ -102,6 +102,87 @@ export class OpenFile extends Fd {
}
}

export class OpenSyncOPFSFile extends Fd {
handle: FileSystemSyncAccessHandle;
position: bigint = 0n;

constructor(file: SyncOPFSFile) {
super();
this.handle = file.handle;
};

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.handle.getSize())) };
}

fd_read(view8: Uint8Array, iovs: Array<wasi.Iovec>): { ret: number, nread: number } {
let nread = 0;
for (let iovec of iovs) {
if (this.position < this.handle.getSize()) {
let buf = new Uint8Array(view8.buffer, iovec.buf, iovec.buf_len);
let n = this.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.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<wasi.Iovec>): { ret: number, nwritten: number } {
let nwritten = 0;
for (let iovec of iovs) {
let buf = new Uint8Array(view8.buffer, iovec.buf, iovec.buf_len);
// don't need to extend file manually, just write
let n = this.handle.write(buf, { at: Number(this.position) });
this.position += BigInt(n);
nwritten += n;
}
return { ret: wasi.ERRNO_SUCCESS, nwritten };
}

fd_datasync(): number {
this.handle.flush();
return wasi.ERRNO_SUCCESS;
}

fd_sync(): number {
return this.fd_datasync();
}

fd_close(): number {
return wasi.ERRNO_SUCCESS;
}

}

export class OpenDirectory extends Fd {
dir: Directory;

Expand Down Expand Up @@ -174,15 +255,7 @@ export class OpenDirectory extends Fd {
// @ts-ignore
entry.truncate();
}
// FIXME handle this more elegantly
if (entry instanceof File) {
// @ts-ignore
return { ret: 0, fd_obj: new OpenFile(entry) };
} else if (entry instanceof Directory) {
return { ret: 0, fd_obj: new OpenDirectory(entry) };
} else {
throw "dir entry neither file nor dir";
}
return { ret: 0, fd_obj: entry.open(fd_flags) };
}

path_create_directory(path: string): number {
Expand All @@ -193,7 +266,7 @@ export class OpenDirectory extends Fd {
export class PreopenDirectory extends OpenDirectory {
prestat_name: Uint8Array;

constructor(name: string, contents: { [key: string]: File | Directory }) {
constructor(name: string, contents: { [key: string]: File | Directory | SyncOPFSFile }) {
super(new Directory(contents));
this.prestat_name = new TextEncoder("utf-8").encode(name);
}
Expand Down
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ import WASI from "./wasi.js";
export { WASI };

export { Fd } from './fd.js';
export { File, Directory } from "./fs_core.js";
export { OpenFile, OpenDirectory, PreopenDirectory } from "./fs_fd.js";
export { File, SyncOPFSFile, Directory } from "./fs_core.js";
export { OpenFile, OpenDirectory, OpenSyncOPFSFile, PreopenDirectory } from "./fs_fd.js";
export { strace } from "./strace.js";
1 change: 1 addition & 0 deletions src/wasi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export default class WASI {

/// Start a WASI command
start(instance: {
// FIXME v0.3: close opened Fds after execution
exports: { memory: WebAssembly.Memory; _start: () => mixed };
}) {
this.inst = instance;
Expand Down

0 comments on commit 8056e62

Please sign in to comment.