@volter/browser-wali 0.1.166 → 0.1.167
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +8 -0
- package/README.md +9 -0
- package/dist/browser-wali-command.d.ts +7 -0
- package/dist/browser-wali-command.d.ts.map +1 -1
- package/dist/browser-wali-command.js +70 -18
- package/dist/browser-wali-command.js.map +1 -1
- package/dist/bulk-memory.d.ts +4 -2
- package/dist/bulk-memory.d.ts.map +1 -1
- package/dist/bulk-memory.js +32 -9
- package/dist/bulk-memory.js.map +1 -1
- package/dist/capabilities.d.ts +1 -1
- package/dist/capabilities.d.ts.map +1 -1
- package/dist/capabilities.js +5 -2
- package/dist/capabilities.js.map +1 -1
- package/dist/child-processes.d.ts +2 -0
- package/dist/child-processes.d.ts.map +1 -1
- package/dist/child-processes.js +9 -1
- package/dist/child-processes.js.map +1 -1
- package/dist/clocks.d.ts +3 -0
- package/dist/clocks.d.ts.map +1 -0
- package/dist/clocks.js +44 -0
- package/dist/clocks.js.map +1 -0
- package/dist/coherent-file-locks.d.ts.map +1 -1
- package/dist/coherent-file-locks.js +2 -1
- package/dist/coherent-file-locks.js.map +1 -1
- package/dist/credentials.d.ts +4 -0
- package/dist/credentials.d.ts.map +1 -0
- package/dist/credentials.js +46 -0
- package/dist/credentials.js.map +1 -0
- package/dist/descriptors.d.ts +14 -0
- package/dist/descriptors.d.ts.map +1 -1
- package/dist/descriptors.js +45 -5
- package/dist/descriptors.js.map +1 -1
- package/dist/environment.d.ts +4 -0
- package/dist/environment.d.ts.map +1 -0
- package/dist/environment.js +23 -0
- package/dist/environment.js.map +1 -0
- package/dist/epoll-ready.d.ts +27 -0
- package/dist/epoll-ready.d.ts.map +1 -0
- package/dist/epoll-ready.js +88 -0
- package/dist/epoll-ready.js.map +1 -0
- package/dist/events.d.ts +1 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +55 -24
- package/dist/events.js.map +1 -1
- package/dist/filesystem-host-session.d.ts +65 -0
- package/dist/filesystem-host-session.d.ts.map +1 -0
- package/dist/filesystem-host-session.js +951 -0
- package/dist/filesystem-host-session.js.map +1 -0
- package/dist/filesystem-host.d.ts +13 -81
- package/dist/filesystem-host.d.ts.map +1 -1
- package/dist/filesystem-host.js +14 -882
- package/dist/filesystem-host.js.map +1 -1
- package/dist/filesystem-protocol.d.ts +3 -1
- package/dist/filesystem-protocol.d.ts.map +1 -1
- package/dist/filesystem-remote.d.ts +8 -3
- package/dist/filesystem-remote.d.ts.map +1 -1
- package/dist/filesystem-remote.js +42 -20
- package/dist/filesystem-remote.js.map +1 -1
- package/dist/filesystem-service-client.d.ts +4 -0
- package/dist/filesystem-service-client.d.ts.map +1 -0
- package/dist/filesystem-service-client.js +16 -0
- package/dist/filesystem-service-client.js.map +1 -0
- package/dist/filesystem.d.ts +25 -4
- package/dist/filesystem.d.ts.map +1 -1
- package/dist/filesystem.js +548 -169
- package/dist/filesystem.js.map +1 -1
- package/dist/fork-memory.d.ts +23 -0
- package/dist/fork-memory.d.ts.map +1 -0
- package/dist/fork-memory.js +93 -0
- package/dist/fork-memory.js.map +1 -0
- package/dist/fork-worker.js +32 -11
- package/dist/fork-worker.js.map +1 -1
- package/dist/jumps.d.ts +7 -0
- package/dist/jumps.d.ts.map +1 -1
- package/dist/jumps.js +19 -1
- package/dist/jumps.js.map +1 -1
- package/dist/kernel-filesystem-worker.d.ts +2 -0
- package/dist/kernel-filesystem-worker.d.ts.map +1 -0
- package/dist/kernel-filesystem-worker.js +28 -0
- package/dist/kernel-filesystem-worker.js.map +1 -0
- package/dist/kernel-filesystem.d.ts +12 -0
- package/dist/kernel-filesystem.d.ts.map +1 -0
- package/dist/kernel-filesystem.js +45 -0
- package/dist/kernel-filesystem.js.map +1 -0
- package/dist/kernel-network-worker.d.ts +2 -0
- package/dist/kernel-network-worker.d.ts.map +1 -0
- package/dist/kernel-network-worker.js +14 -0
- package/dist/kernel-network-worker.js.map +1 -0
- package/dist/kernel-network.d.ts +14 -0
- package/dist/kernel-network.d.ts.map +1 -0
- package/dist/kernel-network.js +50 -0
- package/dist/kernel-network.js.map +1 -0
- package/dist/kernel-process-client.d.ts +6 -1
- package/dist/kernel-process-client.d.ts.map +1 -1
- package/dist/kernel-process-client.js +10 -1
- package/dist/kernel-process-client.js.map +1 -1
- package/dist/module-layout.d.ts +8 -0
- package/dist/module-layout.d.ts.map +1 -1
- package/dist/module-layout.js +263 -2
- package/dist/module-layout.js.map +1 -1
- package/dist/network-host.d.ts +6 -0
- package/dist/network-host.d.ts.map +1 -0
- package/dist/network-host.js +1582 -0
- package/dist/network-host.js.map +1 -0
- package/dist/network-protocol.d.ts +9 -2
- package/dist/network-protocol.d.ts.map +1 -1
- package/dist/network-protocol.js +3 -0
- package/dist/network-protocol.js.map +1 -1
- package/dist/network-service-client.d.ts +4 -0
- package/dist/network-service-client.d.ts.map +1 -0
- package/dist/network-service-client.js +16 -0
- package/dist/network-service-client.js.map +1 -0
- package/dist/network-worker.js +5 -1150
- package/dist/network-worker.js.map +1 -1
- package/dist/network.d.ts +13 -1
- package/dist/network.d.ts.map +1 -1
- package/dist/network.js +175 -48
- package/dist/network.js.map +1 -1
- package/dist/paths.d.ts +11 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +97 -0
- package/dist/paths.js.map +1 -0
- package/dist/process-bridge.d.ts +12 -4
- package/dist/process-bridge.d.ts.map +1 -1
- package/dist/process-bridge.js +53 -17
- package/dist/process-bridge.js.map +1 -1
- package/dist/program.d.ts.map +1 -1
- package/dist/program.js +8 -0
- package/dist/program.js.map +1 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +150 -92
- package/dist/runtime.js.map +1 -1
- package/dist/shared-buffer-pool.d.ts +18 -5
- package/dist/shared-buffer-pool.d.ts.map +1 -1
- package/dist/shared-buffer-pool.js +86 -14
- package/dist/shared-buffer-pool.js.map +1 -1
- package/dist/shared-file-layer.d.ts +3 -1
- package/dist/shared-file-layer.d.ts.map +1 -1
- package/dist/shared-file-layer.js +150 -35
- package/dist/shared-file-layer.js.map +1 -1
- package/dist/shared-pipes.d.ts +3 -1
- package/dist/shared-pipes.d.ts.map +1 -1
- package/dist/shared-pipes.js +105 -33
- package/dist/shared-pipes.js.map +1 -1
- package/dist/signal-interruption.d.ts +9 -0
- package/dist/signal-interruption.d.ts.map +1 -0
- package/dist/signal-interruption.js +6 -0
- package/dist/signal-interruption.js.map +1 -0
- package/dist/signals.d.ts +17 -6
- package/dist/signals.d.ts.map +1 -1
- package/dist/signals.js +101 -22
- package/dist/signals.js.map +1 -1
- package/dist/snapshot-filesystem.d.ts +1 -0
- package/dist/snapshot-filesystem.d.ts.map +1 -1
- package/dist/snapshot-filesystem.js +3 -0
- package/dist/snapshot-filesystem.js.map +1 -1
- package/dist/socket-flags.d.ts +3 -0
- package/dist/socket-flags.d.ts.map +1 -0
- package/dist/socket-flags.js +9 -0
- package/dist/socket-flags.js.map +1 -0
- package/dist/special-files.d.ts +6 -0
- package/dist/special-files.d.ts.map +1 -0
- package/dist/special-files.js +56 -0
- package/dist/special-files.js.map +1 -0
- package/dist/stdin.d.ts.map +1 -1
- package/dist/stdin.js +13 -5
- package/dist/stdin.js.map +1 -1
- package/dist/symlink.d.ts +2 -1
- package/dist/symlink.d.ts.map +1 -1
- package/dist/symlink.js +25 -93
- package/dist/symlink.js.map +1 -1
- package/dist/system.d.ts +2 -4
- package/dist/system.d.ts.map +1 -1
- package/dist/system.js +46 -29
- package/dist/system.js.map +1 -1
- package/dist/thread-coordinator-worker.js +58 -24
- package/dist/thread-coordinator-worker.js.map +1 -1
- package/dist/thread-lifecycle.d.ts +9 -0
- package/dist/thread-lifecycle.d.ts.map +1 -0
- package/dist/thread-lifecycle.js +30 -0
- package/dist/thread-lifecycle.js.map +1 -0
- package/dist/thread-worker.js +125 -44
- package/dist/thread-worker.js.map +1 -1
- package/dist/threads.d.ts +6 -0
- package/dist/threads.d.ts.map +1 -1
- package/dist/threads.js +46 -6
- package/dist/threads.js.map +1 -1
- package/dist/tty-job-control.d.ts +13 -0
- package/dist/tty-job-control.d.ts.map +1 -0
- package/dist/tty-job-control.js +56 -0
- package/dist/tty-job-control.js.map +1 -0
- package/dist/tty-settings.d.ts +3 -0
- package/dist/tty-settings.d.ts.map +1 -0
- package/dist/tty-settings.js +25 -0
- package/dist/tty-settings.js.map +1 -0
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/wait-state.d.ts +8 -0
- package/dist/wait-state.d.ts.map +1 -1
- package/dist/wait-state.js +15 -0
- package/dist/wait-state.js.map +1 -1
- package/dist/worker-program-protocol.d.ts +22 -2
- package/dist/worker-program-protocol.d.ts.map +1 -1
- package/dist/worker-program-worker.js +31 -9
- package/dist/worker-program-worker.js.map +1 -1
- package/dist/worker-program.d.ts +12 -2
- package/dist/worker-program.d.ts.map +1 -1
- package/dist/worker-program.js +214 -36
- package/dist/worker-program.js.map +1 -1
- package/dist/write-vector.d.ts +3 -0
- package/dist/write-vector.d.ts.map +1 -0
- package/dist/write-vector.js +35 -0
- package/dist/write-vector.js.map +1 -0
- package/package.json +1 -1
package/dist/filesystem.js
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
|
+
// File-call rules translated from The Emscripten Authors' WasmFS syscalls.cpp
|
|
2
|
+
// (Copyright 2021, MIT), 862dc0d48acc4134a0088d27ac8f5dd18baba1cd; see ../LICENSE.
|
|
3
|
+
// Storage, shared descriptors and process state remain owned by this kernel.
|
|
4
|
+
import { createWaliDevice, waliDeviceStat } from "./special-files.js";
|
|
1
5
|
import { realtimeMilliseconds } from "./monotonic-clock.js";
|
|
6
|
+
import { gatherWaliWriteVector } from "./write-vector.js";
|
|
7
|
+
import { setBrowserFileTimes } from "@volter/browser-runtime/browser-file-times.js";
|
|
2
8
|
import { WaliDescriptionRegistry, WaliProcessDescriptorTable } from "./descriptors.js";
|
|
9
|
+
import { WaliSharedPipeHost } from "./shared-pipes.js";
|
|
3
10
|
import { createProcessFilesystemMutationGuard } from "@volter/browser-runtime/browser-process-filesystem-policy.js";
|
|
4
11
|
import { BufferedWaliStdin } from "./stdin.js";
|
|
5
12
|
import { readWaliSymlink, resolveWaliSymlinkPath, writeWaliSymlink } from "./symlink.js";
|
|
13
|
+
import { absoluteWaliPath, normalizeWaliPath } from "./paths.js";
|
|
6
14
|
import { PR_WALI, PR_WALI_TREE_ADOPT, PR_WALI_TREE_CONFIRM, PR_WALI_TREE_DECLINE, PR_WALI_TREE_RELEASE, PRIVATE_TREE_INODES, PRIVATE_TREE_WINDOW_BASE, PRIVATE_TREE_WINDOW_SIZE, decodePrivateTree, encodePrivateTree, inPrivateTreeWindow, } from "./private-tree.js";
|
|
7
15
|
let reportedDroppedEnvironment = false;
|
|
8
16
|
const AT_FDCWD = -100;
|
|
@@ -15,6 +23,10 @@ const O_TRUNC = 0x200;
|
|
|
15
23
|
const O_APPEND = 0x400;
|
|
16
24
|
const O_NONBLOCK = 0x800;
|
|
17
25
|
const O_DIRECTORY = 0x1_0000;
|
|
26
|
+
const O_NOFOLLOW = 0x2_0000;
|
|
27
|
+
const AT_EMPTY_PATH = 0x1000;
|
|
28
|
+
const AT_NO_AUTOMOUNT = 0x800;
|
|
29
|
+
const AT_EACCESS = 0x200;
|
|
18
30
|
const SEEK_SET = 0;
|
|
19
31
|
const SEEK_CUR = 1;
|
|
20
32
|
const SEEK_END = 2;
|
|
@@ -53,9 +65,9 @@ export class WaliFileDescriptorTable {
|
|
|
53
65
|
files;
|
|
54
66
|
specialDescriptors;
|
|
55
67
|
stdioAliases;
|
|
56
|
-
descriptorStatusFlags = new Map();
|
|
57
68
|
stdin;
|
|
58
69
|
nextDescription = 1;
|
|
70
|
+
fileCreationMask;
|
|
59
71
|
cwd;
|
|
60
72
|
guestFilesystem;
|
|
61
73
|
assertWritable;
|
|
@@ -64,6 +76,7 @@ export class WaliFileDescriptorTable {
|
|
|
64
76
|
privateTreeDeclined = false;
|
|
65
77
|
constructor(options) {
|
|
66
78
|
this.options = options;
|
|
79
|
+
this.fileCreationMask = (options.fileCreationMask ?? 0o022) & 0o777;
|
|
67
80
|
this.descriptors = options.descriptors ?? new WaliProcessDescriptorTable(new WaliDescriptionRegistry());
|
|
68
81
|
this.files = this.descriptors.view("file");
|
|
69
82
|
this.specialDescriptors = this.descriptors.view("special", (value) => value.close?.());
|
|
@@ -101,7 +114,7 @@ export class WaliFileDescriptorTable {
|
|
|
101
114
|
...(fs.restoreNodeSync ? { restoreNodeSync: (path, node) => fs.restoreNodeSync(this.assertWritable(path), node) } : {}),
|
|
102
115
|
} : fs;
|
|
103
116
|
this.stdin = "read" in options.stdin ? options.stdin : new BufferedWaliStdin(options.stdin);
|
|
104
|
-
if (!options.descriptors) {
|
|
117
|
+
if (!options.descriptors || options.initializeStdio) {
|
|
105
118
|
for (const fd of [0, 1, 2])
|
|
106
119
|
this.specialDescriptors.set(fd, { stdio: fd });
|
|
107
120
|
}
|
|
@@ -117,6 +130,7 @@ export class WaliFileDescriptorTable {
|
|
|
117
130
|
SYS_open: (path, flags, mode) => this.open(this.readCString(Number(path)), Number(flags), Number(mode)),
|
|
118
131
|
SYS_openat: (dirfd, path, flags, mode) => this.openAt(Number(dirfd), this.readCString(Number(path)), Number(flags), Number(mode)),
|
|
119
132
|
SYS_close: (fd) => this.close(Number(fd)),
|
|
133
|
+
SYS_ioctl: (fd, request, argument) => this.ioctl(Number(fd), Number(request), Number(argument)),
|
|
120
134
|
SYS_read: (fd, pointer, length) => this.read(Number(fd), Number(pointer), Number(length)),
|
|
121
135
|
SYS_pread64: (fd, pointer, length, offset) => this.pread(Number(fd), Number(pointer), Number(length), Number(offset)),
|
|
122
136
|
SYS_write: (fd, pointer, length) => this.write(Number(fd), Number(pointer), Number(length)),
|
|
@@ -128,13 +142,19 @@ export class WaliFileDescriptorTable {
|
|
|
128
142
|
SYS_pwritev: (fd, iov, count, offset, high) => this.pwritev(Number(fd), Number(iov), Number(count), vectorOffset(offset, high)),
|
|
129
143
|
SYS_lseek: (fd, offset, whence) => this.lseek(Number(fd), Number(offset), Number(whence)),
|
|
130
144
|
SYS_ftruncate: (fd, length) => this.ftruncate(Number(fd), Number(length)),
|
|
145
|
+
SYS_truncate: (path, length) => this.truncate(this.readCString(Number(path)), Number(length)),
|
|
131
146
|
SYS_fstat: (fd, stat) => this.fstat(Number(fd), Number(stat)),
|
|
132
147
|
SYS_stat: (path, stat) => this.stat(this.readCString(Number(path)), Number(stat), false),
|
|
133
148
|
SYS_lstat: (path, stat) => this.stat(this.readCString(Number(path)), Number(stat), true),
|
|
134
149
|
SYS_newfstatat: (dirfd, path, stat, flags) => this.statAt(Number(dirfd), this.readCString(Number(path)), Number(stat), Number(flags)),
|
|
135
|
-
SYS_access: (path) => this.
|
|
136
|
-
SYS_faccessat: (dirfd, path) => this.accessAt(Number(dirfd), this.readCString(Number(path))),
|
|
137
|
-
SYS_faccessat2: (dirfd, path) => this.accessAt(Number(dirfd), this.readCString(Number(path))),
|
|
150
|
+
SYS_access: (path, mode) => this.accessAt(AT_FDCWD, this.readCString(Number(path)), Number(mode), 0),
|
|
151
|
+
SYS_faccessat: (dirfd, path, mode) => this.accessAt(Number(dirfd), this.readCString(Number(path)), Number(mode), 0),
|
|
152
|
+
SYS_faccessat2: (dirfd, path, mode, flags) => this.accessAt(Number(dirfd), this.readCString(Number(path)), Number(mode), Number(flags)),
|
|
153
|
+
SYS_umask: (mask) => {
|
|
154
|
+
const previous = this.fileCreationMask;
|
|
155
|
+
this.fileCreationMask = Number(mask) & 0o777;
|
|
156
|
+
return BigInt(previous);
|
|
157
|
+
},
|
|
138
158
|
SYS_chmod: (path, mode) => this.chmod(this.readCString(Number(path)), Number(mode)),
|
|
139
159
|
SYS_fchmod: (fd, mode) => this.fchmod(Number(fd), Number(mode)),
|
|
140
160
|
SYS_fchmodat: (dirfd, path, mode, flags) => this.chmodAt(Number(dirfd), this.readCString(Number(path)), Number(mode), Number(flags)),
|
|
@@ -149,10 +169,11 @@ export class WaliFileDescriptorTable {
|
|
|
149
169
|
SYS_inotify_init1: () => this.inotifyInit(),
|
|
150
170
|
SYS_inotify_add_watch: (fd, path) => this.inotifyAddWatch(Number(fd), this.readCString(Number(path))),
|
|
151
171
|
SYS_inotify_rm_watch: (fd) => this.specialDescriptors.has(Number(fd)) ? 0n : -9n,
|
|
152
|
-
SYS_rmdir: (path) => this.
|
|
153
|
-
SYS_unlink: (path) => this.
|
|
172
|
+
SYS_rmdir: (path) => this.unlinkAt(AT_FDCWD, this.readCString(Number(path)), AT_REMOVEDIR),
|
|
173
|
+
SYS_unlink: (path) => this.unlinkAt(AT_FDCWD, this.readCString(Number(path)), 0),
|
|
154
174
|
SYS_unlinkat: (dirfd, path, flags) => this.unlinkAt(Number(dirfd), this.readCString(Number(path)), Number(flags)),
|
|
155
175
|
SYS_rename: (from, to) => this.rename(this.readCString(Number(from)), this.readCString(Number(to))),
|
|
176
|
+
SYS_renameat: (fromDirfd, from, toDirfd, to) => this.rename(this.readCString(Number(from)), this.readCString(Number(to)), Number(fromDirfd), Number(toDirfd)),
|
|
156
177
|
SYS_utimensat: (dirfd, path, times, flags) => this.utimensat(Number(dirfd), Number(path), Number(times), Number(flags)),
|
|
157
178
|
SYS_symlink: (target, link) => this.symlink(this.readCString(Number(target)), this.readCString(Number(link)), AT_FDCWD),
|
|
158
179
|
SYS_symlinkat: (target, dirfd, link) => this.symlink(this.readCString(Number(target)), this.readCString(Number(link)), Number(dirfd)),
|
|
@@ -190,15 +211,17 @@ export class WaliFileDescriptorTable {
|
|
|
190
211
|
this.options.filesystem.mkdirSync("/tmp", { recursive: true });
|
|
191
212
|
}
|
|
192
213
|
catch { /* already present */ }
|
|
193
|
-
//
|
|
194
|
-
//
|
|
214
|
+
// Legacy artifacts read one `name=value` per line. The owned linker now
|
|
215
|
+
// supplies direct environment imports; rebuilt programs never use this
|
|
216
|
+
// compatibility path. Old artifacts still cannot carry a line break.
|
|
217
|
+
// A shell's own
|
|
195
218
|
// parameters (`?`, a newline-valued IFS) reach here through every lane's
|
|
196
219
|
// spawn; refusing the whole spawn for them is what killed a script's
|
|
197
220
|
// `spawn("ffmpeg")`. They are dropped and named once.
|
|
198
221
|
const dropped = [];
|
|
199
222
|
const lines = [];
|
|
200
223
|
for (const [name, value] of Object.entries(env)) {
|
|
201
|
-
if (
|
|
224
|
+
if (/[=\u0000\r\n]/u.test(name) || /[\u0000\r\n]/u.test(value)) {
|
|
202
225
|
dropped.push(name);
|
|
203
226
|
continue;
|
|
204
227
|
}
|
|
@@ -270,7 +293,7 @@ export class WaliFileDescriptorTable {
|
|
|
270
293
|
return this.specialDescriptors.get(fd)?.browserProcessPipe;
|
|
271
294
|
}
|
|
272
295
|
copyForFork(memory) {
|
|
273
|
-
return new WaliFileDescriptorTable({ ...this.options, cwd: this.cwd, memory, descriptors: this.descriptors.copy() });
|
|
296
|
+
return new WaliFileDescriptorTable({ ...this.options, initializeStdio: false, fileCreationMask: this.fileCreationMask, cwd: this.cwd, memory, descriptors: this.descriptors.copy() });
|
|
274
297
|
}
|
|
275
298
|
copyForExec(memory, stdio) {
|
|
276
299
|
const descriptors = this.descriptors.copy();
|
|
@@ -282,17 +305,45 @@ export class WaliFileDescriptorTable {
|
|
|
282
305
|
});
|
|
283
306
|
}
|
|
284
307
|
descriptors.exec();
|
|
285
|
-
return new WaliFileDescriptorTable({ ...this.options, cwd: this.cwd, memory, descriptors });
|
|
308
|
+
return new WaliFileDescriptorTable({ ...this.options, initializeStdio: false, fileCreationMask: this.fileCreationMask, cwd: this.cwd, memory, descriptors });
|
|
286
309
|
}
|
|
287
310
|
currentWorkingDirectory() { return this.cwd; }
|
|
288
|
-
|
|
311
|
+
creationMask() { return this.fileCreationMask; }
|
|
312
|
+
// Translated from Emscripten libsyscall.js and wasmfs/syscalls.cpp (MIT),
|
|
313
|
+
// 862dc0d48acc4134a0088d27ac8f5dd18baba1cd. The kernel owns the description;
|
|
314
|
+
// terminal settings are dispatched separately to the attached tty.
|
|
315
|
+
ioctl(fd, request, argument) {
|
|
289
316
|
if (!this.hasFd(fd))
|
|
290
|
-
return;
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
317
|
+
return -9n;
|
|
318
|
+
const operation = request >>> 0;
|
|
319
|
+
if (operation === 0x5451 || operation === 0x5450) {
|
|
320
|
+
return this.fcntl(fd, 2, operation === 0x5451 ? 1 : 0);
|
|
321
|
+
}
|
|
322
|
+
if (operation !== 0x5421 && operation !== 0x541b)
|
|
323
|
+
return -25n;
|
|
324
|
+
const file = this.files.get(fd);
|
|
325
|
+
if (operation === 0x541b && (!file || file.directory))
|
|
326
|
+
return -25n;
|
|
327
|
+
const memory = this.options.memory();
|
|
328
|
+
if (!Number.isSafeInteger(argument) || argument <= 0 || argument + 4 > memory.buffer.byteLength)
|
|
329
|
+
return -14n;
|
|
330
|
+
if (operation === 0x5421) {
|
|
331
|
+
const nonblocking = this.memoryView().getInt32(argument, true) !== 0;
|
|
332
|
+
const flags = this.fcntl(fd, 3, 0);
|
|
333
|
+
if (flags < 0n)
|
|
334
|
+
return flags;
|
|
335
|
+
return this.fcntl(fd, 4, nonblocking ? Number(flags) | O_NONBLOCK : Number(flags) & ~O_NONBLOCK);
|
|
336
|
+
}
|
|
337
|
+
if (!file)
|
|
338
|
+
return -25n;
|
|
339
|
+
try {
|
|
340
|
+
const remaining = Math.max(0, this.fs().statSync(file.path).size - file.position);
|
|
341
|
+
this.memoryView().setInt32(argument, remaining, true);
|
|
294
342
|
return 0n;
|
|
295
|
-
|
|
343
|
+
}
|
|
344
|
+
catch (error) {
|
|
345
|
+
return negativeErrno(error);
|
|
346
|
+
}
|
|
296
347
|
}
|
|
297
348
|
pollDescriptor(fd, events) {
|
|
298
349
|
if (fd < 0 || !this.hasFd(fd))
|
|
@@ -302,9 +353,12 @@ export class WaliFileDescriptorTable {
|
|
|
302
353
|
return special.poll?.(events) ?? 0;
|
|
303
354
|
const stdio = this.stdioFd(fd);
|
|
304
355
|
let returned = 0;
|
|
305
|
-
|
|
356
|
+
const terminal = this.terminalFd(fd);
|
|
357
|
+
const input = terminal ? this.options.terminalStdin : this.stdin;
|
|
358
|
+
const mode = this.statusFlags(fd) & O_ACCMODE;
|
|
359
|
+
if ((events & 0x1) && stdio === 0 && (!terminal || mode !== O_WRONLY) && input?.poll())
|
|
306
360
|
returned |= 0x1;
|
|
307
|
-
if ((events & 0x4) && stdio !== 0)
|
|
361
|
+
if ((events & 0x4) && (stdio !== 0 || (terminal && mode !== 0)))
|
|
308
362
|
returned |= 0x4;
|
|
309
363
|
return returned;
|
|
310
364
|
}
|
|
@@ -359,20 +413,22 @@ export class WaliFileDescriptorTable {
|
|
|
359
413
|
}
|
|
360
414
|
return text;
|
|
361
415
|
}
|
|
362
|
-
resolve(path) { return
|
|
416
|
+
resolve(path) { return absoluteWaliPath(this.cwd, path); }
|
|
363
417
|
resolveAt(dirfd, path) {
|
|
364
418
|
if (path.startsWith("/"))
|
|
365
|
-
return
|
|
419
|
+
return path;
|
|
366
420
|
if (dirfd === AT_FDCWD)
|
|
367
421
|
return this.resolve(path);
|
|
368
422
|
const directory = this.files.get(dirfd);
|
|
369
|
-
if (!directory
|
|
423
|
+
if (!directory)
|
|
424
|
+
throw errno(this.hasFd(dirfd) ? "ENOTDIR" : "EBADF");
|
|
425
|
+
if (!directory.directory)
|
|
370
426
|
throw errno("ENOTDIR");
|
|
371
|
-
return
|
|
427
|
+
return absoluteWaliPath(directory.path, path);
|
|
372
428
|
}
|
|
373
429
|
open(path, flags, mode) {
|
|
374
430
|
try {
|
|
375
|
-
return BigInt(this.
|
|
431
|
+
return BigInt(this.openPath(this.resolve(path), flags, mode));
|
|
376
432
|
}
|
|
377
433
|
catch (error) {
|
|
378
434
|
return negativeErrno(error);
|
|
@@ -380,45 +436,65 @@ export class WaliFileDescriptorTable {
|
|
|
380
436
|
}
|
|
381
437
|
openAt(dirfd, path, flags, mode) {
|
|
382
438
|
try {
|
|
383
|
-
return BigInt(this.
|
|
439
|
+
return BigInt(this.openPath(this.resolveAt(dirfd, path), flags, mode));
|
|
384
440
|
}
|
|
385
441
|
catch (error) {
|
|
386
442
|
return negativeErrno(error);
|
|
387
443
|
}
|
|
388
444
|
}
|
|
445
|
+
openPath(path, flags, mode) {
|
|
446
|
+
if ((flags & O_ACCMODE) === O_ACCMODE)
|
|
447
|
+
throw errno("EINVAL");
|
|
448
|
+
// Linux >=6.4 rejects this pair instead of creating a regular file and
|
|
449
|
+
// then failing O_DIRECTORY (the old behaviour still in pinned WasmFS).
|
|
450
|
+
if ((flags & O_CREAT) && (flags & O_DIRECTORY))
|
|
451
|
+
throw errno("EINVAL");
|
|
452
|
+
const exclusive = Boolean((flags & O_CREAT) && (flags & O_EXCL));
|
|
453
|
+
const resolved = this.resolveFilesystemPath(path, !(exclusive || (flags & O_NOFOLLOW)));
|
|
454
|
+
const target = exclusive || (flags & O_NOFOLLOW) ? this.virtualLink(resolved) ?? this.readSymlink(resolved) : undefined;
|
|
455
|
+
const entry = exclusive || (flags & O_NOFOLLOW) ? statIfPresent(this.fs(), resolved, true) : undefined;
|
|
456
|
+
const link = target !== undefined || entry?.isSymbolicLink?.();
|
|
457
|
+
// Linux O_EXCL checks the link itself, even when its target is absent.
|
|
458
|
+
if (exclusive && (link || entry !== undefined
|
|
459
|
+
|| ["/dev/null", "/dev/random", "/dev/urandom"].includes(resolved)))
|
|
460
|
+
throw errno("EEXIST");
|
|
461
|
+
if ((flags & O_NOFOLLOW) && link)
|
|
462
|
+
throw errno("ELOOP");
|
|
463
|
+
checkFileName(resolved);
|
|
464
|
+
const descriptor = this.procDescriptor(resolved);
|
|
465
|
+
if (descriptor !== undefined)
|
|
466
|
+
return this.reopenDescriptor(descriptor, flags, mode);
|
|
467
|
+
return this.openResolved(resolved, flags, mode);
|
|
468
|
+
}
|
|
389
469
|
openResolved(path, flags, mode) {
|
|
390
470
|
// O_CLOEXEC is recorded here for every kind of descriptor open makes: musl
|
|
391
471
|
// no longer follows it with F_SETFD (musl 0003), which was the only thing
|
|
392
472
|
// marking these special files (review of dfd87b6).
|
|
393
|
-
if (path === "/dev/
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
return this.markCloseOnExecNumber(
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
},
|
|
412
|
-
write: (value) => BigInt(value.byteLength),
|
|
413
|
-
poll: (events) => ((events & 1) ? 1 : 0) | ((events & 4) ? 4 : 0),
|
|
414
|
-
fcntl: (command) => command === 3 ? BigInt(flags) : [1, 2, 4].includes(command) ? 0n : -22n,
|
|
415
|
-
}), flags);
|
|
473
|
+
if (path === "/dev/tty") {
|
|
474
|
+
if (flags & O_DIRECTORY)
|
|
475
|
+
throw errno("ENOTDIR");
|
|
476
|
+
const fd = this.allocateFd();
|
|
477
|
+
this.specialDescriptors.set(fd, { stdio: 0, terminal: true, stat: () => waliDeviceStat(path) });
|
|
478
|
+
Atomics.store(this.descriptors.registry.pipeMetadata, this.descriptors.entries[fd].description * 3 + 1, flags & ~O_CLOEXEC_FLAG);
|
|
479
|
+
// The calling realm validates controlling-session membership through
|
|
480
|
+
// ttyopen before returning this fd. The filesystem owner owns the fd;
|
|
481
|
+
// terminal input/output stay on the caller's attached transport.
|
|
482
|
+
return this.markCloseOnExecNumber(fd, flags);
|
|
483
|
+
}
|
|
484
|
+
const device = createWaliDevice(path, flags);
|
|
485
|
+
if (device) {
|
|
486
|
+
if (flags & O_DIRECTORY)
|
|
487
|
+
throw errno("ENOTDIR");
|
|
488
|
+
const fd = this.allocateFd();
|
|
489
|
+
this.specialDescriptors.set(fd, device);
|
|
490
|
+
return this.markCloseOnExecNumber(fd, flags);
|
|
416
491
|
}
|
|
492
|
+
const fd = this.allocateFd();
|
|
417
493
|
const fs = this.fs();
|
|
418
494
|
// One lookup where the store answers ENOENT from statSync (a snapshot store's); exists-then-stat resolved it twice.
|
|
419
|
-
let found = fs.linkTargetSync ? statIfPresent(fs, path) : fs.existsSync(path) ? fs.statSync(path) : undefined;
|
|
495
|
+
let found = this.virtualStat(path, false) ?? (fs.linkTargetSync ? statIfPresent(fs, path) : fs.existsSync(path) ? fs.statSync(path) : undefined);
|
|
420
496
|
const exists = found !== undefined;
|
|
421
|
-
if ((flags & O_ACCMODE) !== 0 || (!exists && (flags & O_CREAT)))
|
|
497
|
+
if ((flags & O_ACCMODE) !== 0 || (flags & O_TRUNC) || (!exists && (flags & O_CREAT)))
|
|
422
498
|
this.assertWritable(path);
|
|
423
499
|
if (!exists && !(flags & O_CREAT))
|
|
424
500
|
throw errno("ENOENT");
|
|
@@ -426,30 +502,40 @@ export class WaliFileDescriptorTable {
|
|
|
426
502
|
throw errno("EEXIST");
|
|
427
503
|
if (!exists) {
|
|
428
504
|
fs.writeFileSync(path, new Uint8Array());
|
|
429
|
-
fs.chmodSync?.(path, mode & 0o7777);
|
|
505
|
+
fs.chmodSync?.(path, mode & 0o7777 & ~this.fileCreationMask);
|
|
430
506
|
this.options.fileLocks?.created?.(path);
|
|
431
507
|
found = fs.statSync(path);
|
|
432
508
|
}
|
|
433
509
|
const stat = found;
|
|
434
510
|
if ((flags & O_DIRECTORY) && !stat.isDirectory())
|
|
435
511
|
throw errno("ENOTDIR");
|
|
436
|
-
if (stat.isDirectory() && (flags & O_ACCMODE) !== 0)
|
|
512
|
+
if (stat.isDirectory() && ((flags & O_ACCMODE) !== 0 || (flags & O_CREAT) || (flags & O_TRUNC)))
|
|
437
513
|
throw errno("EISDIR");
|
|
438
|
-
|
|
514
|
+
// doOpen does not recheck the mode of the file it just created: creating
|
|
515
|
+
// mode 0400 with O_RDWR still returns a writable open description.
|
|
516
|
+
if (exists) {
|
|
517
|
+
if ((flags & O_ACCMODE) !== O_WRONLY)
|
|
518
|
+
checkFilePermission(stat, 4);
|
|
519
|
+
if ((flags & O_ACCMODE) !== 0 || (flags & O_TRUNC))
|
|
520
|
+
checkFilePermission(stat, 2);
|
|
521
|
+
}
|
|
522
|
+
const truncated = (flags & O_TRUNC) !== 0 && stat.isFile();
|
|
439
523
|
if (truncated) {
|
|
440
524
|
if (fs.truncateSync)
|
|
441
525
|
fs.truncateSync(path, 0);
|
|
442
526
|
else
|
|
443
527
|
fs.writeFileSync(path, new Uint8Array());
|
|
444
528
|
}
|
|
445
|
-
const fd = this.allocateFd();
|
|
446
529
|
const directory = stat.isDirectory();
|
|
530
|
+
const virtualDirectory = ["/proc", "/proc/self", "/proc/self/fd"].includes(path);
|
|
531
|
+
const entries = directory && !virtualDirectory ? this.directoryEntries(path) : undefined;
|
|
447
532
|
this.files.set(fd, {
|
|
448
533
|
path,
|
|
449
534
|
flags,
|
|
450
|
-
//
|
|
451
|
-
position:
|
|
535
|
+
// O_APPEND chooses EOF on each write; open itself starts at zero.
|
|
536
|
+
position: 0,
|
|
452
537
|
directory,
|
|
538
|
+
...(entries ? { entries } : {}),
|
|
453
539
|
description: this.nextDescription++,
|
|
454
540
|
});
|
|
455
541
|
this.descriptors.entries[fd].flags = flags & 0x80000 ? 1 : 0;
|
|
@@ -471,8 +557,8 @@ export class WaliFileDescriptorTable {
|
|
|
471
557
|
*
|
|
472
558
|
* Scanning costs one probe per open descriptor, and a process has a handful.
|
|
473
559
|
*/
|
|
474
|
-
allocateFd(minimum =
|
|
475
|
-
const maximum = this.options.descriptorMaximum ?? 65_535;
|
|
560
|
+
allocateFd(minimum = 0) {
|
|
561
|
+
const maximum = Math.min(this.options.descriptorMaximum ?? 65_535, this.descriptors.descriptorLimit - 1);
|
|
476
562
|
for (let fd = Math.max(minimum, 0); fd <= maximum; fd += 1) {
|
|
477
563
|
// A private tree's numbers are its libc's, held or given back (ADR-0047).
|
|
478
564
|
if (inPrivateTreeWindow(fd)) {
|
|
@@ -489,7 +575,6 @@ export class WaliFileDescriptorTable {
|
|
|
489
575
|
}
|
|
490
576
|
close(fd) {
|
|
491
577
|
if (this.stdioAliases.delete(fd)) {
|
|
492
|
-
this.descriptorStatusFlags.delete(fd);
|
|
493
578
|
this.setSharedStdioAlias(fd, undefined);
|
|
494
579
|
return 0n;
|
|
495
580
|
}
|
|
@@ -526,9 +611,14 @@ export class WaliFileDescriptorTable {
|
|
|
526
611
|
if (length < 0)
|
|
527
612
|
throw errno("EINVAL");
|
|
528
613
|
if (this.stdioFd(fd) === 0) {
|
|
529
|
-
if ((this.statusFlags(fd) &
|
|
614
|
+
if (this.terminalFd(fd) && (this.statusFlags(fd) & O_ACCMODE) === O_WRONLY)
|
|
615
|
+
return -9n;
|
|
616
|
+
const input = this.terminalFd(fd) ? this.options.terminalStdin : this.stdin;
|
|
617
|
+
if (!input)
|
|
618
|
+
return -95n; // No independent terminal transport was supplied.
|
|
619
|
+
if ((this.statusFlags(fd) & O_NONBLOCK) && !input.poll())
|
|
530
620
|
return -11n;
|
|
531
|
-
const chunk =
|
|
621
|
+
const chunk = input.read(length);
|
|
532
622
|
this.copyIn(pointer, chunk);
|
|
533
623
|
return BigInt(chunk.byteLength);
|
|
534
624
|
}
|
|
@@ -566,13 +656,20 @@ export class WaliFileDescriptorTable {
|
|
|
566
656
|
}
|
|
567
657
|
}
|
|
568
658
|
write(fd, pointer, length) {
|
|
659
|
+
return this.writeBytes(fd, pointer, length);
|
|
660
|
+
}
|
|
661
|
+
/** One vector is one destination write and one shared-offset advance. */
|
|
662
|
+
writeBytes(fd, pointer, length, gathered) {
|
|
569
663
|
try {
|
|
570
664
|
const stdio = this.stdioFd(fd);
|
|
665
|
+
if (this.terminalFd(fd))
|
|
666
|
+
return (this.statusFlags(fd) & O_ACCMODE) === 0 ? -9n
|
|
667
|
+
: this.options.emitTerminal?.(gathered ?? this.copyOut(pointer, length)) ?? -95n;
|
|
571
668
|
if (stdio === 1 || stdio === 2)
|
|
572
|
-
return this.options.emit(stdio, this.copyOut(pointer, length));
|
|
669
|
+
return this.options.emit(stdio, gathered ?? this.copyOut(pointer, length));
|
|
573
670
|
const special = this.specialDescriptors.get(fd);
|
|
574
671
|
if (special)
|
|
575
|
-
return special.write?.(this.copyOut(pointer, length)) ?? -9n;
|
|
672
|
+
return special.write?.(gathered ?? this.copyOut(pointer, length)) ?? -9n;
|
|
576
673
|
const file = this.files.get(fd);
|
|
577
674
|
if (!file)
|
|
578
675
|
throw errno("EBADF");
|
|
@@ -585,28 +682,32 @@ export class WaliFileDescriptorTable {
|
|
|
585
682
|
if (file.flags & O_APPEND)
|
|
586
683
|
file.position = fs.statSync(file.path).size;
|
|
587
684
|
// A view, not a copy: the file copies what it keeps (2026-09-27, one copy of every write fewer).
|
|
588
|
-
|
|
589
|
-
|
|
685
|
+
if (!gathered)
|
|
686
|
+
assertRange(this.options.memory(), pointer, length);
|
|
687
|
+
fs.writeRangeSync(file.path, file.position, gathered ?? this.memoryBytes().subarray(pointer, pointer + length));
|
|
590
688
|
file.position += length;
|
|
591
689
|
return BigInt(length);
|
|
592
690
|
}
|
|
593
|
-
const
|
|
691
|
+
const value = gathered ?? this.copyOut(pointer, length);
|
|
594
692
|
const current = fs.readFileSync(file.path);
|
|
595
693
|
if (file.flags & O_APPEND)
|
|
596
694
|
file.position = current.byteLength;
|
|
597
|
-
const size = Math.max(current.byteLength, file.position +
|
|
695
|
+
const size = Math.max(current.byteLength, file.position + value.byteLength);
|
|
598
696
|
const next = new Uint8Array(size);
|
|
599
697
|
next.set(current);
|
|
600
|
-
next.set(
|
|
698
|
+
next.set(value, file.position);
|
|
601
699
|
fs.writeFileSync(file.path, next);
|
|
602
|
-
file.position +=
|
|
603
|
-
return BigInt(
|
|
700
|
+
file.position += value.byteLength;
|
|
701
|
+
return BigInt(value.byteLength);
|
|
604
702
|
}
|
|
605
703
|
catch (error) {
|
|
606
704
|
return negativeErrno(error);
|
|
607
705
|
}
|
|
608
706
|
}
|
|
609
707
|
pread(fd, pointer, length, offset) {
|
|
708
|
+
const special = this.specialDescriptors.get(fd);
|
|
709
|
+
if (special)
|
|
710
|
+
return !special.seek ? -29n : !Number.isSafeInteger(offset) || offset < 0 ? -22n : this.read(fd, pointer, length);
|
|
610
711
|
const file = this.files.get(fd);
|
|
611
712
|
if (!file)
|
|
612
713
|
return -9n;
|
|
@@ -622,6 +723,9 @@ export class WaliFileDescriptorTable {
|
|
|
622
723
|
}
|
|
623
724
|
}
|
|
624
725
|
pwrite(fd, pointer, length, offset) {
|
|
726
|
+
const special = this.specialDescriptors.get(fd);
|
|
727
|
+
if (special)
|
|
728
|
+
return !special.seek ? -29n : !Number.isSafeInteger(offset) || offset < 0 ? -22n : this.write(fd, pointer, length);
|
|
625
729
|
const file = this.files.get(fd);
|
|
626
730
|
if (!file)
|
|
627
731
|
return -9n;
|
|
@@ -641,6 +745,9 @@ export class WaliFileDescriptorTable {
|
|
|
641
745
|
}
|
|
642
746
|
/** pread over a vector: the same answers, each buffer in turn at the offset, the position unmoved. */
|
|
643
747
|
preadv(fd, pointer, count, offset) {
|
|
748
|
+
const special = this.specialDescriptors.get(fd);
|
|
749
|
+
if (special)
|
|
750
|
+
return !special.seek ? -29n : !Number.isSafeInteger(offset) || offset < 0 ? -22n : this.readv(fd, pointer, count);
|
|
644
751
|
const file = this.files.get(fd);
|
|
645
752
|
if (!file)
|
|
646
753
|
return -9n;
|
|
@@ -657,6 +764,9 @@ export class WaliFileDescriptorTable {
|
|
|
657
764
|
}
|
|
658
765
|
/** pwrite over a vector, as pwrite: at the offset whatever O_APPEND says, the position unmoved. */
|
|
659
766
|
pwritev(fd, pointer, count, offset) {
|
|
767
|
+
const special = this.specialDescriptors.get(fd);
|
|
768
|
+
if (special)
|
|
769
|
+
return !special.seek ? -29n : !Number.isSafeInteger(offset) || offset < 0 ? -22n : this.writev(fd, pointer, count);
|
|
660
770
|
const file = this.files.get(fd);
|
|
661
771
|
if (!file)
|
|
662
772
|
return -9n;
|
|
@@ -689,29 +799,45 @@ export class WaliFileDescriptorTable {
|
|
|
689
799
|
return total;
|
|
690
800
|
}
|
|
691
801
|
writev(fd, pointer, count) {
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
802
|
+
const value = gatherWaliWriteVector(this.options.memory(), pointer, count);
|
|
803
|
+
return typeof value === "bigint" ? value : this.writeBytes(fd, 0, value.byteLength, value);
|
|
804
|
+
}
|
|
805
|
+
// OpenFileState::create caches dirents in Emscripten file_table.cpp/h
|
|
806
|
+
// (Copyright 2021, MIT/NCSA), pinned at 862dc0d4. Each process retains the
|
|
807
|
+
// same description on dup/fork; kernel storage supplies the entry metadata.
|
|
808
|
+
directoryEntries(path) {
|
|
809
|
+
const fs = this.fs();
|
|
810
|
+
const entries = [];
|
|
811
|
+
const virtual = path === "/proc" ? ["self"] : path === "/proc/self" ? ["fd"]
|
|
812
|
+
: path === "/proc/self/fd" ? this.descriptors.entries.flatMap((entry, fd) => entry ? [String(fd)] : []) : undefined;
|
|
813
|
+
for (const name of [".", "..", ...(virtual ?? fs.readdirSync(path))]) {
|
|
814
|
+
const entryPath = name === "." ? path : name === ".."
|
|
815
|
+
? normalizePath(path, "..") : normalizePath(path, name);
|
|
816
|
+
const stat = this.virtualStat(entryPath, true) ?? statIfPresent(fs, entryPath, true);
|
|
817
|
+
if (!stat)
|
|
818
|
+
continue; // A backend entry removed during enumeration.
|
|
819
|
+
const link = this.virtualLink(entryPath) ?? this.readSymlink(entryPath);
|
|
820
|
+
const type = link !== undefined || stat.isSymbolicLink?.() ? 10
|
|
821
|
+
: (stat.mode ?? 0) & 0o170000 ? ((stat.mode & 0o170000) >>> 12)
|
|
822
|
+
: stat.isDirectory() ? 4 : 8;
|
|
823
|
+
entries.push({ name, inode: stat.ino ?? 1, type });
|
|
824
|
+
}
|
|
825
|
+
return entries;
|
|
702
826
|
}
|
|
703
827
|
getdents64(fd, pointer, count) {
|
|
704
828
|
try {
|
|
829
|
+
if (!Number.isSafeInteger(count) || count < 24)
|
|
830
|
+
throw errno("EINVAL");
|
|
705
831
|
const directory = this.files.get(fd);
|
|
706
832
|
if (!directory)
|
|
707
833
|
throw errno("EBADF");
|
|
708
834
|
if (!directory.directory)
|
|
709
835
|
throw errno("ENOTDIR");
|
|
710
|
-
const entries =
|
|
836
|
+
const entries = directory.entries ??= this.directoryEntries(directory.path);
|
|
711
837
|
let written = 0;
|
|
712
838
|
while (directory.position < entries.length) {
|
|
713
|
-
const
|
|
714
|
-
const nameBytes = new TextEncoder().encode(name);
|
|
839
|
+
const entry = entries[directory.position];
|
|
840
|
+
const nameBytes = new TextEncoder().encode(entry.name);
|
|
715
841
|
const recordLength = alignTo(19 + nameBytes.byteLength + 1, 8);
|
|
716
842
|
if (recordLength > count - written) {
|
|
717
843
|
if (written === 0)
|
|
@@ -720,13 +846,10 @@ export class WaliFileDescriptorTable {
|
|
|
720
846
|
}
|
|
721
847
|
const record = pointer + written;
|
|
722
848
|
assertRange(this.options.memory(), record, recordLength);
|
|
723
|
-
|
|
724
|
-
? normalizePath(directory.path, "..") : normalizePath(directory.path, name);
|
|
725
|
-
const stat = this.fs().statSync(entryPath);
|
|
726
|
-
this.memoryView().setBigUint64(record, BigInt(directory.position + 1), true);
|
|
849
|
+
this.memoryView().setBigUint64(record, BigInt(entry.inode), true);
|
|
727
850
|
this.memoryView().setBigInt64(record + 8, BigInt(directory.position + 1), true);
|
|
728
851
|
this.memoryView().setUint16(record + 16, recordLength, true);
|
|
729
|
-
this.memoryView().setUint8(record + 18,
|
|
852
|
+
this.memoryView().setUint8(record + 18, entry.type);
|
|
730
853
|
this.memoryBytes().fill(0, record + 19, record + recordLength);
|
|
731
854
|
this.memoryBytes().set(nameBytes, record + 19);
|
|
732
855
|
directory.position += 1;
|
|
@@ -740,13 +863,14 @@ export class WaliFileDescriptorTable {
|
|
|
740
863
|
}
|
|
741
864
|
lseek(fd, offset, whence) {
|
|
742
865
|
try {
|
|
866
|
+
const special = this.specialDescriptors.get(fd);
|
|
867
|
+
if (special)
|
|
868
|
+
return special.seek?.(offset, whence) ?? -29n;
|
|
743
869
|
const file = this.files.get(fd);
|
|
744
870
|
if (!file)
|
|
745
871
|
throw errno("EBADF");
|
|
746
|
-
if (file.directory)
|
|
747
|
-
throw errno("EINVAL");
|
|
748
872
|
const base = whence === SEEK_SET ? 0 : whence === SEEK_CUR ? file.position
|
|
749
|
-
: whence === SEEK_END ? this.fs().statSync(file.path).size : undefined;
|
|
873
|
+
: whence === SEEK_END && !file.directory ? this.fs().statSync(file.path).size : undefined;
|
|
750
874
|
// An offset or a position past 2^53 - 1 is not one a double holds (musl 0005's lseek says the same).
|
|
751
875
|
if (base === undefined || !Number.isSafeInteger(offset) || base + offset < 0 || !Number.isSafeInteger(base + offset))
|
|
752
876
|
throw errno("EINVAL");
|
|
@@ -759,6 +883,9 @@ export class WaliFileDescriptorTable {
|
|
|
759
883
|
}
|
|
760
884
|
ftruncate(fd, length) {
|
|
761
885
|
try {
|
|
886
|
+
const special = this.specialDescriptors.get(fd);
|
|
887
|
+
if (special)
|
|
888
|
+
return special.truncate?.(length) ?? -22n;
|
|
762
889
|
const file = this.files.get(fd);
|
|
763
890
|
if (!file)
|
|
764
891
|
throw errno("EBADF");
|
|
@@ -786,9 +913,44 @@ export class WaliFileDescriptorTable {
|
|
|
786
913
|
return negativeErrno(error);
|
|
787
914
|
}
|
|
788
915
|
}
|
|
916
|
+
truncate(path, length) {
|
|
917
|
+
try {
|
|
918
|
+
const resolved = this.resolveFilesystemPath(this.resolve(path));
|
|
919
|
+
if (waliDeviceStat(resolved))
|
|
920
|
+
throw errno("EINVAL");
|
|
921
|
+
const fs = this.fs();
|
|
922
|
+
const stat = fs.statSync(resolved);
|
|
923
|
+
if (!stat.isFile())
|
|
924
|
+
throw errno("EISDIR");
|
|
925
|
+
checkFilePermission(stat, 2);
|
|
926
|
+
if (!Number.isSafeInteger(length) || length < 0)
|
|
927
|
+
throw errno("EINVAL");
|
|
928
|
+
if (fs.truncateSync)
|
|
929
|
+
fs.truncateSync(resolved, length);
|
|
930
|
+
else {
|
|
931
|
+
const resized = new Uint8Array(length);
|
|
932
|
+
resized.set(fs.readFileSync(resolved).subarray(0, length));
|
|
933
|
+
fs.writeFileSync(resolved, resized);
|
|
934
|
+
}
|
|
935
|
+
return 0n;
|
|
936
|
+
}
|
|
937
|
+
catch (error) {
|
|
938
|
+
return negativeErrno(error);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
789
941
|
stat(path, pointer, lstat) {
|
|
790
942
|
try {
|
|
791
943
|
const resolved = this.resolveFilesystemPath(this.resolve(path), !lstat);
|
|
944
|
+
const virtual = this.virtualStat(resolved, lstat);
|
|
945
|
+
if (virtual) {
|
|
946
|
+
writeStat(this.memoryView(), pointer, virtual);
|
|
947
|
+
return 0n;
|
|
948
|
+
}
|
|
949
|
+
const device = waliDeviceStat(resolved);
|
|
950
|
+
if (device) {
|
|
951
|
+
writeStat(this.memoryView(), pointer, device);
|
|
952
|
+
return 0n;
|
|
953
|
+
}
|
|
792
954
|
const fs = this.fs();
|
|
793
955
|
// A snapshot store's statSync answers ENOENT itself: asking existsSync first resolved the path twice.
|
|
794
956
|
if (!fs.linkTargetSync && !fs.existsSync(resolved))
|
|
@@ -805,15 +967,31 @@ export class WaliFileDescriptorTable {
|
|
|
805
967
|
}
|
|
806
968
|
statAt(dirfd, path, pointer, flags) {
|
|
807
969
|
try {
|
|
808
|
-
if (flags & ~AT_SYMLINK_NOFOLLOW)
|
|
970
|
+
if (flags & ~(AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH | AT_NO_AUTOMOUNT))
|
|
809
971
|
throw errno("EINVAL");
|
|
972
|
+
// getFileAt with AT_EMPTY_PATH addresses the open description directly,
|
|
973
|
+
// including a non-directory fd; AT_FDCWD means the current directory.
|
|
974
|
+
if (!path && (flags & AT_EMPTY_PATH)) {
|
|
975
|
+
return dirfd === AT_FDCWD ? this.stat(this.cwd, pointer, false) : this.fstat(dirfd, pointer);
|
|
976
|
+
}
|
|
810
977
|
const noFollow = Boolean(flags & AT_SYMLINK_NOFOLLOW);
|
|
811
978
|
const resolved = this.resolveFilesystemPath(this.resolveAt(dirfd, path), !noFollow);
|
|
979
|
+
const virtual = this.virtualStat(resolved, noFollow);
|
|
980
|
+
if (virtual) {
|
|
981
|
+
writeStat(this.memoryView(), pointer, virtual);
|
|
982
|
+
return 0n;
|
|
983
|
+
}
|
|
984
|
+
const device = waliDeviceStat(resolved);
|
|
985
|
+
if (device) {
|
|
986
|
+
writeStat(this.memoryView(), pointer, device);
|
|
987
|
+
return 0n;
|
|
988
|
+
}
|
|
812
989
|
const fs = this.fs();
|
|
813
990
|
if (!fs.linkTargetSync && !fs.existsSync(resolved))
|
|
814
991
|
throw errno("ENOENT");
|
|
815
992
|
const target = noFollow ? this.readSymlink(resolved) : undefined;
|
|
816
|
-
writeStat(this.memoryView(), pointer, target === undefined
|
|
993
|
+
writeStat(this.memoryView(), pointer, target === undefined
|
|
994
|
+
? (noFollow && fs.lstatSync ? fs.lstatSync(resolved) : fs.statSync(resolved)) : syntheticSymlinkStat(target));
|
|
817
995
|
return 0n;
|
|
818
996
|
}
|
|
819
997
|
catch (error) {
|
|
@@ -826,27 +1004,38 @@ export class WaliFileDescriptorTable {
|
|
|
826
1004
|
writeStat(this.memoryView(), pointer, syntheticTerminalStat());
|
|
827
1005
|
return 0n;
|
|
828
1006
|
}
|
|
1007
|
+
const special = this.specialDescriptors.get(fd);
|
|
1008
|
+
const stat = special?.stat?.();
|
|
1009
|
+
if (stat) {
|
|
1010
|
+
writeStat(this.memoryView(), pointer, stat);
|
|
1011
|
+
return 0n;
|
|
1012
|
+
}
|
|
829
1013
|
const file = this.files.get(fd);
|
|
830
1014
|
if (!file)
|
|
831
1015
|
throw errno("EBADF");
|
|
832
|
-
writeStat(this.memoryView(), pointer, this.fs().statSync(file.path));
|
|
1016
|
+
writeStat(this.memoryView(), pointer, this.virtualStat(file.path, false) ?? this.fs().statSync(file.path));
|
|
833
1017
|
return 0n;
|
|
834
1018
|
}
|
|
835
1019
|
catch (error) {
|
|
836
1020
|
return negativeErrno(error);
|
|
837
1021
|
}
|
|
838
1022
|
}
|
|
839
|
-
|
|
840
|
-
try {
|
|
841
|
-
return this.fs().existsSync(this.resolveFilesystemPath(this.resolve(path))) ? 0n : -2n;
|
|
842
|
-
}
|
|
843
|
-
catch (error) {
|
|
844
|
-
return negativeErrno(error);
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
accessAt(dirfd, path) {
|
|
1023
|
+
accessAt(dirfd, path, mode, flags) {
|
|
848
1024
|
try {
|
|
849
|
-
|
|
1025
|
+
if (mode & ~7 || flags & ~(AT_EACCESS | AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH))
|
|
1026
|
+
throw errno("EINVAL");
|
|
1027
|
+
const noFollow = Boolean(flags & AT_SYMLINK_NOFOLLOW);
|
|
1028
|
+
const resolved = !path && (flags & AT_EMPTY_PATH)
|
|
1029
|
+
? dirfd === AT_FDCWD ? this.cwd : this.files.get(dirfd)?.path
|
|
1030
|
+
: this.resolveFilesystemPath(this.resolveAt(dirfd, path), !noFollow);
|
|
1031
|
+
if (resolved === undefined)
|
|
1032
|
+
throw errno("EBADF");
|
|
1033
|
+
const fs = this.fs();
|
|
1034
|
+
const target = noFollow ? this.readSymlink(resolved) : undefined;
|
|
1035
|
+
const stat = this.virtualStat(resolved, noFollow) ?? waliDeviceStat(resolved) ?? (target !== undefined ? syntheticSymlinkStat(target)
|
|
1036
|
+
: noFollow && fs.lstatSync ? fs.lstatSync(resolved) : fs.statSync(resolved));
|
|
1037
|
+
checkFilePermission(stat, mode);
|
|
1038
|
+
return 0n;
|
|
850
1039
|
}
|
|
851
1040
|
catch (error) {
|
|
852
1041
|
return negativeErrno(error);
|
|
@@ -914,24 +1103,34 @@ export class WaliFileDescriptorTable {
|
|
|
914
1103
|
return 0n;
|
|
915
1104
|
}
|
|
916
1105
|
getcwd(pointer, size) {
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
1106
|
+
try {
|
|
1107
|
+
if (!pointer && size > 0)
|
|
1108
|
+
throw errno("EFAULT");
|
|
1109
|
+
if (size === 0)
|
|
1110
|
+
throw errno("EINVAL");
|
|
1111
|
+
if (this.cwd !== "/" && !this.fs().existsSync(this.cwd))
|
|
1112
|
+
throw errno("ENOENT");
|
|
1113
|
+
const encoded = new TextEncoder().encode(`${this.cwd}\0`);
|
|
1114
|
+
if (encoded.byteLength > size)
|
|
1115
|
+
throw errno("ERANGE");
|
|
1116
|
+
this.copyIn(pointer, encoded);
|
|
1117
|
+
return BigInt(encoded.byteLength);
|
|
1118
|
+
}
|
|
1119
|
+
catch (error) {
|
|
1120
|
+
return negativeErrno(error);
|
|
1121
|
+
}
|
|
922
1122
|
}
|
|
923
1123
|
mkdir(path, mode) {
|
|
924
|
-
return this.
|
|
925
|
-
const resolved = this.resolveFilesystemPath(this.resolve(path), false);
|
|
926
|
-
this.fs().mkdirSync(resolved);
|
|
927
|
-
this.fs().chmodSync?.(resolved, mode & 0o7777);
|
|
928
|
-
});
|
|
1124
|
+
return this.mkdirAt(AT_FDCWD, path, mode);
|
|
929
1125
|
}
|
|
930
1126
|
mkdirAt(dirfd, path, mode) {
|
|
931
1127
|
try {
|
|
932
|
-
const resolved = this.resolveFilesystemPath(this.resolveAt(dirfd, path), false);
|
|
1128
|
+
const resolved = this.resolveFilesystemPath(this.resolveAt(dirfd, path), false, true);
|
|
1129
|
+
checkFileName(resolved);
|
|
1130
|
+
if (statIfPresent(this.fs(), resolved, true))
|
|
1131
|
+
throw errno("EEXIST");
|
|
933
1132
|
this.fs().mkdirSync(resolved);
|
|
934
|
-
this.fs().chmodSync?.(resolved, mode & 0o7777);
|
|
1133
|
+
this.fs().chmodSync?.(resolved, mode & 0o7777 & ~this.fileCreationMask);
|
|
935
1134
|
return 0n;
|
|
936
1135
|
}
|
|
937
1136
|
catch (error) {
|
|
@@ -976,21 +1175,38 @@ export class WaliFileDescriptorTable {
|
|
|
976
1175
|
try {
|
|
977
1176
|
if (flags & ~AT_REMOVEDIR)
|
|
978
1177
|
throw errno("EINVAL");
|
|
1178
|
+
const removeDirectory = Boolean(flags & AT_REMOVEDIR);
|
|
1179
|
+
const leaf = path.replace(/\/+$/u, "").split("/").at(-1);
|
|
1180
|
+
if (removeDirectory && leaf === ".")
|
|
1181
|
+
throw errno("EINVAL");
|
|
979
1182
|
const resolved = this.resolveFilesystemPath(this.resolveAt(dirfd, path), false);
|
|
980
|
-
|
|
981
|
-
|
|
1183
|
+
const fs = this.fs();
|
|
1184
|
+
const stat = fs.lstatSync?.(resolved) ?? fs.statSync(resolved);
|
|
1185
|
+
if (resolved === "/")
|
|
1186
|
+
throw errno("EBUSY");
|
|
1187
|
+
if (stat.isDirectory()) {
|
|
1188
|
+
if (!removeDirectory)
|
|
1189
|
+
throw errno("EISDIR");
|
|
1190
|
+
if (fs.readdirSync(resolved).length > 0)
|
|
1191
|
+
throw errno("ENOTEMPTY");
|
|
1192
|
+
}
|
|
1193
|
+
else if (removeDirectory)
|
|
1194
|
+
throw errno("ENOTDIR");
|
|
1195
|
+
checkFilePermission(fs.statSync(resolved.slice(0, resolved.lastIndexOf("/")) || "/"), 2);
|
|
1196
|
+
if (removeDirectory)
|
|
1197
|
+
fs.rmdirSync(resolved);
|
|
982
1198
|
else
|
|
983
|
-
|
|
1199
|
+
fs.unlinkSync(resolved);
|
|
984
1200
|
return 0n;
|
|
985
1201
|
}
|
|
986
1202
|
catch (error) {
|
|
987
1203
|
return negativeErrno(error);
|
|
988
1204
|
}
|
|
989
1205
|
}
|
|
990
|
-
rename(from, to) {
|
|
1206
|
+
rename(from, to, fromDirfd = AT_FDCWD, toDirfd = AT_FDCWD) {
|
|
991
1207
|
return this.mutate(() => {
|
|
992
|
-
const source = this.resolveFilesystemPath(this.
|
|
993
|
-
const destination = this.resolveFilesystemPath(this.
|
|
1208
|
+
const source = this.resolveFilesystemPath(this.resolveAt(fromDirfd, from), false);
|
|
1209
|
+
const destination = this.resolveFilesystemPath(this.resolveAt(toDirfd, to), false);
|
|
994
1210
|
// Moving a directory the held tree is under moves the tree: the libc's to release first (ADR-0047).
|
|
995
1211
|
if (this.privateTree && (underRoot(this.privateTree.root, source) || underRoot(this.privateTree.root, destination)))
|
|
996
1212
|
throw errno("EREMOTE");
|
|
@@ -1003,11 +1219,14 @@ export class WaliFileDescriptorTable {
|
|
|
1003
1219
|
if (fs.existsSync(source)) {
|
|
1004
1220
|
if (source === destination)
|
|
1005
1221
|
return;
|
|
1006
|
-
|
|
1222
|
+
if (source === "/" || destination === "/")
|
|
1223
|
+
throw errno("EBUSY");
|
|
1224
|
+
checkFileName(destination);
|
|
1225
|
+
const directory = (fs.lstatSync?.(source) ?? fs.statSync(source)).isDirectory();
|
|
1007
1226
|
if (directory && destination.startsWith(`${source}/`))
|
|
1008
1227
|
throw errno("EINVAL");
|
|
1009
1228
|
if (fs.existsSync(destination)) {
|
|
1010
|
-
const into = fs.statSync(destination).isDirectory();
|
|
1229
|
+
const into = (fs.lstatSync?.(destination) ?? fs.statSync(destination)).isDirectory();
|
|
1011
1230
|
if (directory && !into)
|
|
1012
1231
|
throw errno("ENOTDIR");
|
|
1013
1232
|
if (!directory && into)
|
|
@@ -1051,8 +1270,8 @@ export class WaliFileDescriptorTable {
|
|
|
1051
1270
|
};
|
|
1052
1271
|
const atimeMs = readTime(0, stat.atimeMs ?? stat.mtimeMs ?? now);
|
|
1053
1272
|
const mtimeMs = readTime(1, stat.mtimeMs ?? now);
|
|
1054
|
-
if (filesystem.utimesSync)
|
|
1055
|
-
filesystem
|
|
1273
|
+
if (filesystem.utimesSync || filesystem.utimesMsSync)
|
|
1274
|
+
setBrowserFileTimes(filesystem, path, atimeMs, mtimeMs);
|
|
1056
1275
|
else if (stat.isFile())
|
|
1057
1276
|
filesystem.writeFileSync(path, filesystem.readFileSync(path));
|
|
1058
1277
|
return 0n;
|
|
@@ -1063,12 +1282,12 @@ export class WaliFileDescriptorTable {
|
|
|
1063
1282
|
}
|
|
1064
1283
|
readlink(path, pointer, size, dirfd) {
|
|
1065
1284
|
try {
|
|
1066
|
-
if (!Number.isSafeInteger(size) || size
|
|
1285
|
+
if (!Number.isSafeInteger(size) || size <= 0)
|
|
1067
1286
|
throw errno("EINVAL");
|
|
1068
|
-
|
|
1287
|
+
let resolved = this.resolveAt(dirfd, path);
|
|
1069
1288
|
if (this.privateTree && underRoot(resolved, this.privateTree.root))
|
|
1070
1289
|
throw errno("EREMOTE");
|
|
1071
|
-
let target;
|
|
1290
|
+
let target = this.virtualLink(resolved);
|
|
1072
1291
|
if (resolved === "/proc/self/exe")
|
|
1073
1292
|
target = this.options.executablePath ?? "/usr/bin/wali-program";
|
|
1074
1293
|
const descriptorMatch = /^\/proc\/self\/fd\/(\d+)$/u.exec(resolved);
|
|
@@ -1077,12 +1296,14 @@ export class WaliFileDescriptorTable {
|
|
|
1077
1296
|
if (descriptor)
|
|
1078
1297
|
target = descriptor.path;
|
|
1079
1298
|
}
|
|
1299
|
+
if (target === undefined)
|
|
1300
|
+
resolved = this.resolveFilesystemPath(resolved, false);
|
|
1080
1301
|
target ??= this.readSymlink(resolved);
|
|
1081
1302
|
// musl realpath(3) probes every path component with readlink(2).
|
|
1082
1303
|
// Existing non-symlinks must report EINVAL; ENOENT means the component
|
|
1083
1304
|
// itself is missing and makes canonicalization fail.
|
|
1084
1305
|
if (!target)
|
|
1085
|
-
return this.fs().existsSync(resolved) ? -22n : -2n;
|
|
1306
|
+
return this.virtualStat(resolved, false) || waliDeviceStat(resolved) || this.fs().existsSync(resolved) ? -22n : -2n;
|
|
1086
1307
|
const encoded = new TextEncoder().encode(target).subarray(0, size);
|
|
1087
1308
|
this.copyIn(pointer, encoded);
|
|
1088
1309
|
return BigInt(encoded.byteLength);
|
|
@@ -1094,6 +1315,7 @@ export class WaliFileDescriptorTable {
|
|
|
1094
1315
|
symlink(target, link, dirfd) {
|
|
1095
1316
|
try {
|
|
1096
1317
|
const resolved = this.resolveFilesystemPath(this.resolveAt(dirfd, link), false);
|
|
1318
|
+
checkFileName(resolved);
|
|
1097
1319
|
writeWaliSymlink(this.fs(), target, resolved);
|
|
1098
1320
|
return 0n;
|
|
1099
1321
|
}
|
|
@@ -1101,8 +1323,21 @@ export class WaliFileDescriptorTable {
|
|
|
1101
1323
|
return negativeErrno(error);
|
|
1102
1324
|
}
|
|
1103
1325
|
}
|
|
1104
|
-
resolveFilesystemPath(path, followFinal = true) {
|
|
1105
|
-
const resolved = resolveWaliSymlinkPath(this.fs(), path, followFinal
|
|
1326
|
+
resolveFilesystemPath(path, followFinal = true, allowMissingDirectory = false) {
|
|
1327
|
+
const resolved = resolveWaliSymlinkPath(this.fs(), path, followFinal, allowMissingDirectory, candidate => {
|
|
1328
|
+
const target = this.virtualLink(candidate, false);
|
|
1329
|
+
if (target !== undefined)
|
|
1330
|
+
return { directory: false, target };
|
|
1331
|
+
if (["/proc", "/proc/self", "/proc/self/fd"].includes(candidate))
|
|
1332
|
+
return { directory: true };
|
|
1333
|
+
const fd = this.procDescriptor(candidate);
|
|
1334
|
+
if (fd === undefined || !this.hasFd(fd))
|
|
1335
|
+
return;
|
|
1336
|
+
const file = this.files.get(fd);
|
|
1337
|
+
// Directory magic links must support descendants (fd/N/name). Files
|
|
1338
|
+
// remain magic leaves so reopen applies the caller's requested flags.
|
|
1339
|
+
return file?.directory ? { directory: false, target: file.path } : { directory: false };
|
|
1340
|
+
});
|
|
1106
1341
|
// A held private tree is the libc's: a path that reaches it here (a symlink
|
|
1107
1342
|
// elsewhere, a directory descriptor) is EREMOTE, and the libc releases the
|
|
1108
1343
|
// tree and asks again (ADR-0047 §3).
|
|
@@ -1110,11 +1345,119 @@ export class WaliFileDescriptorTable {
|
|
|
1110
1345
|
throw errno("EREMOTE");
|
|
1111
1346
|
return resolved;
|
|
1112
1347
|
}
|
|
1348
|
+
procDescriptor(path) {
|
|
1349
|
+
const match = /^\/proc\/self\/fd\/(\d+)$/u.exec(path);
|
|
1350
|
+
return match ? Number(match[1]) : undefined;
|
|
1351
|
+
}
|
|
1352
|
+
virtualLink(path, descriptors = true) {
|
|
1353
|
+
if (path === "/dev/fd")
|
|
1354
|
+
return "/proc/self/fd";
|
|
1355
|
+
if (path === "/dev/stdin")
|
|
1356
|
+
return "/proc/self/fd/0";
|
|
1357
|
+
if (path === "/dev/stdout")
|
|
1358
|
+
return "/proc/self/fd/1";
|
|
1359
|
+
if (path === "/dev/stderr")
|
|
1360
|
+
return "/proc/self/fd/2";
|
|
1361
|
+
if (!descriptors)
|
|
1362
|
+
return;
|
|
1363
|
+
const fd = this.procDescriptor(path);
|
|
1364
|
+
if (fd === undefined)
|
|
1365
|
+
return;
|
|
1366
|
+
const description = this.descriptors.get(fd);
|
|
1367
|
+
if (!description)
|
|
1368
|
+
throw errno("ENOENT");
|
|
1369
|
+
const file = this.files.get(fd);
|
|
1370
|
+
if (file)
|
|
1371
|
+
return file.path;
|
|
1372
|
+
if (description.kind === "pipe")
|
|
1373
|
+
return `pipe:[${description.value.pipe + 1}]`;
|
|
1374
|
+
const special = this.specialDescriptors.get(fd);
|
|
1375
|
+
const rdev = special?.stat?.().rdev;
|
|
1376
|
+
if (rdev !== undefined)
|
|
1377
|
+
return rdev === 0x103 ? "/dev/null" : rdev === 0x108 ? "/dev/random"
|
|
1378
|
+
: rdev === 0x109 ? "/dev/urandom" : "/dev/tty";
|
|
1379
|
+
if (special?.stdio !== undefined)
|
|
1380
|
+
return "/dev/tty";
|
|
1381
|
+
return `anon_inode:[${description.kind}]`;
|
|
1382
|
+
}
|
|
1383
|
+
virtualStat(path, noFollow) {
|
|
1384
|
+
if (noFollow) {
|
|
1385
|
+
const target = this.virtualLink(path);
|
|
1386
|
+
if (target !== undefined)
|
|
1387
|
+
return syntheticSymlinkStat(target);
|
|
1388
|
+
}
|
|
1389
|
+
if (["/proc", "/proc/self", "/proc/self/fd"].includes(path))
|
|
1390
|
+
return {
|
|
1391
|
+
isFile: () => false, isDirectory: () => true, size: 0, mode: 0o040555,
|
|
1392
|
+
};
|
|
1393
|
+
const fd = this.procDescriptor(path);
|
|
1394
|
+
if (fd === undefined)
|
|
1395
|
+
return;
|
|
1396
|
+
const description = this.descriptors.get(fd);
|
|
1397
|
+
if (!description)
|
|
1398
|
+
throw errno("ENOENT");
|
|
1399
|
+
const file = this.files.get(fd);
|
|
1400
|
+
if (file)
|
|
1401
|
+
return this.fs().statSync(file.path);
|
|
1402
|
+
if (description.kind === "pipe")
|
|
1403
|
+
return {
|
|
1404
|
+
isFile: () => false, isDirectory: () => false, size: 0, mode: 0o010666,
|
|
1405
|
+
ino: description.value.pipe + 1,
|
|
1406
|
+
};
|
|
1407
|
+
return this.specialDescriptors.get(fd)?.stat?.() ?? syntheticTerminalStat();
|
|
1408
|
+
}
|
|
1409
|
+
reopenDescriptor(fd, flags, mode) {
|
|
1410
|
+
const description = this.descriptors.get(fd);
|
|
1411
|
+
if (!description)
|
|
1412
|
+
throw errno("ENOENT");
|
|
1413
|
+
const stat = this.virtualStat(`/proc/self/fd/${fd}`, false);
|
|
1414
|
+
if ((flags & O_DIRECTORY) && !stat.isDirectory())
|
|
1415
|
+
throw errno("ENOTDIR");
|
|
1416
|
+
if ((flags & O_ACCMODE) !== O_WRONLY)
|
|
1417
|
+
checkFilePermission(stat, 4);
|
|
1418
|
+
if ((flags & O_ACCMODE) !== 0)
|
|
1419
|
+
checkFilePermission(stat, 2);
|
|
1420
|
+
const file = this.files.get(fd);
|
|
1421
|
+
if (file)
|
|
1422
|
+
return this.openResolved(file.path, flags & ~(O_CREAT | O_EXCL), mode);
|
|
1423
|
+
if (description.kind === "pipe" && this.options.descriptorState) {
|
|
1424
|
+
const host = new WaliSharedPipeHost(this.options.memory, { ...this.options.descriptorState,
|
|
1425
|
+
table: this.descriptors.sharedEntries.buffer }, this.descriptors);
|
|
1426
|
+
const result = host.reopen(fd, flags);
|
|
1427
|
+
if (result < 0n)
|
|
1428
|
+
throw errno(result === -24n ? "EMFILE" : "EOPNOTSUPP");
|
|
1429
|
+
return Number(result);
|
|
1430
|
+
}
|
|
1431
|
+
const target = this.virtualLink(`/proc/self/fd/${fd}`);
|
|
1432
|
+
if (target === "/dev/tty" && this.specialDescriptors.get(fd)?.terminal)
|
|
1433
|
+
return this.openResolved(target, flags, mode);
|
|
1434
|
+
const device = target && createWaliDevice(target, flags);
|
|
1435
|
+
if (device) {
|
|
1436
|
+
const next = this.allocateFd();
|
|
1437
|
+
this.specialDescriptors.set(next, device);
|
|
1438
|
+
return this.markCloseOnExecNumber(next, flags);
|
|
1439
|
+
}
|
|
1440
|
+
const special = this.specialDescriptors.get(fd);
|
|
1441
|
+
if (special?.stdio !== undefined) {
|
|
1442
|
+
// Host streams currently expose one direction rather than an inode
|
|
1443
|
+
// that can be reopened in either direction. Do not return a descriptor
|
|
1444
|
+
// whose access mode promises I/O its transport cannot perform.
|
|
1445
|
+
if ((flags & O_ACCMODE) !== (special.stdio === 0 ? 0 : O_WRONLY))
|
|
1446
|
+
throw errno("EOPNOTSUPP");
|
|
1447
|
+
const next = this.allocateFd();
|
|
1448
|
+
this.specialDescriptors.set(next, { stdio: special.stdio });
|
|
1449
|
+
Atomics.store(this.descriptors.registry.pipeMetadata, this.descriptors.entries[next].description * 3 + 1, flags & ~O_CLOEXEC_FLAG);
|
|
1450
|
+
return this.markCloseOnExecNumber(next, flags);
|
|
1451
|
+
}
|
|
1452
|
+
throw errno("EOPNOTSUPP");
|
|
1453
|
+
}
|
|
1113
1454
|
readSymlink(path) {
|
|
1114
1455
|
return readWaliSymlink(this.fs(), path);
|
|
1115
1456
|
}
|
|
1116
|
-
dup(fd, target, minimum =
|
|
1117
|
-
|
|
1457
|
+
dup(fd, target, minimum = 0) {
|
|
1458
|
+
if (target === fd && this.hasFd(fd))
|
|
1459
|
+
return BigInt(fd);
|
|
1460
|
+
const maximum = Math.min(this.options.descriptorMaximum ?? 65_535, this.descriptors.descriptorLimit - 1);
|
|
1118
1461
|
const stdio = this.stdioFd(fd);
|
|
1119
1462
|
if (stdio !== undefined) {
|
|
1120
1463
|
const next = target ?? this.allocateFd(minimum);
|
|
@@ -1122,8 +1465,8 @@ export class WaliFileDescriptorTable {
|
|
|
1122
1465
|
return -9n;
|
|
1123
1466
|
if (next === fd)
|
|
1124
1467
|
return BigInt(next);
|
|
1468
|
+
this.close(next);
|
|
1125
1469
|
this.descriptors.set(next, this.descriptors.get(fd));
|
|
1126
|
-
this.descriptorStatusFlags.set(next, this.statusFlags(fd));
|
|
1127
1470
|
this.setSharedStdioAlias(next, stdio);
|
|
1128
1471
|
return BigInt(next);
|
|
1129
1472
|
}
|
|
@@ -1147,7 +1490,7 @@ export class WaliFileDescriptorTable {
|
|
|
1147
1490
|
// wasmfs/syscalls.cpp __syscall_fcntl64 F_DUPFD: every open file,
|
|
1148
1491
|
// including special files, shares its description through the table.
|
|
1149
1492
|
// Our kernel retains process ownership and FD_CLOEXEC semantics.
|
|
1150
|
-
if (argument < 0 || argument > (this.options.descriptorMaximum ?? 65_535))
|
|
1493
|
+
if (argument < 0 || argument >= this.descriptors.descriptorLimit || argument > (this.options.descriptorMaximum ?? 65_535))
|
|
1151
1494
|
return -22n;
|
|
1152
1495
|
if (special?.duplicate)
|
|
1153
1496
|
return special.duplicate(argument, command === 1030);
|
|
@@ -1176,13 +1519,18 @@ export class WaliFileDescriptorTable {
|
|
|
1176
1519
|
if (command === 3)
|
|
1177
1520
|
return BigInt(this.statusFlags(fd));
|
|
1178
1521
|
if (command === 4) {
|
|
1522
|
+
// libsyscall F_SETFL: change only mutable status bits. Access mode,
|
|
1523
|
+
// creation flags and descriptor CLOEXEC are not replaced by the argument.
|
|
1524
|
+
const mutable = O_APPEND | O_NONBLOCK | 0x2000 /* O_ASYNC */ | 0x4000 /* O_DIRECT */ | 0x40000 /* O_NOATIME */;
|
|
1179
1525
|
const stdio = this.stdioFd(fd);
|
|
1180
|
-
if (stdio !== undefined)
|
|
1181
|
-
this.
|
|
1526
|
+
if (stdio !== undefined) {
|
|
1527
|
+
const id = this.descriptors.entries[fd].description;
|
|
1528
|
+
Atomics.store(this.descriptors.registry.pipeMetadata, id * 3 + 1, (this.statusFlags(fd) & ~mutable) | (argument & mutable));
|
|
1529
|
+
}
|
|
1182
1530
|
else {
|
|
1183
1531
|
const file = this.files.get(fd);
|
|
1184
1532
|
if (file)
|
|
1185
|
-
file.flags = (file.flags &
|
|
1533
|
+
file.flags = (file.flags & ~mutable) | (argument & mutable);
|
|
1186
1534
|
}
|
|
1187
1535
|
return 0n;
|
|
1188
1536
|
}
|
|
@@ -1347,6 +1695,9 @@ export class WaliFileDescriptorTable {
|
|
|
1347
1695
|
return BigInt(scan());
|
|
1348
1696
|
}
|
|
1349
1697
|
hasFd(fd) {
|
|
1698
|
+
if (this.options.descriptorState && !this.options.descriptors)
|
|
1699
|
+
return Number.isInteger(fd) && fd >= 0 && fd < 65_536
|
|
1700
|
+
&& Atomics.load(new Int32Array(this.options.descriptorState.table), fd) !== 0;
|
|
1350
1701
|
return this.descriptors.get(fd) !== undefined;
|
|
1351
1702
|
}
|
|
1352
1703
|
/** Descriptor aliases retain the identity of their terminal stream. */
|
|
@@ -1355,24 +1706,46 @@ export class WaliFileDescriptorTable {
|
|
|
1355
1706
|
const state = this.options.descriptorState;
|
|
1356
1707
|
if (fd < 0 || fd >= 65_536)
|
|
1357
1708
|
return;
|
|
1358
|
-
const id = Atomics.load(new Int32Array(state.table), fd);
|
|
1359
|
-
const
|
|
1709
|
+
const id = Atomics.load(this.options.descriptors ? this.descriptors.sharedEntries : new Int32Array(state.table), fd);
|
|
1710
|
+
const encoded = Atomics.load(new Int32Array(state.descriptions), id * 3 + 2);
|
|
1711
|
+
if (encoded === 4)
|
|
1712
|
+
return 0;
|
|
1713
|
+
const stream = encoded - 1;
|
|
1360
1714
|
return stream >= 0 && stream <= 2 ? stream : undefined;
|
|
1361
1715
|
}
|
|
1362
1716
|
return this.specialDescriptors.get(fd)?.stdio;
|
|
1363
1717
|
}
|
|
1718
|
+
terminalFd(fd) {
|
|
1719
|
+
const state = this.options.descriptorState;
|
|
1720
|
+
if (!state)
|
|
1721
|
+
return this.specialDescriptors.get(fd)?.terminal === true;
|
|
1722
|
+
if (fd < 0 || fd >= 65_536)
|
|
1723
|
+
return false;
|
|
1724
|
+
const id = Atomics.load(this.options.descriptors ? this.descriptors.sharedEntries : new Int32Array(state.table), fd);
|
|
1725
|
+
return Atomics.load(new Int32Array(state.descriptions), id * 3 + 2) === 4;
|
|
1726
|
+
}
|
|
1727
|
+
terminalAvailable(pointer) {
|
|
1728
|
+
if (!this.options.terminalStdin)
|
|
1729
|
+
return -95n;
|
|
1730
|
+
if (pointer <= 0 || pointer + 4 > this.options.memory().buffer.byteLength)
|
|
1731
|
+
return -14n;
|
|
1732
|
+
this.memoryView().setInt32(pointer, this.options.terminalStdin.available?.() ?? 0, true);
|
|
1733
|
+
return 0n;
|
|
1734
|
+
}
|
|
1364
1735
|
setSharedStdioAlias(fd, target) {
|
|
1365
1736
|
if (fd < 0 || fd >= (this.options.stdioAliasRegistry?.length ?? 0))
|
|
1366
1737
|
return;
|
|
1367
1738
|
Atomics.store(this.options.stdioAliasRegistry, fd, target ?? -1);
|
|
1368
1739
|
}
|
|
1369
1740
|
statusFlags(fd) {
|
|
1370
|
-
const stored = this.descriptorStatusFlags.get(fd);
|
|
1371
|
-
if (stored !== undefined)
|
|
1372
|
-
return stored;
|
|
1373
1741
|
const stdio = this.stdioFd(fd);
|
|
1374
|
-
if (stdio !== undefined)
|
|
1375
|
-
|
|
1742
|
+
if (stdio !== undefined) {
|
|
1743
|
+
const state = this.options.descriptorState;
|
|
1744
|
+
const table = state && !this.options.descriptors ? new Int32Array(state.table) : this.descriptors.sharedEntries;
|
|
1745
|
+
const metadata = state ? new Int32Array(state.descriptions) : this.descriptors.registry.pipeMetadata;
|
|
1746
|
+
const id = Atomics.load(table, fd);
|
|
1747
|
+
return Atomics.load(metadata, id * 3 + 1) | (stdio === 0 || this.terminalFd(fd) ? 0 : O_WRONLY);
|
|
1748
|
+
}
|
|
1376
1749
|
return this.files.get(fd)?.flags ?? O_RDWR;
|
|
1377
1750
|
}
|
|
1378
1751
|
mutate(operation) {
|
|
@@ -1409,7 +1782,7 @@ export class WaliFileDescriptorTable {
|
|
|
1409
1782
|
else
|
|
1410
1783
|
fs.writeFileSync(node.path, node.content);
|
|
1411
1784
|
fs.chmodSync?.(node.path, node.mode & 0o7777);
|
|
1412
|
-
fs
|
|
1785
|
+
setBrowserFileTimes(fs, node.path, node.atimeMs, node.mtimeMs);
|
|
1413
1786
|
}
|
|
1414
1787
|
}
|
|
1415
1788
|
this.privateTree = undefined;
|
|
@@ -1546,7 +1919,7 @@ export class WaliFileDescriptorTable {
|
|
|
1546
1919
|
else
|
|
1547
1920
|
fs.writeFileSync(node.path, node.content);
|
|
1548
1921
|
fs.chmodSync?.(node.path, node.mode & 0o7777);
|
|
1549
|
-
fs
|
|
1922
|
+
setBrowserFileTimes(fs, node.path, node.atimeMs, node.mtimeMs);
|
|
1550
1923
|
}
|
|
1551
1924
|
}
|
|
1552
1925
|
for (const descriptor of descriptors) {
|
|
@@ -1555,7 +1928,9 @@ export class WaliFileDescriptorTable {
|
|
|
1555
1928
|
if (descriptor.group === descriptor.fd) {
|
|
1556
1929
|
this.files.set(descriptor.fd, {
|
|
1557
1930
|
path: descriptor.path, flags: descriptor.flags, position: descriptor.position,
|
|
1558
|
-
directory: descriptor.directory,
|
|
1931
|
+
directory: descriptor.directory,
|
|
1932
|
+
...(descriptor.directory ? { entries: this.directoryEntries(descriptor.path) } : {}),
|
|
1933
|
+
description: this.nextDescription++,
|
|
1559
1934
|
});
|
|
1560
1935
|
}
|
|
1561
1936
|
else {
|
|
@@ -1570,6 +1945,19 @@ export class WaliFileDescriptorTable {
|
|
|
1570
1945
|
return 0n;
|
|
1571
1946
|
}
|
|
1572
1947
|
}
|
|
1948
|
+
/** WasmFS's single-user mode checks; identity/credential policy is a separate seam. */
|
|
1949
|
+
function checkFilePermission(stat, requested) {
|
|
1950
|
+
const mode = stat.mode ?? (stat.isDirectory() ? 0o777 : 0o666);
|
|
1951
|
+
if (((requested & 4) && !(mode & 0o444))
|
|
1952
|
+
|| ((requested & 2) && !(mode & 0o222))
|
|
1953
|
+
|| ((requested & 1) && !(mode & 0o111)))
|
|
1954
|
+
throw errno("EACCES");
|
|
1955
|
+
}
|
|
1956
|
+
function checkFileName(path) {
|
|
1957
|
+
// WASMFS_NAME_MAX is Linux's 255-byte component limit, not a work budget.
|
|
1958
|
+
if (new TextEncoder().encode(path.slice(path.lastIndexOf("/") + 1)).byteLength > 255)
|
|
1959
|
+
throw errno("ENAMETOOLONG");
|
|
1960
|
+
}
|
|
1573
1961
|
/** preadv's offset: one 64-bit argument, or two 32-bit halves from a libc that splits it (musl's own preadv.c). */
|
|
1574
1962
|
function vectorOffset(offset, high) {
|
|
1575
1963
|
if (high === undefined)
|
|
@@ -1585,19 +1973,11 @@ function normalizePath(cwd, path) {
|
|
|
1585
1973
|
// Most paths a program names are already normal: itself, without the split.
|
|
1586
1974
|
if (isNormalPath(path))
|
|
1587
1975
|
return path;
|
|
1588
|
-
const components = [];
|
|
1589
1976
|
const value = path.startsWith("/") ? path : `${cwd}/${path}`;
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
continue;
|
|
1593
|
-
if (component === "..")
|
|
1594
|
-
components.pop();
|
|
1595
|
-
else
|
|
1596
|
-
components.push(component);
|
|
1597
|
-
}
|
|
1598
|
-
return `/${components.join("/")}`;
|
|
1977
|
+
const normalized = normalizeWaliPath(value);
|
|
1978
|
+
return normalized === "/" ? "/" : normalized.replace(/\/$/u, "");
|
|
1599
1979
|
}
|
|
1600
|
-
/** Absolute
|
|
1980
|
+
/** Absolute with no empty/dot component or trailing slash; backslash is a literal character. */
|
|
1601
1981
|
function isNormalPath(path) {
|
|
1602
1982
|
if (path.charCodeAt(0) !== 47)
|
|
1603
1983
|
return false;
|
|
@@ -1606,8 +1986,6 @@ function isNormalPath(path) {
|
|
|
1606
1986
|
let start = 1;
|
|
1607
1987
|
for (let index = 1; index <= path.length; index += 1) {
|
|
1608
1988
|
const code = index < path.length ? path.charCodeAt(index) : 47;
|
|
1609
|
-
if (code === 92)
|
|
1610
|
-
return false;
|
|
1611
1989
|
if (code !== 47)
|
|
1612
1990
|
continue;
|
|
1613
1991
|
const length = index - start;
|
|
@@ -1637,9 +2015,9 @@ function assertRange(memory, pointer, length) {
|
|
|
1637
2015
|
throw new WebAssembly.RuntimeError("WALI filesystem memory access is out of bounds.");
|
|
1638
2016
|
}
|
|
1639
2017
|
/** The stat of `path`, or undefined when the store says ENOENT; any other failure is thrown. */
|
|
1640
|
-
function statIfPresent(fs, path) {
|
|
2018
|
+
function statIfPresent(fs, path, noFollow = false) {
|
|
1641
2019
|
try {
|
|
1642
|
-
return fs.statSync(path);
|
|
2020
|
+
return noFollow && fs.lstatSync ? fs.lstatSync(path) : fs.statSync(path);
|
|
1643
2021
|
}
|
|
1644
2022
|
catch (error) {
|
|
1645
2023
|
if (/\bENOENT\b/u.test(error instanceof Error ? error.message : String(error)))
|
|
@@ -1657,8 +2035,9 @@ function writeStat(view, pointer, stat) {
|
|
|
1657
2035
|
setUint64(view, pointer, stat.dev ?? 1);
|
|
1658
2036
|
setUint64(view, pointer + 8, stat.ino ?? 1);
|
|
1659
2037
|
setUint64(view, pointer + 16, stat.nlink ?? 1);
|
|
1660
|
-
|
|
1661
|
-
|
|
2038
|
+
// A device supplies S_IFCHR; ORing S_IFREG into it made it S_IFLNK.
|
|
2039
|
+
const kind = (stat.mode ?? 0) & 0o170000 || (stat.isSymbolicLink?.() ? S_IFLNK : stat.isDirectory() ? S_IFDIR : S_IFREG);
|
|
2040
|
+
view.setUint32(pointer + 24, kind | ((stat.mode ?? (stat.isDirectory() ? 0o755 : 0o644)) & 0o7777), true);
|
|
1662
2041
|
view.setUint32(pointer + 28, stat.uid ?? 1000, true);
|
|
1663
2042
|
view.setUint32(pointer + 32, stat.gid ?? 1000, true);
|
|
1664
2043
|
view.setUint32(pointer + 36, 0, true);
|
|
@@ -1732,10 +2111,10 @@ function errno(errnoName) {
|
|
|
1732
2111
|
return error;
|
|
1733
2112
|
}
|
|
1734
2113
|
const ERRNO_NUMBERS = {
|
|
1735
|
-
EPERM: 1n, ENOENT: 2n, EIO: 5n, EBADF: 9n, EAGAIN: 11n, ENOMEM: 12n, EACCES: 13n,
|
|
2114
|
+
EPERM: 1n, ENOENT: 2n, EINTR: 4n, EIO: 5n, ENXIO: 6n, EBADF: 9n, EAGAIN: 11n, ENOMEM: 12n, EACCES: 13n,
|
|
1736
2115
|
EFAULT: 14n, EBUSY: 16n, EEXIST: 17n, EXDEV: 18n, ENOTDIR: 20n, EISDIR: 21n,
|
|
1737
2116
|
EINVAL: 22n, EMFILE: 24n, ENOSPC: 28n, ESPIPE: 29n, EROFS: 30n, ERANGE: 34n,
|
|
1738
|
-
ENOSYS: 38n, ENOTEMPTY: 39n, ELOOP: 40n, ENAMETOOLONG: 36n, EREMOTE: 66n,
|
|
2117
|
+
ENOSYS: 38n, ENOTEMPTY: 39n, ELOOP: 40n, ENAMETOOLONG: 36n, EREMOTE: 66n, EOPNOTSUPP: 95n,
|
|
1739
2118
|
};
|
|
1740
2119
|
function negativeErrno(error) {
|
|
1741
2120
|
// The errno's own name first, and the message parsed only for an error that has none.
|