Skip to content

Commit 8056e62

Browse files
authored
Merge pull request #34 from ansemjo/opfs-sync-file
Add support for files in OPFS
2 parents ee4f287 + ba73046 commit 8056e62

File tree

4 files changed

+139
-17
lines changed

4 files changed

+139
-17
lines changed

src/fs_core.ts

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { OpenDirectory, OpenFile, OpenSyncOPFSFile } from "./fs_fd.js";
12
import * as wasi from "./wasi_defs.js";
23

34
export class File {
@@ -8,6 +9,12 @@ export class File {
89
this.data = new Uint8Array(data);
910
}
1011

12+
open(fd_flags: number) {
13+
let file = new OpenFile(this);
14+
if (fd_flags & wasi.FDFLAGS_APPEND) file.fd_seek(0n, wasi.WHENCE_END);
15+
return file;
16+
}
17+
1118
get size(): bigint {
1219
return BigInt(this.data.byteLength);
1320
}
@@ -21,19 +28,60 @@ export class File {
2128
}
2229
}
2330

31+
// Shim for https://developer.mozilla.org/en-US/docs/Web/API/FileSystemSyncAccessHandle
32+
// This is not part of the public interface.
33+
export interface FileSystemSyncAccessHandle {
34+
close(): void;
35+
flush(): void;
36+
getSize(): number;
37+
read(buffer: ArrayBuffer | ArrayBufferView, options?: { at: number }): number;
38+
truncate(to: number): void;
39+
write(buffer: ArrayBuffer | ArrayBufferView, options?: { at: number }): number;
40+
}
41+
42+
// Synchronous access to an individual file in the origin private file system.
43+
// Only allowed inside a WebWorker.
44+
export class SyncOPFSFile {
45+
// FIXME needs a close() method to be called after start() to release the underlying handle
46+
constructor(public handle: FileSystemSyncAccessHandle) { }
47+
48+
open(fd_flags: number) {
49+
let file = new OpenSyncOPFSFile(this);
50+
if (fd_flags & wasi.FDFLAGS_APPEND) file.fd_seek(0n, wasi.WHENCE_END);
51+
return file;
52+
}
53+
54+
get size(): bigint {
55+
return BigInt(this.handle.getSize());
56+
}
57+
58+
stat(): wasi.Filestat {
59+
return new wasi.Filestat(wasi.FILETYPE_REGULAR_FILE, this.size);
60+
}
61+
62+
truncate() {
63+
return this.handle.truncate(0);
64+
}
65+
66+
}
67+
2468
export class Directory {
25-
contents: { [key: string]: File | Directory };
69+
contents: { [key: string]: File | Directory | SyncOPFSFile };
2670

27-
constructor(contents: { [key: string]: File | Directory }) {
71+
constructor(contents: { [key: string]: File | Directory | SyncOPFSFile }) {
2872
this.contents = contents;
2973
}
3074

75+
open(fd_flags: number) {
76+
return new OpenDirectory(this);
77+
}
78+
3179
stat(): wasi.Filestat {
3280
return new wasi.Filestat(wasi.FILETYPE_DIRECTORY, 0n);
3381
}
3482

35-
get_entry_for_path(path: string): File | Directory | null {
36-
let entry: File | Directory = this;
83+
get_entry_for_path(path: string): File | Directory | SyncOPFSFile | null {
84+
let entry: File | Directory | SyncOPFSFile = this;
3785
for (let component of path.split("/")) {
3886
if (component == "") break;
3987
if (component == ".") continue;

src/fs_fd.ts

Lines changed: 84 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as wasi from "./wasi_defs.js";
2-
import { File, Directory } from "./fs_core.js";
2+
import { File, Directory, SyncOPFSFile, FileSystemSyncAccessHandle } from "./fs_core.js";
33
import { Fd } from "./fd.js";
44

55
declare var TextEncoder: {
@@ -102,6 +102,87 @@ export class OpenFile extends Fd {
102102
}
103103
}
104104

105+
export class OpenSyncOPFSFile extends Fd {
106+
handle: FileSystemSyncAccessHandle;
107+
position: bigint = 0n;
108+
109+
constructor(file: SyncOPFSFile) {
110+
super();
111+
this.handle = file.handle;
112+
};
113+
114+
fd_fdstat_get(): { ret: number; fdstat: wasi.Fdstat | null } {
115+
return { ret: 0, fdstat: new wasi.Fdstat(wasi.FILETYPE_REGULAR_FILE, 0) };
116+
}
117+
118+
fd_filestat_get(): { ret: number; filestat: wasi.Filestat } {
119+
return { ret: 0, filestat: new wasi.Filestat(wasi.FILETYPE_REGULAR_FILE, BigInt(this.handle.getSize())) };
120+
}
121+
122+
fd_read(view8: Uint8Array, iovs: Array<wasi.Iovec>): { ret: number, nread: number } {
123+
let nread = 0;
124+
for (let iovec of iovs) {
125+
if (this.position < this.handle.getSize()) {
126+
let buf = new Uint8Array(view8.buffer, iovec.buf, iovec.buf_len);
127+
let n = this.handle.read(buf, { at: Number(this.position) });
128+
this.position += BigInt(n);
129+
nread += n;
130+
} else {
131+
break;
132+
}
133+
}
134+
return { ret: 0, nread };
135+
}
136+
137+
fd_seek(offset: number | bigint, whence: number): { ret: number, offset: bigint } {
138+
let calculated_offset: bigint;
139+
switch (whence) {
140+
case wasi.WHENCE_SET:
141+
calculated_offset = BigInt(offset);
142+
break;
143+
case wasi.WHENCE_CUR:
144+
calculated_offset = this.position + BigInt(offset);
145+
break;
146+
case wasi.WHENCE_END:
147+
calculated_offset = BigInt(this.handle.getSize()) + BigInt(offset);
148+
break;
149+
default:
150+
return { ret: wasi.ERRNO_INVAL, offset: 0n };
151+
}
152+
if (calculated_offset < 0) {
153+
return { ret: wasi.ERRNO_INVAL, offset: 0n };
154+
}
155+
this.position = calculated_offset;
156+
return { ret: wasi.ERRNO_SUCCESS, offset: this.position };
157+
}
158+
159+
fd_write(view8: Uint8Array, iovs: Array<wasi.Iovec>): { ret: number, nwritten: number } {
160+
let nwritten = 0;
161+
for (let iovec of iovs) {
162+
let buf = new Uint8Array(view8.buffer, iovec.buf, iovec.buf_len);
163+
// don't need to extend file manually, just write
164+
let n = this.handle.write(buf, { at: Number(this.position) });
165+
this.position += BigInt(n);
166+
nwritten += n;
167+
}
168+
return { ret: wasi.ERRNO_SUCCESS, nwritten };
169+
}
170+
171+
fd_datasync(): number {
172+
this.handle.flush();
173+
return wasi.ERRNO_SUCCESS;
174+
}
175+
176+
fd_sync(): number {
177+
return this.fd_datasync();
178+
}
179+
180+
fd_close(): number {
181+
return wasi.ERRNO_SUCCESS;
182+
}
183+
184+
}
185+
105186
export class OpenDirectory extends Fd {
106187
dir: Directory;
107188

@@ -174,15 +255,7 @@ export class OpenDirectory extends Fd {
174255
// @ts-ignore
175256
entry.truncate();
176257
}
177-
// FIXME handle this more elegantly
178-
if (entry instanceof File) {
179-
// @ts-ignore
180-
return { ret: 0, fd_obj: new OpenFile(entry) };
181-
} else if (entry instanceof Directory) {
182-
return { ret: 0, fd_obj: new OpenDirectory(entry) };
183-
} else {
184-
throw "dir entry neither file nor dir";
185-
}
258+
return { ret: 0, fd_obj: entry.open(fd_flags) };
186259
}
187260

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

196-
constructor(name: string, contents: { [key: string]: File | Directory }) {
269+
constructor(name: string, contents: { [key: string]: File | Directory | SyncOPFSFile }) {
197270
super(new Directory(contents));
198271
this.prestat_name = new TextEncoder("utf-8").encode(name);
199272
}

src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@ import WASI from "./wasi.js";
22
export { WASI };
33

44
export { Fd } from './fd.js';
5-
export { File, Directory } from "./fs_core.js";
6-
export { OpenFile, OpenDirectory, PreopenDirectory } from "./fs_fd.js";
5+
export { File, SyncOPFSFile, Directory } from "./fs_core.js";
6+
export { OpenFile, OpenDirectory, OpenSyncOPFSFile, PreopenDirectory } from "./fs_fd.js";
77
export { strace } from "./strace.js";

src/wasi.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export default class WASI {
1818

1919
/// Start a WASI command
2020
start(instance: {
21+
// FIXME v0.3: close opened Fds after execution
2122
exports: { memory: WebAssembly.Memory; _start: () => mixed };
2223
}) {
2324
this.inst = instance;

0 commit comments

Comments
 (0)