@volter/browser-wali 0.1.166 → 0.1.168
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 +6 -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 +43 -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 +26 -4
- package/dist/filesystem.d.ts.map +1 -1
- package/dist/filesystem.js +633 -175
- 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 +12 -1
- package/dist/snapshot-filesystem.d.ts.map +1 -1
- package/dist/snapshot-filesystem.js +241 -73
- 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 +26 -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,8 +76,9 @@ 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
|
-
this.files = this.descriptors.view("file");
|
|
81
|
+
this.files = this.descriptors.view("file", (file) => file.inode?.close());
|
|
69
82
|
this.specialDescriptors = this.descriptors.view("special", (value) => value.close?.());
|
|
70
83
|
this.stdioAliases = this.descriptors.view("stdio");
|
|
71
84
|
this.cwd = normalizePath("/", options.cwd);
|
|
@@ -93,6 +106,9 @@ export class WaliFileDescriptorTable {
|
|
|
93
106
|
...(fs.truncateSync ? { truncateSync: (path, length) => fs.truncateSync(this.assertWritable(path), length) } : {}),
|
|
94
107
|
mkdirSync: (path, settings) => fs.mkdirSync(this.assertWritable(path), settings),
|
|
95
108
|
unlinkSync: (path) => fs.unlinkSync(this.assertWritable(path)),
|
|
109
|
+
// A writable alias must not let the guest mutate an immutable source.
|
|
110
|
+
...(fs.linkSync ? { linkSync: (source, destination) => fs.linkSync(this.assertWritable(source), this.assertWritable(destination)) } : {}),
|
|
111
|
+
...(fs.openFileSync ? { openFileSync: fs.openFileSync.bind(fs) } : {}),
|
|
96
112
|
rmdirSync: (path) => fs.rmdirSync(this.assertWritable(path)),
|
|
97
113
|
renameSync: (from, to) => fs.renameSync(this.assertWritable(from), this.assertWritable(to)),
|
|
98
114
|
...(fs.chmodSync ? { chmodSync: (path, mode) => fs.chmodSync(this.assertWritable(path), mode) } : {}),
|
|
@@ -101,7 +117,7 @@ export class WaliFileDescriptorTable {
|
|
|
101
117
|
...(fs.restoreNodeSync ? { restoreNodeSync: (path, node) => fs.restoreNodeSync(this.assertWritable(path), node) } : {}),
|
|
102
118
|
} : fs;
|
|
103
119
|
this.stdin = "read" in options.stdin ? options.stdin : new BufferedWaliStdin(options.stdin);
|
|
104
|
-
if (!options.descriptors) {
|
|
120
|
+
if (!options.descriptors || options.initializeStdio) {
|
|
105
121
|
for (const fd of [0, 1, 2])
|
|
106
122
|
this.specialDescriptors.set(fd, { stdio: fd });
|
|
107
123
|
}
|
|
@@ -117,6 +133,7 @@ export class WaliFileDescriptorTable {
|
|
|
117
133
|
SYS_open: (path, flags, mode) => this.open(this.readCString(Number(path)), Number(flags), Number(mode)),
|
|
118
134
|
SYS_openat: (dirfd, path, flags, mode) => this.openAt(Number(dirfd), this.readCString(Number(path)), Number(flags), Number(mode)),
|
|
119
135
|
SYS_close: (fd) => this.close(Number(fd)),
|
|
136
|
+
SYS_ioctl: (fd, request, argument) => this.ioctl(Number(fd), Number(request), Number(argument)),
|
|
120
137
|
SYS_read: (fd, pointer, length) => this.read(Number(fd), Number(pointer), Number(length)),
|
|
121
138
|
SYS_pread64: (fd, pointer, length, offset) => this.pread(Number(fd), Number(pointer), Number(length), Number(offset)),
|
|
122
139
|
SYS_write: (fd, pointer, length) => this.write(Number(fd), Number(pointer), Number(length)),
|
|
@@ -128,13 +145,19 @@ export class WaliFileDescriptorTable {
|
|
|
128
145
|
SYS_pwritev: (fd, iov, count, offset, high) => this.pwritev(Number(fd), Number(iov), Number(count), vectorOffset(offset, high)),
|
|
129
146
|
SYS_lseek: (fd, offset, whence) => this.lseek(Number(fd), Number(offset), Number(whence)),
|
|
130
147
|
SYS_ftruncate: (fd, length) => this.ftruncate(Number(fd), Number(length)),
|
|
148
|
+
SYS_truncate: (path, length) => this.truncate(this.readCString(Number(path)), Number(length)),
|
|
131
149
|
SYS_fstat: (fd, stat) => this.fstat(Number(fd), Number(stat)),
|
|
132
150
|
SYS_stat: (path, stat) => this.stat(this.readCString(Number(path)), Number(stat), false),
|
|
133
151
|
SYS_lstat: (path, stat) => this.stat(this.readCString(Number(path)), Number(stat), true),
|
|
134
152
|
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))),
|
|
153
|
+
SYS_access: (path, mode) => this.accessAt(AT_FDCWD, this.readCString(Number(path)), Number(mode), 0),
|
|
154
|
+
SYS_faccessat: (dirfd, path, mode) => this.accessAt(Number(dirfd), this.readCString(Number(path)), Number(mode), 0),
|
|
155
|
+
SYS_faccessat2: (dirfd, path, mode, flags) => this.accessAt(Number(dirfd), this.readCString(Number(path)), Number(mode), Number(flags)),
|
|
156
|
+
SYS_umask: (mask) => {
|
|
157
|
+
const previous = this.fileCreationMask;
|
|
158
|
+
this.fileCreationMask = Number(mask) & 0o777;
|
|
159
|
+
return BigInt(previous);
|
|
160
|
+
},
|
|
138
161
|
SYS_chmod: (path, mode) => this.chmod(this.readCString(Number(path)), Number(mode)),
|
|
139
162
|
SYS_fchmod: (fd, mode) => this.fchmod(Number(fd), Number(mode)),
|
|
140
163
|
SYS_fchmodat: (dirfd, path, mode, flags) => this.chmodAt(Number(dirfd), this.readCString(Number(path)), Number(mode), Number(flags)),
|
|
@@ -149,10 +172,13 @@ export class WaliFileDescriptorTable {
|
|
|
149
172
|
SYS_inotify_init1: () => this.inotifyInit(),
|
|
150
173
|
SYS_inotify_add_watch: (fd, path) => this.inotifyAddWatch(Number(fd), this.readCString(Number(path))),
|
|
151
174
|
SYS_inotify_rm_watch: (fd) => this.specialDescriptors.has(Number(fd)) ? 0n : -9n,
|
|
152
|
-
SYS_rmdir: (path) => this.
|
|
153
|
-
SYS_unlink: (path) => this.
|
|
175
|
+
SYS_rmdir: (path) => this.unlinkAt(AT_FDCWD, this.readCString(Number(path)), AT_REMOVEDIR),
|
|
176
|
+
SYS_unlink: (path) => this.unlinkAt(AT_FDCWD, this.readCString(Number(path)), 0),
|
|
154
177
|
SYS_unlinkat: (dirfd, path, flags) => this.unlinkAt(Number(dirfd), this.readCString(Number(path)), Number(flags)),
|
|
178
|
+
SYS_link: (source, destination) => this.linkAt(AT_FDCWD, this.readCString(Number(source)), AT_FDCWD, this.readCString(Number(destination)), 0),
|
|
179
|
+
SYS_linkat: (sourceDirfd, source, destinationDirfd, destination, flags) => this.linkAt(Number(sourceDirfd), this.readCString(Number(source)), Number(destinationDirfd), this.readCString(Number(destination)), Number(flags)),
|
|
155
180
|
SYS_rename: (from, to) => this.rename(this.readCString(Number(from)), this.readCString(Number(to))),
|
|
181
|
+
SYS_renameat: (fromDirfd, from, toDirfd, to) => this.rename(this.readCString(Number(from)), this.readCString(Number(to)), Number(fromDirfd), Number(toDirfd)),
|
|
156
182
|
SYS_utimensat: (dirfd, path, times, flags) => this.utimensat(Number(dirfd), Number(path), Number(times), Number(flags)),
|
|
157
183
|
SYS_symlink: (target, link) => this.symlink(this.readCString(Number(target)), this.readCString(Number(link)), AT_FDCWD),
|
|
158
184
|
SYS_symlinkat: (target, dirfd, link) => this.symlink(this.readCString(Number(target)), this.readCString(Number(link)), Number(dirfd)),
|
|
@@ -190,15 +216,17 @@ export class WaliFileDescriptorTable {
|
|
|
190
216
|
this.options.filesystem.mkdirSync("/tmp", { recursive: true });
|
|
191
217
|
}
|
|
192
218
|
catch { /* already present */ }
|
|
193
|
-
//
|
|
194
|
-
//
|
|
219
|
+
// Legacy artifacts read one `name=value` per line. The owned linker now
|
|
220
|
+
// supplies direct environment imports; rebuilt programs never use this
|
|
221
|
+
// compatibility path. Old artifacts still cannot carry a line break.
|
|
222
|
+
// A shell's own
|
|
195
223
|
// parameters (`?`, a newline-valued IFS) reach here through every lane's
|
|
196
224
|
// spawn; refusing the whole spawn for them is what killed a script's
|
|
197
225
|
// `spawn("ffmpeg")`. They are dropped and named once.
|
|
198
226
|
const dropped = [];
|
|
199
227
|
const lines = [];
|
|
200
228
|
for (const [name, value] of Object.entries(env)) {
|
|
201
|
-
if (
|
|
229
|
+
if (/[=\u0000\r\n]/u.test(name) || /[\u0000\r\n]/u.test(value)) {
|
|
202
230
|
dropped.push(name);
|
|
203
231
|
continue;
|
|
204
232
|
}
|
|
@@ -226,7 +254,8 @@ export class WaliFileDescriptorTable {
|
|
|
226
254
|
// The range, not the whole file sliced: a mapping is read in pieces, and
|
|
227
255
|
// each piece copied the file entire (2026-09-27, Blender reading a `.blend`).
|
|
228
256
|
const fs = this.fs();
|
|
229
|
-
return
|
|
257
|
+
return file.inode ? file.inode.read(offset, length).slice()
|
|
258
|
+
: fs.readRangeSync ? fs.readRangeSync(file.path, offset, length) : fs.readFileSync(file.path).slice(offset, offset + length);
|
|
230
259
|
}
|
|
231
260
|
mappingPath(fd) {
|
|
232
261
|
const file = this.files.get(fd);
|
|
@@ -270,7 +299,7 @@ export class WaliFileDescriptorTable {
|
|
|
270
299
|
return this.specialDescriptors.get(fd)?.browserProcessPipe;
|
|
271
300
|
}
|
|
272
301
|
copyForFork(memory) {
|
|
273
|
-
return new WaliFileDescriptorTable({ ...this.options, cwd: this.cwd, memory, descriptors: this.descriptors.copy() });
|
|
302
|
+
return new WaliFileDescriptorTable({ ...this.options, initializeStdio: false, fileCreationMask: this.fileCreationMask, cwd: this.cwd, memory, descriptors: this.descriptors.copy() });
|
|
274
303
|
}
|
|
275
304
|
copyForExec(memory, stdio) {
|
|
276
305
|
const descriptors = this.descriptors.copy();
|
|
@@ -282,17 +311,45 @@ export class WaliFileDescriptorTable {
|
|
|
282
311
|
});
|
|
283
312
|
}
|
|
284
313
|
descriptors.exec();
|
|
285
|
-
return new WaliFileDescriptorTable({ ...this.options, cwd: this.cwd, memory, descriptors });
|
|
314
|
+
return new WaliFileDescriptorTable({ ...this.options, initializeStdio: false, fileCreationMask: this.fileCreationMask, cwd: this.cwd, memory, descriptors });
|
|
286
315
|
}
|
|
287
316
|
currentWorkingDirectory() { return this.cwd; }
|
|
288
|
-
|
|
317
|
+
creationMask() { return this.fileCreationMask; }
|
|
318
|
+
// Translated from Emscripten libsyscall.js and wasmfs/syscalls.cpp (MIT),
|
|
319
|
+
// 862dc0d48acc4134a0088d27ac8f5dd18baba1cd. The kernel owns the description;
|
|
320
|
+
// terminal settings are dispatched separately to the attached tty.
|
|
321
|
+
ioctl(fd, request, argument) {
|
|
289
322
|
if (!this.hasFd(fd))
|
|
290
|
-
return;
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
323
|
+
return -9n;
|
|
324
|
+
const operation = request >>> 0;
|
|
325
|
+
if (operation === 0x5451 || operation === 0x5450) {
|
|
326
|
+
return this.fcntl(fd, 2, operation === 0x5451 ? 1 : 0);
|
|
327
|
+
}
|
|
328
|
+
if (operation !== 0x5421 && operation !== 0x541b)
|
|
329
|
+
return -25n;
|
|
330
|
+
const file = this.files.get(fd);
|
|
331
|
+
if (operation === 0x541b && (!file || file.directory))
|
|
332
|
+
return -25n;
|
|
333
|
+
const memory = this.options.memory();
|
|
334
|
+
if (!Number.isSafeInteger(argument) || argument <= 0 || argument + 4 > memory.buffer.byteLength)
|
|
335
|
+
return -14n;
|
|
336
|
+
if (operation === 0x5421) {
|
|
337
|
+
const nonblocking = this.memoryView().getInt32(argument, true) !== 0;
|
|
338
|
+
const flags = this.fcntl(fd, 3, 0);
|
|
339
|
+
if (flags < 0n)
|
|
340
|
+
return flags;
|
|
341
|
+
return this.fcntl(fd, 4, nonblocking ? Number(flags) | O_NONBLOCK : Number(flags) & ~O_NONBLOCK);
|
|
342
|
+
}
|
|
343
|
+
if (!file)
|
|
344
|
+
return -25n;
|
|
345
|
+
try {
|
|
346
|
+
const remaining = Math.max(0, (file.inode?.stat() ?? this.fs().statSync(file.path)).size - file.position);
|
|
347
|
+
this.memoryView().setInt32(argument, remaining, true);
|
|
294
348
|
return 0n;
|
|
295
|
-
|
|
349
|
+
}
|
|
350
|
+
catch (error) {
|
|
351
|
+
return negativeErrno(error);
|
|
352
|
+
}
|
|
296
353
|
}
|
|
297
354
|
pollDescriptor(fd, events) {
|
|
298
355
|
if (fd < 0 || !this.hasFd(fd))
|
|
@@ -302,9 +359,12 @@ export class WaliFileDescriptorTable {
|
|
|
302
359
|
return special.poll?.(events) ?? 0;
|
|
303
360
|
const stdio = this.stdioFd(fd);
|
|
304
361
|
let returned = 0;
|
|
305
|
-
|
|
362
|
+
const terminal = this.terminalFd(fd);
|
|
363
|
+
const input = terminal ? this.options.terminalStdin : this.stdin;
|
|
364
|
+
const mode = this.statusFlags(fd) & O_ACCMODE;
|
|
365
|
+
if ((events & 0x1) && stdio === 0 && (!terminal || mode !== O_WRONLY) && input?.poll())
|
|
306
366
|
returned |= 0x1;
|
|
307
|
-
if ((events & 0x4) && stdio !== 0)
|
|
367
|
+
if ((events & 0x4) && (stdio !== 0 || (terminal && mode !== 0)))
|
|
308
368
|
returned |= 0x4;
|
|
309
369
|
return returned;
|
|
310
370
|
}
|
|
@@ -359,20 +419,22 @@ export class WaliFileDescriptorTable {
|
|
|
359
419
|
}
|
|
360
420
|
return text;
|
|
361
421
|
}
|
|
362
|
-
resolve(path) { return
|
|
422
|
+
resolve(path) { return absoluteWaliPath(this.cwd, path); }
|
|
363
423
|
resolveAt(dirfd, path) {
|
|
364
424
|
if (path.startsWith("/"))
|
|
365
|
-
return
|
|
425
|
+
return path;
|
|
366
426
|
if (dirfd === AT_FDCWD)
|
|
367
427
|
return this.resolve(path);
|
|
368
428
|
const directory = this.files.get(dirfd);
|
|
369
|
-
if (!directory
|
|
429
|
+
if (!directory)
|
|
430
|
+
throw errno(this.hasFd(dirfd) ? "ENOTDIR" : "EBADF");
|
|
431
|
+
if (!directory.directory)
|
|
370
432
|
throw errno("ENOTDIR");
|
|
371
|
-
return
|
|
433
|
+
return absoluteWaliPath(directory.path, path);
|
|
372
434
|
}
|
|
373
435
|
open(path, flags, mode) {
|
|
374
436
|
try {
|
|
375
|
-
return BigInt(this.
|
|
437
|
+
return BigInt(this.openPath(this.resolve(path), flags, mode));
|
|
376
438
|
}
|
|
377
439
|
catch (error) {
|
|
378
440
|
return negativeErrno(error);
|
|
@@ -380,45 +442,65 @@ export class WaliFileDescriptorTable {
|
|
|
380
442
|
}
|
|
381
443
|
openAt(dirfd, path, flags, mode) {
|
|
382
444
|
try {
|
|
383
|
-
return BigInt(this.
|
|
445
|
+
return BigInt(this.openPath(this.resolveAt(dirfd, path), flags, mode));
|
|
384
446
|
}
|
|
385
447
|
catch (error) {
|
|
386
448
|
return negativeErrno(error);
|
|
387
449
|
}
|
|
388
450
|
}
|
|
451
|
+
openPath(path, flags, mode) {
|
|
452
|
+
if ((flags & O_ACCMODE) === O_ACCMODE)
|
|
453
|
+
throw errno("EINVAL");
|
|
454
|
+
// Linux >=6.4 rejects this pair instead of creating a regular file and
|
|
455
|
+
// then failing O_DIRECTORY (the old behaviour still in pinned WasmFS).
|
|
456
|
+
if ((flags & O_CREAT) && (flags & O_DIRECTORY))
|
|
457
|
+
throw errno("EINVAL");
|
|
458
|
+
const exclusive = Boolean((flags & O_CREAT) && (flags & O_EXCL));
|
|
459
|
+
const resolved = this.resolveFilesystemPath(path, !(exclusive || (flags & O_NOFOLLOW)));
|
|
460
|
+
const target = exclusive || (flags & O_NOFOLLOW) ? this.virtualLink(resolved) ?? this.readSymlink(resolved) : undefined;
|
|
461
|
+
const entry = exclusive || (flags & O_NOFOLLOW) ? statIfPresent(this.fs(), resolved, true) : undefined;
|
|
462
|
+
const link = target !== undefined || entry?.isSymbolicLink?.();
|
|
463
|
+
// Linux O_EXCL checks the link itself, even when its target is absent.
|
|
464
|
+
if (exclusive && (link || entry !== undefined
|
|
465
|
+
|| ["/dev/null", "/dev/random", "/dev/urandom"].includes(resolved)))
|
|
466
|
+
throw errno("EEXIST");
|
|
467
|
+
if ((flags & O_NOFOLLOW) && link)
|
|
468
|
+
throw errno("ELOOP");
|
|
469
|
+
checkFileName(resolved);
|
|
470
|
+
const descriptor = this.procDescriptor(resolved);
|
|
471
|
+
if (descriptor !== undefined)
|
|
472
|
+
return this.reopenDescriptor(descriptor, flags, mode);
|
|
473
|
+
return this.openResolved(resolved, flags, mode);
|
|
474
|
+
}
|
|
389
475
|
openResolved(path, flags, mode) {
|
|
390
476
|
// O_CLOEXEC is recorded here for every kind of descriptor open makes: musl
|
|
391
477
|
// no longer follows it with F_SETFD (musl 0003), which was the only thing
|
|
392
478
|
// 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);
|
|
479
|
+
if (path === "/dev/tty") {
|
|
480
|
+
if (flags & O_DIRECTORY)
|
|
481
|
+
throw errno("ENOTDIR");
|
|
482
|
+
const fd = this.allocateFd();
|
|
483
|
+
this.specialDescriptors.set(fd, { stdio: 0, terminal: true, stat: () => waliDeviceStat(path) });
|
|
484
|
+
Atomics.store(this.descriptors.registry.pipeMetadata, this.descriptors.entries[fd].description * 3 + 1, flags & ~O_CLOEXEC_FLAG);
|
|
485
|
+
// The calling realm validates controlling-session membership through
|
|
486
|
+
// ttyopen before returning this fd. The filesystem owner owns the fd;
|
|
487
|
+
// terminal input/output stay on the caller's attached transport.
|
|
488
|
+
return this.markCloseOnExecNumber(fd, flags);
|
|
489
|
+
}
|
|
490
|
+
const device = createWaliDevice(path, flags);
|
|
491
|
+
if (device) {
|
|
492
|
+
if (flags & O_DIRECTORY)
|
|
493
|
+
throw errno("ENOTDIR");
|
|
494
|
+
const fd = this.allocateFd();
|
|
495
|
+
this.specialDescriptors.set(fd, device);
|
|
496
|
+
return this.markCloseOnExecNumber(fd, flags);
|
|
416
497
|
}
|
|
498
|
+
const fd = this.allocateFd();
|
|
417
499
|
const fs = this.fs();
|
|
418
500
|
// 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;
|
|
501
|
+
let found = this.virtualStat(path, false) ?? (fs.linkTargetSync ? statIfPresent(fs, path) : fs.existsSync(path) ? fs.statSync(path) : undefined);
|
|
420
502
|
const exists = found !== undefined;
|
|
421
|
-
if ((flags & O_ACCMODE) !== 0 || (!exists && (flags & O_CREAT)))
|
|
503
|
+
if ((flags & O_ACCMODE) !== 0 || (flags & O_TRUNC) || (!exists && (flags & O_CREAT)))
|
|
422
504
|
this.assertWritable(path);
|
|
423
505
|
if (!exists && !(flags & O_CREAT))
|
|
424
506
|
throw errno("ENOENT");
|
|
@@ -426,30 +508,41 @@ export class WaliFileDescriptorTable {
|
|
|
426
508
|
throw errno("EEXIST");
|
|
427
509
|
if (!exists) {
|
|
428
510
|
fs.writeFileSync(path, new Uint8Array());
|
|
429
|
-
fs.chmodSync?.(path, mode & 0o7777);
|
|
511
|
+
fs.chmodSync?.(path, mode & 0o7777 & ~this.fileCreationMask);
|
|
430
512
|
this.options.fileLocks?.created?.(path);
|
|
431
513
|
found = fs.statSync(path);
|
|
432
514
|
}
|
|
433
515
|
const stat = found;
|
|
434
516
|
if ((flags & O_DIRECTORY) && !stat.isDirectory())
|
|
435
517
|
throw errno("ENOTDIR");
|
|
436
|
-
if (stat.isDirectory() && (flags & O_ACCMODE) !== 0)
|
|
518
|
+
if (stat.isDirectory() && ((flags & O_ACCMODE) !== 0 || (flags & O_CREAT) || (flags & O_TRUNC)))
|
|
437
519
|
throw errno("EISDIR");
|
|
438
|
-
|
|
520
|
+
// doOpen does not recheck the mode of the file it just created: creating
|
|
521
|
+
// mode 0400 with O_RDWR still returns a writable open description.
|
|
522
|
+
if (exists) {
|
|
523
|
+
if ((flags & O_ACCMODE) !== O_WRONLY)
|
|
524
|
+
checkFilePermission(stat, 4);
|
|
525
|
+
if ((flags & O_ACCMODE) !== 0 || (flags & O_TRUNC))
|
|
526
|
+
checkFilePermission(stat, 2);
|
|
527
|
+
}
|
|
528
|
+
const truncated = (flags & O_TRUNC) !== 0 && stat.isFile();
|
|
439
529
|
if (truncated) {
|
|
440
530
|
if (fs.truncateSync)
|
|
441
531
|
fs.truncateSync(path, 0);
|
|
442
532
|
else
|
|
443
533
|
fs.writeFileSync(path, new Uint8Array());
|
|
444
534
|
}
|
|
445
|
-
const fd = this.allocateFd();
|
|
446
535
|
const directory = stat.isDirectory();
|
|
536
|
+
const virtualDirectory = ["/proc", "/proc/self", "/proc/self/fd"].includes(path);
|
|
537
|
+
const entries = directory && !virtualDirectory ? this.directoryEntries(path) : undefined;
|
|
447
538
|
this.files.set(fd, {
|
|
448
539
|
path,
|
|
540
|
+
...(!directory && fs.openFileSync ? { inode: fs.openFileSync(path) } : {}),
|
|
449
541
|
flags,
|
|
450
|
-
//
|
|
451
|
-
position:
|
|
542
|
+
// O_APPEND chooses EOF on each write; open itself starts at zero.
|
|
543
|
+
position: 0,
|
|
452
544
|
directory,
|
|
545
|
+
...(entries ? { entries } : {}),
|
|
453
546
|
description: this.nextDescription++,
|
|
454
547
|
});
|
|
455
548
|
this.descriptors.entries[fd].flags = flags & 0x80000 ? 1 : 0;
|
|
@@ -471,8 +564,8 @@ export class WaliFileDescriptorTable {
|
|
|
471
564
|
*
|
|
472
565
|
* Scanning costs one probe per open descriptor, and a process has a handful.
|
|
473
566
|
*/
|
|
474
|
-
allocateFd(minimum =
|
|
475
|
-
const maximum = this.options.descriptorMaximum ?? 65_535;
|
|
567
|
+
allocateFd(minimum = 0) {
|
|
568
|
+
const maximum = Math.min(this.options.descriptorMaximum ?? 65_535, this.descriptors.descriptorLimit - 1);
|
|
476
569
|
for (let fd = Math.max(minimum, 0); fd <= maximum; fd += 1) {
|
|
477
570
|
// A private tree's numbers are its libc's, held or given back (ADR-0047).
|
|
478
571
|
if (inPrivateTreeWindow(fd)) {
|
|
@@ -489,7 +582,6 @@ export class WaliFileDescriptorTable {
|
|
|
489
582
|
}
|
|
490
583
|
close(fd) {
|
|
491
584
|
if (this.stdioAliases.delete(fd)) {
|
|
492
|
-
this.descriptorStatusFlags.delete(fd);
|
|
493
585
|
this.setSharedStdioAlias(fd, undefined);
|
|
494
586
|
return 0n;
|
|
495
587
|
}
|
|
@@ -526,9 +618,14 @@ export class WaliFileDescriptorTable {
|
|
|
526
618
|
if (length < 0)
|
|
527
619
|
throw errno("EINVAL");
|
|
528
620
|
if (this.stdioFd(fd) === 0) {
|
|
529
|
-
if ((this.statusFlags(fd) &
|
|
621
|
+
if (this.terminalFd(fd) && (this.statusFlags(fd) & O_ACCMODE) === O_WRONLY)
|
|
622
|
+
return -9n;
|
|
623
|
+
const input = this.terminalFd(fd) ? this.options.terminalStdin : this.stdin;
|
|
624
|
+
if (!input)
|
|
625
|
+
return -95n; // No independent terminal transport was supplied.
|
|
626
|
+
if ((this.statusFlags(fd) & O_NONBLOCK) && !input.poll())
|
|
530
627
|
return -11n;
|
|
531
|
-
const chunk =
|
|
628
|
+
const chunk = input.read(length);
|
|
532
629
|
this.copyIn(pointer, chunk);
|
|
533
630
|
return BigInt(chunk.byteLength);
|
|
534
631
|
}
|
|
@@ -552,7 +649,7 @@ export class WaliFileDescriptorTable {
|
|
|
552
649
|
throw errno("EBADF");
|
|
553
650
|
const fs = this.fs();
|
|
554
651
|
// A view where the filesystem offers one: copied into the program's memory at once, so one copy, not two.
|
|
555
|
-
const chunk = fs.viewRangeSync
|
|
652
|
+
const chunk = file.inode ? file.inode.read(file.position, length) : fs.viewRangeSync
|
|
556
653
|
? fs.viewRangeSync(file.path, file.position, length)
|
|
557
654
|
: fs.readRangeSync
|
|
558
655
|
? fs.readRangeSync(file.path, file.position, length)
|
|
@@ -566,13 +663,20 @@ export class WaliFileDescriptorTable {
|
|
|
566
663
|
}
|
|
567
664
|
}
|
|
568
665
|
write(fd, pointer, length) {
|
|
666
|
+
return this.writeBytes(fd, pointer, length);
|
|
667
|
+
}
|
|
668
|
+
/** One vector is one destination write and one shared-offset advance. */
|
|
669
|
+
writeBytes(fd, pointer, length, gathered) {
|
|
569
670
|
try {
|
|
570
671
|
const stdio = this.stdioFd(fd);
|
|
672
|
+
if (this.terminalFd(fd))
|
|
673
|
+
return (this.statusFlags(fd) & O_ACCMODE) === 0 ? -9n
|
|
674
|
+
: this.options.emitTerminal?.(gathered ?? this.copyOut(pointer, length)) ?? -95n;
|
|
571
675
|
if (stdio === 1 || stdio === 2)
|
|
572
|
-
return this.options.emit(stdio, this.copyOut(pointer, length));
|
|
676
|
+
return this.options.emit(stdio, gathered ?? this.copyOut(pointer, length));
|
|
573
677
|
const special = this.specialDescriptors.get(fd);
|
|
574
678
|
if (special)
|
|
575
|
-
return special.write?.(this.copyOut(pointer, length)) ?? -9n;
|
|
679
|
+
return special.write?.(gathered ?? this.copyOut(pointer, length)) ?? -9n;
|
|
576
680
|
const file = this.files.get(fd);
|
|
577
681
|
if (!file)
|
|
578
682
|
throw errno("EBADF");
|
|
@@ -581,32 +685,46 @@ export class WaliFileDescriptorTable {
|
|
|
581
685
|
if ((file.flags & O_ACCMODE) === 0)
|
|
582
686
|
throw errno("EBADF");
|
|
583
687
|
const fs = this.fs();
|
|
688
|
+
if (file.inode) {
|
|
689
|
+
this.assertWritable(file.path);
|
|
690
|
+
if (file.flags & O_APPEND)
|
|
691
|
+
file.position = file.inode.stat().size;
|
|
692
|
+
if (!gathered)
|
|
693
|
+
assertRange(this.options.memory(), pointer, length);
|
|
694
|
+
file.inode.write(file.position, gathered ?? this.memoryBytes().subarray(pointer, pointer + length));
|
|
695
|
+
file.position += length;
|
|
696
|
+
return BigInt(length);
|
|
697
|
+
}
|
|
584
698
|
if (fs.writeRangeSync) {
|
|
585
699
|
if (file.flags & O_APPEND)
|
|
586
700
|
file.position = fs.statSync(file.path).size;
|
|
587
701
|
// A view, not a copy: the file copies what it keeps (2026-09-27, one copy of every write fewer).
|
|
588
|
-
|
|
589
|
-
|
|
702
|
+
if (!gathered)
|
|
703
|
+
assertRange(this.options.memory(), pointer, length);
|
|
704
|
+
fs.writeRangeSync(file.path, file.position, gathered ?? this.memoryBytes().subarray(pointer, pointer + length));
|
|
590
705
|
file.position += length;
|
|
591
706
|
return BigInt(length);
|
|
592
707
|
}
|
|
593
|
-
const
|
|
708
|
+
const value = gathered ?? this.copyOut(pointer, length);
|
|
594
709
|
const current = fs.readFileSync(file.path);
|
|
595
710
|
if (file.flags & O_APPEND)
|
|
596
711
|
file.position = current.byteLength;
|
|
597
|
-
const size = Math.max(current.byteLength, file.position +
|
|
712
|
+
const size = Math.max(current.byteLength, file.position + value.byteLength);
|
|
598
713
|
const next = new Uint8Array(size);
|
|
599
714
|
next.set(current);
|
|
600
|
-
next.set(
|
|
715
|
+
next.set(value, file.position);
|
|
601
716
|
fs.writeFileSync(file.path, next);
|
|
602
|
-
file.position +=
|
|
603
|
-
return BigInt(
|
|
717
|
+
file.position += value.byteLength;
|
|
718
|
+
return BigInt(value.byteLength);
|
|
604
719
|
}
|
|
605
720
|
catch (error) {
|
|
606
721
|
return negativeErrno(error);
|
|
607
722
|
}
|
|
608
723
|
}
|
|
609
724
|
pread(fd, pointer, length, offset) {
|
|
725
|
+
const special = this.specialDescriptors.get(fd);
|
|
726
|
+
if (special)
|
|
727
|
+
return !special.seek ? -29n : !Number.isSafeInteger(offset) || offset < 0 ? -22n : this.read(fd, pointer, length);
|
|
610
728
|
const file = this.files.get(fd);
|
|
611
729
|
if (!file)
|
|
612
730
|
return -9n;
|
|
@@ -622,6 +740,9 @@ export class WaliFileDescriptorTable {
|
|
|
622
740
|
}
|
|
623
741
|
}
|
|
624
742
|
pwrite(fd, pointer, length, offset) {
|
|
743
|
+
const special = this.specialDescriptors.get(fd);
|
|
744
|
+
if (special)
|
|
745
|
+
return !special.seek ? -29n : !Number.isSafeInteger(offset) || offset < 0 ? -22n : this.write(fd, pointer, length);
|
|
625
746
|
const file = this.files.get(fd);
|
|
626
747
|
if (!file)
|
|
627
748
|
return -9n;
|
|
@@ -641,6 +762,9 @@ export class WaliFileDescriptorTable {
|
|
|
641
762
|
}
|
|
642
763
|
/** pread over a vector: the same answers, each buffer in turn at the offset, the position unmoved. */
|
|
643
764
|
preadv(fd, pointer, count, offset) {
|
|
765
|
+
const special = this.specialDescriptors.get(fd);
|
|
766
|
+
if (special)
|
|
767
|
+
return !special.seek ? -29n : !Number.isSafeInteger(offset) || offset < 0 ? -22n : this.readv(fd, pointer, count);
|
|
644
768
|
const file = this.files.get(fd);
|
|
645
769
|
if (!file)
|
|
646
770
|
return -9n;
|
|
@@ -657,6 +781,9 @@ export class WaliFileDescriptorTable {
|
|
|
657
781
|
}
|
|
658
782
|
/** pwrite over a vector, as pwrite: at the offset whatever O_APPEND says, the position unmoved. */
|
|
659
783
|
pwritev(fd, pointer, count, offset) {
|
|
784
|
+
const special = this.specialDescriptors.get(fd);
|
|
785
|
+
if (special)
|
|
786
|
+
return !special.seek ? -29n : !Number.isSafeInteger(offset) || offset < 0 ? -22n : this.writev(fd, pointer, count);
|
|
660
787
|
const file = this.files.get(fd);
|
|
661
788
|
if (!file)
|
|
662
789
|
return -9n;
|
|
@@ -689,29 +816,45 @@ export class WaliFileDescriptorTable {
|
|
|
689
816
|
return total;
|
|
690
817
|
}
|
|
691
818
|
writev(fd, pointer, count) {
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
819
|
+
const value = gatherWaliWriteVector(this.options.memory(), pointer, count);
|
|
820
|
+
return typeof value === "bigint" ? value : this.writeBytes(fd, 0, value.byteLength, value);
|
|
821
|
+
}
|
|
822
|
+
// OpenFileState::create caches dirents in Emscripten file_table.cpp/h
|
|
823
|
+
// (Copyright 2021, MIT/NCSA), pinned at 862dc0d4. Each process retains the
|
|
824
|
+
// same description on dup/fork; kernel storage supplies the entry metadata.
|
|
825
|
+
directoryEntries(path) {
|
|
826
|
+
const fs = this.fs();
|
|
827
|
+
const entries = [];
|
|
828
|
+
const virtual = path === "/proc" ? ["self"] : path === "/proc/self" ? ["fd"]
|
|
829
|
+
: path === "/proc/self/fd" ? this.descriptors.entries.flatMap((entry, fd) => entry ? [String(fd)] : []) : undefined;
|
|
830
|
+
for (const name of [".", "..", ...(virtual ?? fs.readdirSync(path))]) {
|
|
831
|
+
const entryPath = name === "." ? path : name === ".."
|
|
832
|
+
? normalizePath(path, "..") : normalizePath(path, name);
|
|
833
|
+
const stat = this.virtualStat(entryPath, true) ?? statIfPresent(fs, entryPath, true);
|
|
834
|
+
if (!stat)
|
|
835
|
+
continue; // A backend entry removed during enumeration.
|
|
836
|
+
const link = this.virtualLink(entryPath) ?? this.readSymlink(entryPath);
|
|
837
|
+
const type = link !== undefined || stat.isSymbolicLink?.() ? 10
|
|
838
|
+
: (stat.mode ?? 0) & 0o170000 ? ((stat.mode & 0o170000) >>> 12)
|
|
839
|
+
: stat.isDirectory() ? 4 : 8;
|
|
840
|
+
entries.push({ name, inode: stat.ino ?? 1, type });
|
|
841
|
+
}
|
|
842
|
+
return entries;
|
|
702
843
|
}
|
|
703
844
|
getdents64(fd, pointer, count) {
|
|
704
845
|
try {
|
|
846
|
+
if (!Number.isSafeInteger(count) || count < 24)
|
|
847
|
+
throw errno("EINVAL");
|
|
705
848
|
const directory = this.files.get(fd);
|
|
706
849
|
if (!directory)
|
|
707
850
|
throw errno("EBADF");
|
|
708
851
|
if (!directory.directory)
|
|
709
852
|
throw errno("ENOTDIR");
|
|
710
|
-
const entries =
|
|
853
|
+
const entries = directory.entries ??= this.directoryEntries(directory.path);
|
|
711
854
|
let written = 0;
|
|
712
855
|
while (directory.position < entries.length) {
|
|
713
|
-
const
|
|
714
|
-
const nameBytes = new TextEncoder().encode(name);
|
|
856
|
+
const entry = entries[directory.position];
|
|
857
|
+
const nameBytes = new TextEncoder().encode(entry.name);
|
|
715
858
|
const recordLength = alignTo(19 + nameBytes.byteLength + 1, 8);
|
|
716
859
|
if (recordLength > count - written) {
|
|
717
860
|
if (written === 0)
|
|
@@ -720,13 +863,10 @@ export class WaliFileDescriptorTable {
|
|
|
720
863
|
}
|
|
721
864
|
const record = pointer + written;
|
|
722
865
|
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);
|
|
866
|
+
this.memoryView().setBigUint64(record, BigInt(entry.inode), true);
|
|
727
867
|
this.memoryView().setBigInt64(record + 8, BigInt(directory.position + 1), true);
|
|
728
868
|
this.memoryView().setUint16(record + 16, recordLength, true);
|
|
729
|
-
this.memoryView().setUint8(record + 18,
|
|
869
|
+
this.memoryView().setUint8(record + 18, entry.type);
|
|
730
870
|
this.memoryBytes().fill(0, record + 19, record + recordLength);
|
|
731
871
|
this.memoryBytes().set(nameBytes, record + 19);
|
|
732
872
|
directory.position += 1;
|
|
@@ -740,13 +880,14 @@ export class WaliFileDescriptorTable {
|
|
|
740
880
|
}
|
|
741
881
|
lseek(fd, offset, whence) {
|
|
742
882
|
try {
|
|
883
|
+
const special = this.specialDescriptors.get(fd);
|
|
884
|
+
if (special)
|
|
885
|
+
return special.seek?.(offset, whence) ?? -29n;
|
|
743
886
|
const file = this.files.get(fd);
|
|
744
887
|
if (!file)
|
|
745
888
|
throw errno("EBADF");
|
|
746
|
-
if (file.directory)
|
|
747
|
-
throw errno("EINVAL");
|
|
748
889
|
const base = whence === SEEK_SET ? 0 : whence === SEEK_CUR ? file.position
|
|
749
|
-
: whence === SEEK_END ? this.fs().statSync(file.path).size : undefined;
|
|
890
|
+
: whence === SEEK_END && !file.directory ? (file.inode?.stat() ?? this.fs().statSync(file.path)).size : undefined;
|
|
750
891
|
// An offset or a position past 2^53 - 1 is not one a double holds (musl 0005's lseek says the same).
|
|
751
892
|
if (base === undefined || !Number.isSafeInteger(offset) || base + offset < 0 || !Number.isSafeInteger(base + offset))
|
|
752
893
|
throw errno("EINVAL");
|
|
@@ -759,6 +900,9 @@ export class WaliFileDescriptorTable {
|
|
|
759
900
|
}
|
|
760
901
|
ftruncate(fd, length) {
|
|
761
902
|
try {
|
|
903
|
+
const special = this.specialDescriptors.get(fd);
|
|
904
|
+
if (special)
|
|
905
|
+
return special.truncate?.(length) ?? -22n;
|
|
762
906
|
const file = this.files.get(fd);
|
|
763
907
|
if (!file)
|
|
764
908
|
throw errno("EBADF");
|
|
@@ -769,6 +913,12 @@ export class WaliFileDescriptorTable {
|
|
|
769
913
|
if (!Number.isSafeInteger(length) || length < 0)
|
|
770
914
|
throw errno("EINVAL");
|
|
771
915
|
const fs = this.fs();
|
|
916
|
+
if (file.inode) {
|
|
917
|
+
this.assertWritable(file.path);
|
|
918
|
+
if (file.inode.stat().size !== length)
|
|
919
|
+
file.inode.truncate(length);
|
|
920
|
+
return 0n;
|
|
921
|
+
}
|
|
772
922
|
if (fs.truncateSync) {
|
|
773
923
|
if (fs.statSync(file.path).size !== length)
|
|
774
924
|
fs.truncateSync(file.path, length);
|
|
@@ -786,9 +936,44 @@ export class WaliFileDescriptorTable {
|
|
|
786
936
|
return negativeErrno(error);
|
|
787
937
|
}
|
|
788
938
|
}
|
|
939
|
+
truncate(path, length) {
|
|
940
|
+
try {
|
|
941
|
+
const resolved = this.resolveFilesystemPath(this.resolve(path));
|
|
942
|
+
if (waliDeviceStat(resolved))
|
|
943
|
+
throw errno("EINVAL");
|
|
944
|
+
const fs = this.fs();
|
|
945
|
+
const stat = fs.statSync(resolved);
|
|
946
|
+
if (!stat.isFile())
|
|
947
|
+
throw errno("EISDIR");
|
|
948
|
+
checkFilePermission(stat, 2);
|
|
949
|
+
if (!Number.isSafeInteger(length) || length < 0)
|
|
950
|
+
throw errno("EINVAL");
|
|
951
|
+
if (fs.truncateSync)
|
|
952
|
+
fs.truncateSync(resolved, length);
|
|
953
|
+
else {
|
|
954
|
+
const resized = new Uint8Array(length);
|
|
955
|
+
resized.set(fs.readFileSync(resolved).subarray(0, length));
|
|
956
|
+
fs.writeFileSync(resolved, resized);
|
|
957
|
+
}
|
|
958
|
+
return 0n;
|
|
959
|
+
}
|
|
960
|
+
catch (error) {
|
|
961
|
+
return negativeErrno(error);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
789
964
|
stat(path, pointer, lstat) {
|
|
790
965
|
try {
|
|
791
966
|
const resolved = this.resolveFilesystemPath(this.resolve(path), !lstat);
|
|
967
|
+
const virtual = this.virtualStat(resolved, lstat);
|
|
968
|
+
if (virtual) {
|
|
969
|
+
writeStat(this.memoryView(), pointer, virtual);
|
|
970
|
+
return 0n;
|
|
971
|
+
}
|
|
972
|
+
const device = waliDeviceStat(resolved);
|
|
973
|
+
if (device) {
|
|
974
|
+
writeStat(this.memoryView(), pointer, device);
|
|
975
|
+
return 0n;
|
|
976
|
+
}
|
|
792
977
|
const fs = this.fs();
|
|
793
978
|
// A snapshot store's statSync answers ENOENT itself: asking existsSync first resolved the path twice.
|
|
794
979
|
if (!fs.linkTargetSync && !fs.existsSync(resolved))
|
|
@@ -796,7 +981,7 @@ export class WaliFileDescriptorTable {
|
|
|
796
981
|
const target = lstat ? this.readSymlink(resolved) : undefined;
|
|
797
982
|
writeStat(this.memoryView(), pointer, target === undefined
|
|
798
983
|
? (lstat && fs.lstatSync ? fs.lstatSync(resolved) : fs.statSync(resolved))
|
|
799
|
-
: syntheticSymlinkStat(target));
|
|
984
|
+
: { ...fs.statSync(resolved), ...syntheticSymlinkStat(target) });
|
|
800
985
|
return 0n;
|
|
801
986
|
}
|
|
802
987
|
catch (error) {
|
|
@@ -805,15 +990,32 @@ export class WaliFileDescriptorTable {
|
|
|
805
990
|
}
|
|
806
991
|
statAt(dirfd, path, pointer, flags) {
|
|
807
992
|
try {
|
|
808
|
-
if (flags & ~AT_SYMLINK_NOFOLLOW)
|
|
993
|
+
if (flags & ~(AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH | AT_NO_AUTOMOUNT))
|
|
809
994
|
throw errno("EINVAL");
|
|
995
|
+
// getFileAt with AT_EMPTY_PATH addresses the open description directly,
|
|
996
|
+
// including a non-directory fd; AT_FDCWD means the current directory.
|
|
997
|
+
if (!path && (flags & AT_EMPTY_PATH)) {
|
|
998
|
+
return dirfd === AT_FDCWD ? this.stat(this.cwd, pointer, false) : this.fstat(dirfd, pointer);
|
|
999
|
+
}
|
|
810
1000
|
const noFollow = Boolean(flags & AT_SYMLINK_NOFOLLOW);
|
|
811
1001
|
const resolved = this.resolveFilesystemPath(this.resolveAt(dirfd, path), !noFollow);
|
|
1002
|
+
const virtual = this.virtualStat(resolved, noFollow);
|
|
1003
|
+
if (virtual) {
|
|
1004
|
+
writeStat(this.memoryView(), pointer, virtual);
|
|
1005
|
+
return 0n;
|
|
1006
|
+
}
|
|
1007
|
+
const device = waliDeviceStat(resolved);
|
|
1008
|
+
if (device) {
|
|
1009
|
+
writeStat(this.memoryView(), pointer, device);
|
|
1010
|
+
return 0n;
|
|
1011
|
+
}
|
|
812
1012
|
const fs = this.fs();
|
|
813
1013
|
if (!fs.linkTargetSync && !fs.existsSync(resolved))
|
|
814
1014
|
throw errno("ENOENT");
|
|
815
1015
|
const target = noFollow ? this.readSymlink(resolved) : undefined;
|
|
816
|
-
writeStat(this.memoryView(), pointer, target === undefined
|
|
1016
|
+
writeStat(this.memoryView(), pointer, target === undefined
|
|
1017
|
+
? (noFollow && fs.lstatSync ? fs.lstatSync(resolved) : fs.statSync(resolved))
|
|
1018
|
+
: { ...fs.statSync(resolved), ...syntheticSymlinkStat(target) });
|
|
817
1019
|
return 0n;
|
|
818
1020
|
}
|
|
819
1021
|
catch (error) {
|
|
@@ -826,27 +1028,38 @@ export class WaliFileDescriptorTable {
|
|
|
826
1028
|
writeStat(this.memoryView(), pointer, syntheticTerminalStat());
|
|
827
1029
|
return 0n;
|
|
828
1030
|
}
|
|
1031
|
+
const special = this.specialDescriptors.get(fd);
|
|
1032
|
+
const stat = special?.stat?.();
|
|
1033
|
+
if (stat) {
|
|
1034
|
+
writeStat(this.memoryView(), pointer, stat);
|
|
1035
|
+
return 0n;
|
|
1036
|
+
}
|
|
829
1037
|
const file = this.files.get(fd);
|
|
830
1038
|
if (!file)
|
|
831
1039
|
throw errno("EBADF");
|
|
832
|
-
writeStat(this.memoryView(), pointer, this.fs().statSync(file.path));
|
|
1040
|
+
writeStat(this.memoryView(), pointer, this.virtualStat(file.path, false) ?? (file.inode?.stat() ?? this.fs().statSync(file.path)));
|
|
833
1041
|
return 0n;
|
|
834
1042
|
}
|
|
835
1043
|
catch (error) {
|
|
836
1044
|
return negativeErrno(error);
|
|
837
1045
|
}
|
|
838
1046
|
}
|
|
839
|
-
|
|
1047
|
+
accessAt(dirfd, path, mode, flags) {
|
|
840
1048
|
try {
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
1049
|
+
if (mode & ~7 || flags & ~(AT_EACCESS | AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH))
|
|
1050
|
+
throw errno("EINVAL");
|
|
1051
|
+
const noFollow = Boolean(flags & AT_SYMLINK_NOFOLLOW);
|
|
1052
|
+
const resolved = !path && (flags & AT_EMPTY_PATH)
|
|
1053
|
+
? dirfd === AT_FDCWD ? this.cwd : this.files.get(dirfd)?.path
|
|
1054
|
+
: this.resolveFilesystemPath(this.resolveAt(dirfd, path), !noFollow);
|
|
1055
|
+
if (resolved === undefined)
|
|
1056
|
+
throw errno("EBADF");
|
|
1057
|
+
const fs = this.fs();
|
|
1058
|
+
const target = noFollow ? this.readSymlink(resolved) : undefined;
|
|
1059
|
+
const stat = this.virtualStat(resolved, noFollow) ?? waliDeviceStat(resolved) ?? (target !== undefined ? syntheticSymlinkStat(target)
|
|
1060
|
+
: noFollow && fs.lstatSync ? fs.lstatSync(resolved) : fs.statSync(resolved));
|
|
1061
|
+
checkFilePermission(stat, mode);
|
|
1062
|
+
return 0n;
|
|
850
1063
|
}
|
|
851
1064
|
catch (error) {
|
|
852
1065
|
return negativeErrno(error);
|
|
@@ -885,7 +1098,12 @@ export class WaliFileDescriptorTable {
|
|
|
885
1098
|
if (!file)
|
|
886
1099
|
return -9n;
|
|
887
1100
|
try {
|
|
888
|
-
|
|
1101
|
+
if (file.inode) {
|
|
1102
|
+
this.assertWritable(file.path);
|
|
1103
|
+
file.inode.chmod(mode & 0o7777);
|
|
1104
|
+
}
|
|
1105
|
+
else
|
|
1106
|
+
this.fs().chmodSync?.(file.path, mode & 0o7777);
|
|
889
1107
|
return 0n;
|
|
890
1108
|
}
|
|
891
1109
|
catch (error) {
|
|
@@ -914,24 +1132,34 @@ export class WaliFileDescriptorTable {
|
|
|
914
1132
|
return 0n;
|
|
915
1133
|
}
|
|
916
1134
|
getcwd(pointer, size) {
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
1135
|
+
try {
|
|
1136
|
+
if (!pointer && size > 0)
|
|
1137
|
+
throw errno("EFAULT");
|
|
1138
|
+
if (size === 0)
|
|
1139
|
+
throw errno("EINVAL");
|
|
1140
|
+
if (this.cwd !== "/" && !this.fs().existsSync(this.cwd))
|
|
1141
|
+
throw errno("ENOENT");
|
|
1142
|
+
const encoded = new TextEncoder().encode(`${this.cwd}\0`);
|
|
1143
|
+
if (encoded.byteLength > size)
|
|
1144
|
+
throw errno("ERANGE");
|
|
1145
|
+
this.copyIn(pointer, encoded);
|
|
1146
|
+
return BigInt(encoded.byteLength);
|
|
1147
|
+
}
|
|
1148
|
+
catch (error) {
|
|
1149
|
+
return negativeErrno(error);
|
|
1150
|
+
}
|
|
922
1151
|
}
|
|
923
1152
|
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
|
-
});
|
|
1153
|
+
return this.mkdirAt(AT_FDCWD, path, mode);
|
|
929
1154
|
}
|
|
930
1155
|
mkdirAt(dirfd, path, mode) {
|
|
931
1156
|
try {
|
|
932
|
-
const resolved = this.resolveFilesystemPath(this.resolveAt(dirfd, path), false);
|
|
1157
|
+
const resolved = this.resolveFilesystemPath(this.resolveAt(dirfd, path), false, true);
|
|
1158
|
+
checkFileName(resolved);
|
|
1159
|
+
if (statIfPresent(this.fs(), resolved, true))
|
|
1160
|
+
throw errno("EEXIST");
|
|
933
1161
|
this.fs().mkdirSync(resolved);
|
|
934
|
-
this.fs().chmodSync?.(resolved, mode & 0o7777);
|
|
1162
|
+
this.fs().chmodSync?.(resolved, mode & 0o7777 & ~this.fileCreationMask);
|
|
935
1163
|
return 0n;
|
|
936
1164
|
}
|
|
937
1165
|
catch (error) {
|
|
@@ -976,21 +1204,70 @@ export class WaliFileDescriptorTable {
|
|
|
976
1204
|
try {
|
|
977
1205
|
if (flags & ~AT_REMOVEDIR)
|
|
978
1206
|
throw errno("EINVAL");
|
|
1207
|
+
const removeDirectory = Boolean(flags & AT_REMOVEDIR);
|
|
1208
|
+
const leaf = path.replace(/\/+$/u, "").split("/").at(-1);
|
|
1209
|
+
if (removeDirectory && leaf === ".")
|
|
1210
|
+
throw errno("EINVAL");
|
|
979
1211
|
const resolved = this.resolveFilesystemPath(this.resolveAt(dirfd, path), false);
|
|
980
|
-
|
|
981
|
-
|
|
1212
|
+
const fs = this.fs();
|
|
1213
|
+
const stat = fs.lstatSync?.(resolved) ?? fs.statSync(resolved);
|
|
1214
|
+
if (resolved === "/")
|
|
1215
|
+
throw errno("EBUSY");
|
|
1216
|
+
if (stat.isDirectory()) {
|
|
1217
|
+
if (!removeDirectory)
|
|
1218
|
+
throw errno("EISDIR");
|
|
1219
|
+
if (fs.readdirSync(resolved).length > 0)
|
|
1220
|
+
throw errno("ENOTEMPTY");
|
|
1221
|
+
}
|
|
1222
|
+
else if (removeDirectory)
|
|
1223
|
+
throw errno("ENOTDIR");
|
|
1224
|
+
checkFilePermission(fs.statSync(resolved.slice(0, resolved.lastIndexOf("/")) || "/"), 2);
|
|
1225
|
+
if (removeDirectory)
|
|
1226
|
+
fs.rmdirSync(resolved);
|
|
982
1227
|
else
|
|
983
|
-
|
|
1228
|
+
fs.unlinkSync(resolved);
|
|
984
1229
|
return 0n;
|
|
985
1230
|
}
|
|
986
1231
|
catch (error) {
|
|
987
1232
|
return negativeErrno(error);
|
|
988
1233
|
}
|
|
989
1234
|
}
|
|
990
|
-
|
|
1235
|
+
linkAt(sourceDirfd, sourcePath, destinationDirfd, destinationPath, flags) {
|
|
1236
|
+
return this.mutate(() => {
|
|
1237
|
+
const follow = 0x400; // Linux AT_SYMLINK_FOLLOW
|
|
1238
|
+
if (flags & ~(follow | AT_EMPTY_PATH))
|
|
1239
|
+
throw errno("EINVAL");
|
|
1240
|
+
// AT_EMPTY_PATH needs the capability model and an inode handle for an
|
|
1241
|
+
// unlinked source. Do not substitute the descriptor's old pathname.
|
|
1242
|
+
if (!sourcePath && (flags & AT_EMPTY_PATH))
|
|
1243
|
+
throw errno("EPERM");
|
|
1244
|
+
if (!sourcePath || !destinationPath)
|
|
1245
|
+
throw errno("ENOENT");
|
|
1246
|
+
const source = this.resolveFilesystemPath(this.resolveAt(sourceDirfd, sourcePath), Boolean(flags & follow));
|
|
1247
|
+
const destination = this.resolveFilesystemPath(this.resolveAt(destinationDirfd, destinationPath), false);
|
|
1248
|
+
checkFileName(destination);
|
|
1249
|
+
if (this.virtualStat(source, true) || waliDeviceStat(source))
|
|
1250
|
+
throw errno("EXDEV");
|
|
1251
|
+
const fs = this.fs();
|
|
1252
|
+
const stat = fs.lstatSync?.(source) ?? fs.statSync(source);
|
|
1253
|
+
if (stat.isDirectory())
|
|
1254
|
+
throw errno("EPERM");
|
|
1255
|
+
if (this.virtualStat(destination, true) || waliDeviceStat(destination) || fs.existsSync(destination))
|
|
1256
|
+
throw errno("EEXIST");
|
|
1257
|
+
const directory = fs.statSync(destination.slice(0, destination.lastIndexOf("/")) || "/");
|
|
1258
|
+
if (!directory.isDirectory())
|
|
1259
|
+
throw errno("ENOTDIR");
|
|
1260
|
+
checkFilePermission(directory, 3);
|
|
1261
|
+
if (!fs.linkSync)
|
|
1262
|
+
throw errno("ENOSYS");
|
|
1263
|
+
fs.linkSync(source, destination);
|
|
1264
|
+
this.options.fileLocks?.created?.(destination);
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
rename(from, to, fromDirfd = AT_FDCWD, toDirfd = AT_FDCWD) {
|
|
991
1268
|
return this.mutate(() => {
|
|
992
|
-
const source = this.resolveFilesystemPath(this.
|
|
993
|
-
const destination = this.resolveFilesystemPath(this.
|
|
1269
|
+
const source = this.resolveFilesystemPath(this.resolveAt(fromDirfd, from), false);
|
|
1270
|
+
const destination = this.resolveFilesystemPath(this.resolveAt(toDirfd, to), false);
|
|
994
1271
|
// Moving a directory the held tree is under moves the tree: the libc's to release first (ADR-0047).
|
|
995
1272
|
if (this.privateTree && (underRoot(this.privateTree.root, source) || underRoot(this.privateTree.root, destination)))
|
|
996
1273
|
throw errno("EREMOTE");
|
|
@@ -1003,11 +1280,14 @@ export class WaliFileDescriptorTable {
|
|
|
1003
1280
|
if (fs.existsSync(source)) {
|
|
1004
1281
|
if (source === destination)
|
|
1005
1282
|
return;
|
|
1006
|
-
|
|
1283
|
+
if (source === "/" || destination === "/")
|
|
1284
|
+
throw errno("EBUSY");
|
|
1285
|
+
checkFileName(destination);
|
|
1286
|
+
const directory = (fs.lstatSync?.(source) ?? fs.statSync(source)).isDirectory();
|
|
1007
1287
|
if (directory && destination.startsWith(`${source}/`))
|
|
1008
1288
|
throw errno("EINVAL");
|
|
1009
1289
|
if (fs.existsSync(destination)) {
|
|
1010
|
-
const into = fs.statSync(destination).isDirectory();
|
|
1290
|
+
const into = (fs.lstatSync?.(destination) ?? fs.statSync(destination)).isDirectory();
|
|
1011
1291
|
if (directory && !into)
|
|
1012
1292
|
throw errno("ENOTDIR");
|
|
1013
1293
|
if (!directory && into)
|
|
@@ -1051,8 +1331,8 @@ export class WaliFileDescriptorTable {
|
|
|
1051
1331
|
};
|
|
1052
1332
|
const atimeMs = readTime(0, stat.atimeMs ?? stat.mtimeMs ?? now);
|
|
1053
1333
|
const mtimeMs = readTime(1, stat.mtimeMs ?? now);
|
|
1054
|
-
if (filesystem.utimesSync)
|
|
1055
|
-
filesystem
|
|
1334
|
+
if (filesystem.utimesSync || filesystem.utimesMsSync)
|
|
1335
|
+
setBrowserFileTimes(filesystem, path, atimeMs, mtimeMs);
|
|
1056
1336
|
else if (stat.isFile())
|
|
1057
1337
|
filesystem.writeFileSync(path, filesystem.readFileSync(path));
|
|
1058
1338
|
return 0n;
|
|
@@ -1063,12 +1343,12 @@ export class WaliFileDescriptorTable {
|
|
|
1063
1343
|
}
|
|
1064
1344
|
readlink(path, pointer, size, dirfd) {
|
|
1065
1345
|
try {
|
|
1066
|
-
if (!Number.isSafeInteger(size) || size
|
|
1346
|
+
if (!Number.isSafeInteger(size) || size <= 0)
|
|
1067
1347
|
throw errno("EINVAL");
|
|
1068
|
-
|
|
1348
|
+
let resolved = this.resolveAt(dirfd, path);
|
|
1069
1349
|
if (this.privateTree && underRoot(resolved, this.privateTree.root))
|
|
1070
1350
|
throw errno("EREMOTE");
|
|
1071
|
-
let target;
|
|
1351
|
+
let target = this.virtualLink(resolved);
|
|
1072
1352
|
if (resolved === "/proc/self/exe")
|
|
1073
1353
|
target = this.options.executablePath ?? "/usr/bin/wali-program";
|
|
1074
1354
|
const descriptorMatch = /^\/proc\/self\/fd\/(\d+)$/u.exec(resolved);
|
|
@@ -1077,12 +1357,14 @@ export class WaliFileDescriptorTable {
|
|
|
1077
1357
|
if (descriptor)
|
|
1078
1358
|
target = descriptor.path;
|
|
1079
1359
|
}
|
|
1360
|
+
if (target === undefined)
|
|
1361
|
+
resolved = this.resolveFilesystemPath(resolved, false);
|
|
1080
1362
|
target ??= this.readSymlink(resolved);
|
|
1081
1363
|
// musl realpath(3) probes every path component with readlink(2).
|
|
1082
1364
|
// Existing non-symlinks must report EINVAL; ENOENT means the component
|
|
1083
1365
|
// itself is missing and makes canonicalization fail.
|
|
1084
1366
|
if (!target)
|
|
1085
|
-
return this.fs().existsSync(resolved) ? -22n : -2n;
|
|
1367
|
+
return this.virtualStat(resolved, false) || waliDeviceStat(resolved) || this.fs().existsSync(resolved) ? -22n : -2n;
|
|
1086
1368
|
const encoded = new TextEncoder().encode(target).subarray(0, size);
|
|
1087
1369
|
this.copyIn(pointer, encoded);
|
|
1088
1370
|
return BigInt(encoded.byteLength);
|
|
@@ -1094,6 +1376,7 @@ export class WaliFileDescriptorTable {
|
|
|
1094
1376
|
symlink(target, link, dirfd) {
|
|
1095
1377
|
try {
|
|
1096
1378
|
const resolved = this.resolveFilesystemPath(this.resolveAt(dirfd, link), false);
|
|
1379
|
+
checkFileName(resolved);
|
|
1097
1380
|
writeWaliSymlink(this.fs(), target, resolved);
|
|
1098
1381
|
return 0n;
|
|
1099
1382
|
}
|
|
@@ -1101,8 +1384,21 @@ export class WaliFileDescriptorTable {
|
|
|
1101
1384
|
return negativeErrno(error);
|
|
1102
1385
|
}
|
|
1103
1386
|
}
|
|
1104
|
-
resolveFilesystemPath(path, followFinal = true) {
|
|
1105
|
-
const resolved = resolveWaliSymlinkPath(this.fs(), path, followFinal
|
|
1387
|
+
resolveFilesystemPath(path, followFinal = true, allowMissingDirectory = false) {
|
|
1388
|
+
const resolved = resolveWaliSymlinkPath(this.fs(), path, followFinal, allowMissingDirectory, candidate => {
|
|
1389
|
+
const target = this.virtualLink(candidate, false);
|
|
1390
|
+
if (target !== undefined)
|
|
1391
|
+
return { directory: false, target };
|
|
1392
|
+
if (["/proc", "/proc/self", "/proc/self/fd"].includes(candidate))
|
|
1393
|
+
return { directory: true };
|
|
1394
|
+
const fd = this.procDescriptor(candidate);
|
|
1395
|
+
if (fd === undefined || !this.hasFd(fd))
|
|
1396
|
+
return;
|
|
1397
|
+
const file = this.files.get(fd);
|
|
1398
|
+
// Directory magic links must support descendants (fd/N/name). Files
|
|
1399
|
+
// remain magic leaves so reopen applies the caller's requested flags.
|
|
1400
|
+
return file?.directory ? { directory: false, target: file.path } : { directory: false };
|
|
1401
|
+
});
|
|
1106
1402
|
// A held private tree is the libc's: a path that reaches it here (a symlink
|
|
1107
1403
|
// elsewhere, a directory descriptor) is EREMOTE, and the libc releases the
|
|
1108
1404
|
// tree and asks again (ADR-0047 §3).
|
|
@@ -1110,11 +1406,136 @@ export class WaliFileDescriptorTable {
|
|
|
1110
1406
|
throw errno("EREMOTE");
|
|
1111
1407
|
return resolved;
|
|
1112
1408
|
}
|
|
1409
|
+
procDescriptor(path) {
|
|
1410
|
+
const match = /^\/proc\/self\/fd\/(\d+)$/u.exec(path);
|
|
1411
|
+
return match ? Number(match[1]) : undefined;
|
|
1412
|
+
}
|
|
1413
|
+
virtualLink(path, descriptors = true) {
|
|
1414
|
+
if (path === "/dev/fd")
|
|
1415
|
+
return "/proc/self/fd";
|
|
1416
|
+
if (path === "/dev/stdin")
|
|
1417
|
+
return "/proc/self/fd/0";
|
|
1418
|
+
if (path === "/dev/stdout")
|
|
1419
|
+
return "/proc/self/fd/1";
|
|
1420
|
+
if (path === "/dev/stderr")
|
|
1421
|
+
return "/proc/self/fd/2";
|
|
1422
|
+
if (!descriptors)
|
|
1423
|
+
return;
|
|
1424
|
+
const fd = this.procDescriptor(path);
|
|
1425
|
+
if (fd === undefined)
|
|
1426
|
+
return;
|
|
1427
|
+
const description = this.descriptors.get(fd);
|
|
1428
|
+
if (!description)
|
|
1429
|
+
throw errno("ENOENT");
|
|
1430
|
+
const file = this.files.get(fd);
|
|
1431
|
+
if (file)
|
|
1432
|
+
return file.path;
|
|
1433
|
+
if (description.kind === "pipe")
|
|
1434
|
+
return `pipe:[${description.value.pipe + 1}]`;
|
|
1435
|
+
const special = this.specialDescriptors.get(fd);
|
|
1436
|
+
const rdev = special?.stat?.().rdev;
|
|
1437
|
+
if (rdev !== undefined)
|
|
1438
|
+
return rdev === 0x103 ? "/dev/null" : rdev === 0x108 ? "/dev/random"
|
|
1439
|
+
: rdev === 0x109 ? "/dev/urandom" : "/dev/tty";
|
|
1440
|
+
if (special?.stdio !== undefined)
|
|
1441
|
+
return "/dev/tty";
|
|
1442
|
+
return `anon_inode:[${description.kind}]`;
|
|
1443
|
+
}
|
|
1444
|
+
virtualStat(path, noFollow) {
|
|
1445
|
+
if (noFollow) {
|
|
1446
|
+
const target = this.virtualLink(path);
|
|
1447
|
+
if (target !== undefined)
|
|
1448
|
+
return syntheticSymlinkStat(target);
|
|
1449
|
+
}
|
|
1450
|
+
if (["/proc", "/proc/self", "/proc/self/fd"].includes(path))
|
|
1451
|
+
return {
|
|
1452
|
+
isFile: () => false, isDirectory: () => true, size: 0, mode: 0o040555,
|
|
1453
|
+
};
|
|
1454
|
+
const fd = this.procDescriptor(path);
|
|
1455
|
+
if (fd === undefined)
|
|
1456
|
+
return;
|
|
1457
|
+
const description = this.descriptors.get(fd);
|
|
1458
|
+
if (!description)
|
|
1459
|
+
throw errno("ENOENT");
|
|
1460
|
+
const file = this.files.get(fd);
|
|
1461
|
+
if (file)
|
|
1462
|
+
return (file.inode?.stat() ?? this.fs().statSync(file.path));
|
|
1463
|
+
if (description.kind === "pipe")
|
|
1464
|
+
return {
|
|
1465
|
+
isFile: () => false, isDirectory: () => false, size: 0, mode: 0o010666,
|
|
1466
|
+
ino: description.value.pipe + 1,
|
|
1467
|
+
};
|
|
1468
|
+
return this.specialDescriptors.get(fd)?.stat?.() ?? syntheticTerminalStat();
|
|
1469
|
+
}
|
|
1470
|
+
reopenDescriptor(fd, flags, mode) {
|
|
1471
|
+
const description = this.descriptors.get(fd);
|
|
1472
|
+
if (!description)
|
|
1473
|
+
throw errno("ENOENT");
|
|
1474
|
+
const stat = this.virtualStat(`/proc/self/fd/${fd}`, false);
|
|
1475
|
+
if ((flags & O_DIRECTORY) && !stat.isDirectory())
|
|
1476
|
+
throw errno("ENOTDIR");
|
|
1477
|
+
if ((flags & O_ACCMODE) !== O_WRONLY)
|
|
1478
|
+
checkFilePermission(stat, 4);
|
|
1479
|
+
if ((flags & O_ACCMODE) !== 0)
|
|
1480
|
+
checkFilePermission(stat, 2);
|
|
1481
|
+
const file = this.files.get(fd);
|
|
1482
|
+
if (file) {
|
|
1483
|
+
if (!file.inode)
|
|
1484
|
+
return this.openResolved(file.path, flags & ~(O_CREAT | O_EXCL), mode);
|
|
1485
|
+
if ((flags & O_ACCMODE) !== 0 || (flags & O_TRUNC))
|
|
1486
|
+
this.assertWritable(file.path);
|
|
1487
|
+
const next = this.allocateFd();
|
|
1488
|
+
const inode = file.inode.retain();
|
|
1489
|
+
try {
|
|
1490
|
+
if (flags & O_TRUNC)
|
|
1491
|
+
inode.truncate(0);
|
|
1492
|
+
this.files.set(next, { path: file.path, inode, flags, position: 0, directory: false,
|
|
1493
|
+
description: this.nextDescription++ });
|
|
1494
|
+
return this.markCloseOnExecNumber(next, flags);
|
|
1495
|
+
}
|
|
1496
|
+
catch (error) {
|
|
1497
|
+
inode.close();
|
|
1498
|
+
throw error;
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
if (description.kind === "pipe" && this.options.descriptorState) {
|
|
1502
|
+
const host = new WaliSharedPipeHost(this.options.memory, { ...this.options.descriptorState,
|
|
1503
|
+
table: this.descriptors.sharedEntries.buffer }, this.descriptors);
|
|
1504
|
+
const result = host.reopen(fd, flags);
|
|
1505
|
+
if (result < 0n)
|
|
1506
|
+
throw errno(result === -24n ? "EMFILE" : "EOPNOTSUPP");
|
|
1507
|
+
return Number(result);
|
|
1508
|
+
}
|
|
1509
|
+
const target = this.virtualLink(`/proc/self/fd/${fd}`);
|
|
1510
|
+
if (target === "/dev/tty" && this.specialDescriptors.get(fd)?.terminal)
|
|
1511
|
+
return this.openResolved(target, flags, mode);
|
|
1512
|
+
const device = target && createWaliDevice(target, flags);
|
|
1513
|
+
if (device) {
|
|
1514
|
+
const next = this.allocateFd();
|
|
1515
|
+
this.specialDescriptors.set(next, device);
|
|
1516
|
+
return this.markCloseOnExecNumber(next, flags);
|
|
1517
|
+
}
|
|
1518
|
+
const special = this.specialDescriptors.get(fd);
|
|
1519
|
+
if (special?.stdio !== undefined) {
|
|
1520
|
+
// Host streams currently expose one direction rather than an inode
|
|
1521
|
+
// that can be reopened in either direction. Do not return a descriptor
|
|
1522
|
+
// whose access mode promises I/O its transport cannot perform.
|
|
1523
|
+
if ((flags & O_ACCMODE) !== (special.stdio === 0 ? 0 : O_WRONLY))
|
|
1524
|
+
throw errno("EOPNOTSUPP");
|
|
1525
|
+
const next = this.allocateFd();
|
|
1526
|
+
this.specialDescriptors.set(next, { stdio: special.stdio });
|
|
1527
|
+
Atomics.store(this.descriptors.registry.pipeMetadata, this.descriptors.entries[next].description * 3 + 1, flags & ~O_CLOEXEC_FLAG);
|
|
1528
|
+
return this.markCloseOnExecNumber(next, flags);
|
|
1529
|
+
}
|
|
1530
|
+
throw errno("EOPNOTSUPP");
|
|
1531
|
+
}
|
|
1113
1532
|
readSymlink(path) {
|
|
1114
1533
|
return readWaliSymlink(this.fs(), path);
|
|
1115
1534
|
}
|
|
1116
|
-
dup(fd, target, minimum =
|
|
1117
|
-
|
|
1535
|
+
dup(fd, target, minimum = 0) {
|
|
1536
|
+
if (target === fd && this.hasFd(fd))
|
|
1537
|
+
return BigInt(fd);
|
|
1538
|
+
const maximum = Math.min(this.options.descriptorMaximum ?? 65_535, this.descriptors.descriptorLimit - 1);
|
|
1118
1539
|
const stdio = this.stdioFd(fd);
|
|
1119
1540
|
if (stdio !== undefined) {
|
|
1120
1541
|
const next = target ?? this.allocateFd(minimum);
|
|
@@ -1122,8 +1543,8 @@ export class WaliFileDescriptorTable {
|
|
|
1122
1543
|
return -9n;
|
|
1123
1544
|
if (next === fd)
|
|
1124
1545
|
return BigInt(next);
|
|
1546
|
+
this.close(next);
|
|
1125
1547
|
this.descriptors.set(next, this.descriptors.get(fd));
|
|
1126
|
-
this.descriptorStatusFlags.set(next, this.statusFlags(fd));
|
|
1127
1548
|
this.setSharedStdioAlias(next, stdio);
|
|
1128
1549
|
return BigInt(next);
|
|
1129
1550
|
}
|
|
@@ -1147,7 +1568,7 @@ export class WaliFileDescriptorTable {
|
|
|
1147
1568
|
// wasmfs/syscalls.cpp __syscall_fcntl64 F_DUPFD: every open file,
|
|
1148
1569
|
// including special files, shares its description through the table.
|
|
1149
1570
|
// Our kernel retains process ownership and FD_CLOEXEC semantics.
|
|
1150
|
-
if (argument < 0 || argument > (this.options.descriptorMaximum ?? 65_535))
|
|
1571
|
+
if (argument < 0 || argument >= this.descriptors.descriptorLimit || argument > (this.options.descriptorMaximum ?? 65_535))
|
|
1151
1572
|
return -22n;
|
|
1152
1573
|
if (special?.duplicate)
|
|
1153
1574
|
return special.duplicate(argument, command === 1030);
|
|
@@ -1176,13 +1597,18 @@ export class WaliFileDescriptorTable {
|
|
|
1176
1597
|
if (command === 3)
|
|
1177
1598
|
return BigInt(this.statusFlags(fd));
|
|
1178
1599
|
if (command === 4) {
|
|
1600
|
+
// libsyscall F_SETFL: change only mutable status bits. Access mode,
|
|
1601
|
+
// creation flags and descriptor CLOEXEC are not replaced by the argument.
|
|
1602
|
+
const mutable = O_APPEND | O_NONBLOCK | 0x2000 /* O_ASYNC */ | 0x4000 /* O_DIRECT */ | 0x40000 /* O_NOATIME */;
|
|
1179
1603
|
const stdio = this.stdioFd(fd);
|
|
1180
|
-
if (stdio !== undefined)
|
|
1181
|
-
this.
|
|
1604
|
+
if (stdio !== undefined) {
|
|
1605
|
+
const id = this.descriptors.entries[fd].description;
|
|
1606
|
+
Atomics.store(this.descriptors.registry.pipeMetadata, id * 3 + 1, (this.statusFlags(fd) & ~mutable) | (argument & mutable));
|
|
1607
|
+
}
|
|
1182
1608
|
else {
|
|
1183
1609
|
const file = this.files.get(fd);
|
|
1184
1610
|
if (file)
|
|
1185
|
-
file.flags = (file.flags &
|
|
1611
|
+
file.flags = (file.flags & ~mutable) | (argument & mutable);
|
|
1186
1612
|
}
|
|
1187
1613
|
return 0n;
|
|
1188
1614
|
}
|
|
@@ -1241,7 +1667,7 @@ export class WaliFileDescriptorTable {
|
|
|
1241
1667
|
const offset = safeOffset(view.getBigInt64(pointer + 8, true));
|
|
1242
1668
|
const length = safeOffset(view.getBigInt64(pointer + 16, true));
|
|
1243
1669
|
const base = whence === SEEK_SET ? 0 : whence === SEEK_CUR ? file.position
|
|
1244
|
-
: whence === SEEK_END ? this.fs().statSync(file.path).size : undefined;
|
|
1670
|
+
: whence === SEEK_END ? (file.inode?.stat() ?? this.fs().statSync(file.path)).size : undefined;
|
|
1245
1671
|
if (base === undefined)
|
|
1246
1672
|
throw errno("EINVAL");
|
|
1247
1673
|
let start = base + offset;
|
|
@@ -1347,6 +1773,9 @@ export class WaliFileDescriptorTable {
|
|
|
1347
1773
|
return BigInt(scan());
|
|
1348
1774
|
}
|
|
1349
1775
|
hasFd(fd) {
|
|
1776
|
+
if (this.options.descriptorState && !this.options.descriptors)
|
|
1777
|
+
return Number.isInteger(fd) && fd >= 0 && fd < 65_536
|
|
1778
|
+
&& Atomics.load(new Int32Array(this.options.descriptorState.table), fd) !== 0;
|
|
1350
1779
|
return this.descriptors.get(fd) !== undefined;
|
|
1351
1780
|
}
|
|
1352
1781
|
/** Descriptor aliases retain the identity of their terminal stream. */
|
|
@@ -1355,24 +1784,46 @@ export class WaliFileDescriptorTable {
|
|
|
1355
1784
|
const state = this.options.descriptorState;
|
|
1356
1785
|
if (fd < 0 || fd >= 65_536)
|
|
1357
1786
|
return;
|
|
1358
|
-
const id = Atomics.load(new Int32Array(state.table), fd);
|
|
1359
|
-
const
|
|
1787
|
+
const id = Atomics.load(this.options.descriptors ? this.descriptors.sharedEntries : new Int32Array(state.table), fd);
|
|
1788
|
+
const encoded = Atomics.load(new Int32Array(state.descriptions), id * 3 + 2);
|
|
1789
|
+
if (encoded === 4)
|
|
1790
|
+
return 0;
|
|
1791
|
+
const stream = encoded - 1;
|
|
1360
1792
|
return stream >= 0 && stream <= 2 ? stream : undefined;
|
|
1361
1793
|
}
|
|
1362
1794
|
return this.specialDescriptors.get(fd)?.stdio;
|
|
1363
1795
|
}
|
|
1796
|
+
terminalFd(fd) {
|
|
1797
|
+
const state = this.options.descriptorState;
|
|
1798
|
+
if (!state)
|
|
1799
|
+
return this.specialDescriptors.get(fd)?.terminal === true;
|
|
1800
|
+
if (fd < 0 || fd >= 65_536)
|
|
1801
|
+
return false;
|
|
1802
|
+
const id = Atomics.load(this.options.descriptors ? this.descriptors.sharedEntries : new Int32Array(state.table), fd);
|
|
1803
|
+
return Atomics.load(new Int32Array(state.descriptions), id * 3 + 2) === 4;
|
|
1804
|
+
}
|
|
1805
|
+
terminalAvailable(pointer) {
|
|
1806
|
+
if (!this.options.terminalStdin)
|
|
1807
|
+
return -95n;
|
|
1808
|
+
if (pointer <= 0 || pointer + 4 > this.options.memory().buffer.byteLength)
|
|
1809
|
+
return -14n;
|
|
1810
|
+
this.memoryView().setInt32(pointer, this.options.terminalStdin.available?.() ?? 0, true);
|
|
1811
|
+
return 0n;
|
|
1812
|
+
}
|
|
1364
1813
|
setSharedStdioAlias(fd, target) {
|
|
1365
1814
|
if (fd < 0 || fd >= (this.options.stdioAliasRegistry?.length ?? 0))
|
|
1366
1815
|
return;
|
|
1367
1816
|
Atomics.store(this.options.stdioAliasRegistry, fd, target ?? -1);
|
|
1368
1817
|
}
|
|
1369
1818
|
statusFlags(fd) {
|
|
1370
|
-
const stored = this.descriptorStatusFlags.get(fd);
|
|
1371
|
-
if (stored !== undefined)
|
|
1372
|
-
return stored;
|
|
1373
1819
|
const stdio = this.stdioFd(fd);
|
|
1374
|
-
if (stdio !== undefined)
|
|
1375
|
-
|
|
1820
|
+
if (stdio !== undefined) {
|
|
1821
|
+
const state = this.options.descriptorState;
|
|
1822
|
+
const table = state && !this.options.descriptors ? new Int32Array(state.table) : this.descriptors.sharedEntries;
|
|
1823
|
+
const metadata = state ? new Int32Array(state.descriptions) : this.descriptors.registry.pipeMetadata;
|
|
1824
|
+
const id = Atomics.load(table, fd);
|
|
1825
|
+
return Atomics.load(metadata, id * 3 + 1) | (stdio === 0 || this.terminalFd(fd) ? 0 : O_WRONLY);
|
|
1826
|
+
}
|
|
1376
1827
|
return this.files.get(fd)?.flags ?? O_RDWR;
|
|
1377
1828
|
}
|
|
1378
1829
|
mutate(operation) {
|
|
@@ -1409,7 +1860,7 @@ export class WaliFileDescriptorTable {
|
|
|
1409
1860
|
else
|
|
1410
1861
|
fs.writeFileSync(node.path, node.content);
|
|
1411
1862
|
fs.chmodSync?.(node.path, node.mode & 0o7777);
|
|
1412
|
-
fs
|
|
1863
|
+
setBrowserFileTimes(fs, node.path, node.atimeMs, node.mtimeMs);
|
|
1413
1864
|
}
|
|
1414
1865
|
}
|
|
1415
1866
|
this.privateTree = undefined;
|
|
@@ -1546,7 +1997,7 @@ export class WaliFileDescriptorTable {
|
|
|
1546
1997
|
else
|
|
1547
1998
|
fs.writeFileSync(node.path, node.content);
|
|
1548
1999
|
fs.chmodSync?.(node.path, node.mode & 0o7777);
|
|
1549
|
-
fs
|
|
2000
|
+
setBrowserFileTimes(fs, node.path, node.atimeMs, node.mtimeMs);
|
|
1550
2001
|
}
|
|
1551
2002
|
}
|
|
1552
2003
|
for (const descriptor of descriptors) {
|
|
@@ -1555,7 +2006,10 @@ export class WaliFileDescriptorTable {
|
|
|
1555
2006
|
if (descriptor.group === descriptor.fd) {
|
|
1556
2007
|
this.files.set(descriptor.fd, {
|
|
1557
2008
|
path: descriptor.path, flags: descriptor.flags, position: descriptor.position,
|
|
1558
|
-
directory: descriptor.
|
|
2009
|
+
...(!descriptor.directory && fs.openFileSync ? { inode: fs.openFileSync(descriptor.path) } : {}),
|
|
2010
|
+
directory: descriptor.directory,
|
|
2011
|
+
...(descriptor.directory ? { entries: this.directoryEntries(descriptor.path) } : {}),
|
|
2012
|
+
description: this.nextDescription++,
|
|
1559
2013
|
});
|
|
1560
2014
|
}
|
|
1561
2015
|
else {
|
|
@@ -1570,6 +2024,19 @@ export class WaliFileDescriptorTable {
|
|
|
1570
2024
|
return 0n;
|
|
1571
2025
|
}
|
|
1572
2026
|
}
|
|
2027
|
+
/** WasmFS's single-user mode checks; identity/credential policy is a separate seam. */
|
|
2028
|
+
function checkFilePermission(stat, requested) {
|
|
2029
|
+
const mode = stat.mode ?? (stat.isDirectory() ? 0o777 : 0o666);
|
|
2030
|
+
if (((requested & 4) && !(mode & 0o444))
|
|
2031
|
+
|| ((requested & 2) && !(mode & 0o222))
|
|
2032
|
+
|| ((requested & 1) && !(mode & 0o111)))
|
|
2033
|
+
throw errno("EACCES");
|
|
2034
|
+
}
|
|
2035
|
+
function checkFileName(path) {
|
|
2036
|
+
// WASMFS_NAME_MAX is Linux's 255-byte component limit, not a work budget.
|
|
2037
|
+
if (new TextEncoder().encode(path.slice(path.lastIndexOf("/") + 1)).byteLength > 255)
|
|
2038
|
+
throw errno("ENAMETOOLONG");
|
|
2039
|
+
}
|
|
1573
2040
|
/** preadv's offset: one 64-bit argument, or two 32-bit halves from a libc that splits it (musl's own preadv.c). */
|
|
1574
2041
|
function vectorOffset(offset, high) {
|
|
1575
2042
|
if (high === undefined)
|
|
@@ -1585,19 +2052,11 @@ function normalizePath(cwd, path) {
|
|
|
1585
2052
|
// Most paths a program names are already normal: itself, without the split.
|
|
1586
2053
|
if (isNormalPath(path))
|
|
1587
2054
|
return path;
|
|
1588
|
-
const components = [];
|
|
1589
2055
|
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("/")}`;
|
|
2056
|
+
const normalized = normalizeWaliPath(value);
|
|
2057
|
+
return normalized === "/" ? "/" : normalized.replace(/\/$/u, "");
|
|
1599
2058
|
}
|
|
1600
|
-
/** Absolute
|
|
2059
|
+
/** Absolute with no empty/dot component or trailing slash; backslash is a literal character. */
|
|
1601
2060
|
function isNormalPath(path) {
|
|
1602
2061
|
if (path.charCodeAt(0) !== 47)
|
|
1603
2062
|
return false;
|
|
@@ -1606,8 +2065,6 @@ function isNormalPath(path) {
|
|
|
1606
2065
|
let start = 1;
|
|
1607
2066
|
for (let index = 1; index <= path.length; index += 1) {
|
|
1608
2067
|
const code = index < path.length ? path.charCodeAt(index) : 47;
|
|
1609
|
-
if (code === 92)
|
|
1610
|
-
return false;
|
|
1611
2068
|
if (code !== 47)
|
|
1612
2069
|
continue;
|
|
1613
2070
|
const length = index - start;
|
|
@@ -1637,9 +2094,9 @@ function assertRange(memory, pointer, length) {
|
|
|
1637
2094
|
throw new WebAssembly.RuntimeError("WALI filesystem memory access is out of bounds.");
|
|
1638
2095
|
}
|
|
1639
2096
|
/** The stat of `path`, or undefined when the store says ENOENT; any other failure is thrown. */
|
|
1640
|
-
function statIfPresent(fs, path) {
|
|
2097
|
+
function statIfPresent(fs, path, noFollow = false) {
|
|
1641
2098
|
try {
|
|
1642
|
-
return fs.statSync(path);
|
|
2099
|
+
return noFollow && fs.lstatSync ? fs.lstatSync(path) : fs.statSync(path);
|
|
1643
2100
|
}
|
|
1644
2101
|
catch (error) {
|
|
1645
2102
|
if (/\bENOENT\b/u.test(error instanceof Error ? error.message : String(error)))
|
|
@@ -1657,8 +2114,9 @@ function writeStat(view, pointer, stat) {
|
|
|
1657
2114
|
setUint64(view, pointer, stat.dev ?? 1);
|
|
1658
2115
|
setUint64(view, pointer + 8, stat.ino ?? 1);
|
|
1659
2116
|
setUint64(view, pointer + 16, stat.nlink ?? 1);
|
|
1660
|
-
|
|
1661
|
-
|
|
2117
|
+
// A device supplies S_IFCHR; ORing S_IFREG into it made it S_IFLNK.
|
|
2118
|
+
const kind = (stat.mode ?? 0) & 0o170000 || (stat.isSymbolicLink?.() ? S_IFLNK : stat.isDirectory() ? S_IFDIR : S_IFREG);
|
|
2119
|
+
view.setUint32(pointer + 24, kind | ((stat.mode ?? (stat.isDirectory() ? 0o755 : 0o644)) & 0o7777), true);
|
|
1662
2120
|
view.setUint32(pointer + 28, stat.uid ?? 1000, true);
|
|
1663
2121
|
view.setUint32(pointer + 32, stat.gid ?? 1000, true);
|
|
1664
2122
|
view.setUint32(pointer + 36, 0, true);
|
|
@@ -1732,10 +2190,10 @@ function errno(errnoName) {
|
|
|
1732
2190
|
return error;
|
|
1733
2191
|
}
|
|
1734
2192
|
const ERRNO_NUMBERS = {
|
|
1735
|
-
EPERM: 1n, ENOENT: 2n, EIO: 5n, EBADF: 9n, EAGAIN: 11n, ENOMEM: 12n, EACCES: 13n,
|
|
2193
|
+
EPERM: 1n, ENOENT: 2n, EINTR: 4n, EIO: 5n, ENXIO: 6n, EBADF: 9n, EAGAIN: 11n, ENOMEM: 12n, EACCES: 13n,
|
|
1736
2194
|
EFAULT: 14n, EBUSY: 16n, EEXIST: 17n, EXDEV: 18n, ENOTDIR: 20n, EISDIR: 21n,
|
|
1737
2195
|
EINVAL: 22n, EMFILE: 24n, ENOSPC: 28n, ESPIPE: 29n, EROFS: 30n, ERANGE: 34n,
|
|
1738
|
-
ENOSYS: 38n, ENOTEMPTY: 39n, ELOOP: 40n, ENAMETOOLONG: 36n, EREMOTE: 66n,
|
|
2196
|
+
ENOSYS: 38n, ENOTEMPTY: 39n, ELOOP: 40n, ENAMETOOLONG: 36n, EREMOTE: 66n, EOPNOTSUPP: 95n,
|
|
1739
2197
|
};
|
|
1740
2198
|
function negativeErrno(error) {
|
|
1741
2199
|
// The errno's own name first, and the message parsed only for an error that has none.
|