@volter/browser-wali 0.1.5 → 0.1.7
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/README.md +73 -0
- package/dist/browser-wali-command.d.ts +3 -0
- package/dist/browser-wali-command.d.ts.map +1 -1
- package/dist/browser-wali-command.js +2 -1
- package/dist/browser-wali-command.js.map +1 -1
- package/dist/capabilities.d.ts +73 -1
- package/dist/capabilities.d.ts.map +1 -1
- package/dist/capabilities.js +86 -2
- package/dist/capabilities.js.map +1 -1
- package/dist/child-processes.d.ts +24 -0
- package/dist/child-processes.d.ts.map +1 -0
- package/dist/child-processes.js +134 -0
- package/dist/child-processes.js.map +1 -0
- package/dist/codec-protocol.d.ts +66 -0
- package/dist/codec-protocol.d.ts.map +1 -0
- package/dist/codec-protocol.js +59 -0
- package/dist/codec-protocol.js.map +1 -0
- package/dist/codec-remote.d.ts +33 -0
- package/dist/codec-remote.d.ts.map +1 -0
- package/dist/codec-remote.js +190 -0
- package/dist/codec-remote.js.map +1 -0
- package/dist/codec-worker.d.ts +2 -0
- package/dist/codec-worker.d.ts.map +1 -0
- package/dist/codec-worker.js +438 -0
- package/dist/codec-worker.js.map +1 -0
- package/dist/coherent-file-locks.d.ts.map +1 -1
- package/dist/coherent-file-locks.js +17 -5
- package/dist/coherent-file-locks.js.map +1 -1
- package/dist/descriptors.d.ts +49 -0
- package/dist/descriptors.d.ts.map +1 -0
- package/dist/descriptors.js +115 -0
- package/dist/descriptors.js.map +1 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +2 -2
- package/dist/events.js.map +1 -1
- package/dist/filesystem-protocol.d.ts +28 -1
- package/dist/filesystem-protocol.d.ts.map +1 -1
- package/dist/filesystem-protocol.js +18 -0
- package/dist/filesystem-protocol.js.map +1 -1
- package/dist/filesystem-remote.d.ts +26 -2
- package/dist/filesystem-remote.d.ts.map +1 -1
- package/dist/filesystem-remote.js +132 -23
- package/dist/filesystem-remote.js.map +1 -1
- package/dist/filesystem-worker.js +274 -34
- package/dist/filesystem-worker.js.map +1 -1
- package/dist/filesystem.d.ts +30 -1
- package/dist/filesystem.d.ts.map +1 -1
- package/dist/filesystem.js +180 -50
- package/dist/filesystem.js.map +1 -1
- package/dist/fork-worker.d.ts +2 -0
- package/dist/fork-worker.d.ts.map +1 -0
- package/dist/fork-worker.js +52 -0
- package/dist/fork-worker.js.map +1 -0
- package/dist/host.d.ts +36 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +153 -0
- package/dist/host.js.map +1 -0
- package/dist/loopback-host.d.ts +25 -1
- package/dist/loopback-host.d.ts.map +1 -1
- package/dist/loopback-host.js +447 -13
- package/dist/loopback-host.js.map +1 -1
- package/dist/memory.d.ts +65 -2
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +306 -49
- package/dist/memory.js.map +1 -1
- package/dist/module-layout.d.ts +2 -0
- package/dist/module-layout.d.ts.map +1 -1
- package/dist/module-layout.js +4 -2
- package/dist/module-layout.js.map +1 -1
- package/dist/monotonic-clock.d.ts +12 -0
- package/dist/monotonic-clock.d.ts.map +1 -0
- package/dist/monotonic-clock.js +14 -0
- package/dist/monotonic-clock.js.map +1 -0
- package/dist/network-protocol.d.ts +12 -1
- package/dist/network-protocol.d.ts.map +1 -1
- package/dist/network-protocol.js +6 -0
- package/dist/network-protocol.js.map +1 -1
- package/dist/network-worker.js +149 -20
- package/dist/network-worker.js.map +1 -1
- package/dist/network.d.ts +15 -3
- package/dist/network.d.ts.map +1 -1
- package/dist/network.js +74 -39
- package/dist/network.js.map +1 -1
- package/dist/process-adapter.d.ts +4 -1
- package/dist/process-adapter.d.ts.map +1 -1
- package/dist/process-adapter.js +3 -3
- package/dist/process-adapter.js.map +1 -1
- package/dist/process-bridge.d.ts +43 -0
- package/dist/process-bridge.d.ts.map +1 -1
- package/dist/process-bridge.js +30 -1
- package/dist/process-bridge.js.map +1 -1
- package/dist/program.d.ts +60 -1
- package/dist/program.d.ts.map +1 -1
- package/dist/program.js +92 -31
- package/dist/program.js.map +1 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +491 -134
- package/dist/runtime.js.map +1 -1
- package/dist/select.d.ts +16 -0
- package/dist/select.d.ts.map +1 -0
- package/dist/select.js +100 -0
- package/dist/select.js.map +1 -0
- package/dist/shared-pipes.d.ts +17 -6
- package/dist/shared-pipes.d.ts.map +1 -1
- package/dist/shared-pipes.js +94 -70
- package/dist/shared-pipes.js.map +1 -1
- package/dist/signals.d.ts +48 -0
- package/dist/signals.d.ts.map +1 -0
- package/dist/signals.js +232 -0
- package/dist/signals.js.map +1 -0
- package/dist/snapshot-filesystem.d.ts +128 -2
- package/dist/snapshot-filesystem.d.ts.map +1 -1
- package/dist/snapshot-filesystem.js +481 -53
- package/dist/snapshot-filesystem.js.map +1 -1
- package/dist/spawn-policy.d.ts +5 -0
- package/dist/spawn-policy.d.ts.map +1 -0
- package/dist/spawn-policy.js +29 -0
- package/dist/spawn-policy.js.map +1 -0
- package/dist/stdin.d.ts +2 -0
- package/dist/stdin.d.ts.map +1 -1
- package/dist/stdin.js +4 -0
- package/dist/stdin.js.map +1 -1
- package/dist/symlink.d.ts +8 -0
- package/dist/symlink.d.ts.map +1 -1
- package/dist/symlink.js +13 -0
- package/dist/symlink.js.map +1 -1
- package/dist/system.d.ts +152 -1
- package/dist/system.d.ts.map +1 -1
- package/dist/system.js +226 -7
- package/dist/system.js.map +1 -1
- package/dist/thread-coordinator-worker.d.ts +1 -0
- package/dist/thread-coordinator-worker.d.ts.map +1 -1
- package/dist/thread-coordinator-worker.js +1 -0
- package/dist/thread-coordinator-worker.js.map +1 -1
- package/dist/thread-worker.js +241 -122
- package/dist/thread-worker.js.map +1 -1
- package/dist/threads.d.ts +12 -2
- package/dist/threads.d.ts.map +1 -1
- package/dist/threads.js +30 -10
- package/dist/threads.js.map +1 -1
- package/dist/types.d.ts +39 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/websocket-peer.d.ts.map +1 -1
- package/dist/websocket-peer.js +4 -14
- package/dist/websocket-peer.js.map +1 -1
- package/dist/worker-program-protocol.d.ts +70 -3
- package/dist/worker-program-protocol.d.ts.map +1 -1
- package/dist/worker-program-worker.js +115 -23
- package/dist/worker-program-worker.js.map +1 -1
- package/dist/worker-program.d.ts +25 -1
- package/dist/worker-program.d.ts.map +1 -1
- package/dist/worker-program.js +470 -61
- package/dist/worker-program.js.map +1 -1
- package/dist/worker-standby.d.ts +3 -0
- package/dist/worker-standby.d.ts.map +1 -0
- package/dist/worker-standby.js +17 -0
- package/dist/worker-standby.js.map +1 -0
- package/package.json +20 -6
package/dist/filesystem.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { WaliProcessDescriptorTable } from "./descriptors.js";
|
|
2
|
+
import { type BrowserProcessFilesystemPolicy } from "@volter/browser-runtime/browser-process-filesystem-policy.js";
|
|
1
3
|
import type { WaliFileSystem, WaliImportHandler, WaliStdin } from "./types.js";
|
|
2
4
|
import type { BrowserProcessPipeHandle } from "./process-bridge.js";
|
|
3
5
|
import type { BrowserFileLockRequest, BrowserFileLockResponse } from "@volter/browser-runtime/browser-file-locks.js";
|
|
4
6
|
export interface WaliSpecialDescriptor {
|
|
7
|
+
stdio?: 0 | 1 | 2;
|
|
5
8
|
read?(length: number): Uint8Array | bigint;
|
|
6
9
|
write?(value: Uint8Array): bigint;
|
|
7
10
|
poll?(events: number): number;
|
|
@@ -11,7 +14,10 @@ export interface WaliSpecialDescriptor {
|
|
|
11
14
|
browserProcessPipe?: BrowserProcessPipeHandle;
|
|
12
15
|
}
|
|
13
16
|
export interface WaliFileDescriptorOptions {
|
|
17
|
+
descriptorState?: import("./shared-pipes.js").WaliSharedPipeState;
|
|
18
|
+
descriptors?: WaliProcessDescriptorTable;
|
|
14
19
|
filesystem?: WaliFileSystem;
|
|
20
|
+
filesystemPolicy?: BrowserProcessFilesystemPolicy;
|
|
15
21
|
cwd: string;
|
|
16
22
|
stdin: Uint8Array | WaliStdin;
|
|
17
23
|
executablePath?: string;
|
|
@@ -30,14 +36,16 @@ export interface WaliFileDescriptorOptions {
|
|
|
30
36
|
export declare function createStdioAliasRegistry(): Int32Array;
|
|
31
37
|
export declare class WaliFileDescriptorTable {
|
|
32
38
|
private readonly options;
|
|
39
|
+
readonly descriptors: WaliProcessDescriptorTable;
|
|
33
40
|
private readonly files;
|
|
34
41
|
private readonly specialDescriptors;
|
|
35
42
|
private readonly stdioAliases;
|
|
36
43
|
private readonly descriptorStatusFlags;
|
|
37
44
|
private readonly stdin;
|
|
38
|
-
private nextFd;
|
|
39
45
|
private nextDescription;
|
|
40
46
|
private cwd;
|
|
47
|
+
private readonly guestFilesystem;
|
|
48
|
+
private readonly assertWritable;
|
|
41
49
|
constructor(options: WaliFileDescriptorOptions);
|
|
42
50
|
imports(): Record<string, WaliImportHandler>;
|
|
43
51
|
writeEnvironmentFile(env: Readonly<Record<string, string>>, pid: number): string | undefined;
|
|
@@ -47,6 +55,8 @@ export declare class WaliFileDescriptorTable {
|
|
|
47
55
|
writeForMapping(path: string, offset: number, value: Uint8Array): boolean;
|
|
48
56
|
allocateSpecial(descriptor: WaliSpecialDescriptor, minimum?: number): number;
|
|
49
57
|
exportBrowserProcessPipe(fd: number): BrowserProcessPipeHandle | undefined;
|
|
58
|
+
copyForFork(memory: () => WebAssembly.Memory): WaliFileDescriptorTable;
|
|
59
|
+
copyForExec(memory: () => WebAssembly.Memory, stdio?: number[]): WaliFileDescriptorTable;
|
|
50
60
|
currentWorkingDirectory(): string;
|
|
51
61
|
ioctl(fd: number, request: number): bigint | undefined;
|
|
52
62
|
pollDescriptor(fd: number, events: number): number;
|
|
@@ -61,6 +71,22 @@ export declare class WaliFileDescriptorTable {
|
|
|
61
71
|
private open;
|
|
62
72
|
private openAt;
|
|
63
73
|
private openResolved;
|
|
74
|
+
/**
|
|
75
|
+
* THE LOWEST DESCRIPTOR NOT CURRENTLY OPEN, which is what POSIX says `open`
|
|
76
|
+
* returns and `F_DUPFD` returns at or above its argument.
|
|
77
|
+
*
|
|
78
|
+
* This used to be a MONOTONIC CURSOR that never rewound, so a closed
|
|
79
|
+
* descriptor's NUMBER was gone for the life of the process and the table's
|
|
80
|
+
* maximum bounded a program's LIFETIME opens instead of its concurrent ones.
|
|
81
|
+
* MEASURED 2026-09-19 in Blender's WALI session, `descriptorMaximum: 9_999`:
|
|
82
|
+
* `os.stat` in a loop survived 40,000, `open(...).close()` in a loop raised
|
|
83
|
+
* `[Errno 24] No file descriptors available` after 9,176 with at most one
|
|
84
|
+
* file open at a time. The session's channel loop then went DEAF -- its
|
|
85
|
+
* `os.listdir` failed EMFILE forever -- and a call sat unanswered for 1,008
|
|
86
|
+
* seconds with the program alive and no error anywhere.
|
|
87
|
+
*
|
|
88
|
+
* Scanning costs one probe per open descriptor, and a process has a handful.
|
|
89
|
+
*/
|
|
64
90
|
private allocateFd;
|
|
65
91
|
private close;
|
|
66
92
|
private read;
|
|
@@ -102,6 +128,9 @@ export declare class WaliFileDescriptorTable {
|
|
|
102
128
|
private readRecordLock;
|
|
103
129
|
private writeRecordLock;
|
|
104
130
|
private flock;
|
|
131
|
+
private readonly posixLockedPaths;
|
|
132
|
+
private readonly lockingOwners;
|
|
133
|
+
private heldLock;
|
|
105
134
|
private lockOwner;
|
|
106
135
|
private releasePosixLocks;
|
|
107
136
|
private releaseLockOwner;
|
package/dist/filesystem.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filesystem.d.ts","sourceRoot":"","sources":["../src/filesystem.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,cAAc,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE7F,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAEpE,OAAO,KAAK,EAIV,sBAAsB,EACtB,uBAAuB,EACxB,MAAM,+CAA+C,CAAC;
|
|
1
|
+
{"version":3,"file":"filesystem.d.ts","sourceRoot":"","sources":["../src/filesystem.ts"],"names":[],"mappings":"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"}
|
package/dist/filesystem.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { WaliDescriptionRegistry, WaliProcessDescriptorTable } from "./descriptors.js";
|
|
2
|
+
import { createProcessFilesystemMutationGuard } from "@volter/browser-runtime/browser-process-filesystem-policy.js";
|
|
1
3
|
import { BufferedWaliStdin } from "./stdin.js";
|
|
2
4
|
import { readWaliSymlink, resolveWaliSymlinkPath, writeWaliSymlink } from "./symlink.js";
|
|
5
|
+
let reportedDroppedEnvironment = false;
|
|
3
6
|
const AT_FDCWD = -100;
|
|
4
7
|
const O_ACCMODE = 3;
|
|
5
8
|
const O_WRONLY = 1;
|
|
@@ -43,18 +46,56 @@ export function createStdioAliasRegistry() {
|
|
|
43
46
|
}
|
|
44
47
|
export class WaliFileDescriptorTable {
|
|
45
48
|
options;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
+
descriptors;
|
|
50
|
+
files;
|
|
51
|
+
specialDescriptors;
|
|
52
|
+
stdioAliases;
|
|
49
53
|
descriptorStatusFlags = new Map();
|
|
50
54
|
stdin;
|
|
51
|
-
nextFd = 3;
|
|
52
55
|
nextDescription = 1;
|
|
53
56
|
cwd;
|
|
57
|
+
guestFilesystem;
|
|
58
|
+
assertWritable;
|
|
54
59
|
constructor(options) {
|
|
55
60
|
this.options = options;
|
|
61
|
+
this.descriptors = options.descriptors ?? new WaliProcessDescriptorTable(new WaliDescriptionRegistry());
|
|
62
|
+
this.files = this.descriptors.view("file");
|
|
63
|
+
this.specialDescriptors = this.descriptors.view("special", (value) => value.close?.());
|
|
64
|
+
this.stdioAliases = this.descriptors.view("stdio");
|
|
56
65
|
this.cwd = normalizePath("/", options.cwd);
|
|
66
|
+
const fs = options.filesystem;
|
|
67
|
+
this.assertWritable = options.filesystemPolicy ? createProcessFilesystemMutationGuard({
|
|
68
|
+
lstatSync: (path) => {
|
|
69
|
+
if (!fs)
|
|
70
|
+
throw errno("ENOSYS");
|
|
71
|
+
const stat = fs.lstatSync?.(path) ?? fs.statSync(path);
|
|
72
|
+
return { isSymbolicLink: () => stat.isSymbolicLink?.() === true || readWaliSymlink(fs, path) !== undefined };
|
|
73
|
+
},
|
|
74
|
+
}, options.filesystemPolicy) : (path) => path;
|
|
75
|
+
// Only guest operations use this view. Host imports and environment setup
|
|
76
|
+
// still own the raw tree; the guest cannot call snapshot replacement.
|
|
77
|
+
this.guestFilesystem = fs && options.filesystemPolicy ? {
|
|
78
|
+
existsSync: fs.existsSync.bind(fs), statSync: fs.statSync.bind(fs),
|
|
79
|
+
...(fs.lstatSync ? { lstatSync: fs.lstatSync.bind(fs) } : {}),
|
|
80
|
+
readFileSync: fs.readFileSync.bind(fs), readFile: fs.readFile.bind(fs),
|
|
81
|
+
readdirSync: fs.readdirSync.bind(fs),
|
|
82
|
+
writeFileSync: (path, bytes) => fs.writeFileSync(this.assertWritable(path), bytes),
|
|
83
|
+
mkdirSync: (path, settings) => fs.mkdirSync(this.assertWritable(path), settings),
|
|
84
|
+
unlinkSync: (path) => fs.unlinkSync(this.assertWritable(path)),
|
|
85
|
+
rmdirSync: (path) => fs.rmdirSync(this.assertWritable(path)),
|
|
86
|
+
renameSync: (from, to) => fs.renameSync(this.assertWritable(from), this.assertWritable(to)),
|
|
87
|
+
...(fs.chmodSync ? { chmodSync: (path, mode) => fs.chmodSync(this.assertWritable(path), mode) } : {}),
|
|
88
|
+
...(fs.utimesSync ? { utimesSync: (path, atime, mtime) => fs.utimesSync(this.assertWritable(path), atime, mtime) } : {}),
|
|
89
|
+
} : fs;
|
|
57
90
|
this.stdin = "read" in options.stdin ? options.stdin : new BufferedWaliStdin(options.stdin);
|
|
91
|
+
if (!options.descriptors) {
|
|
92
|
+
for (const fd of [0, 1, 2])
|
|
93
|
+
this.specialDescriptors.set(fd, { stdio: fd });
|
|
94
|
+
}
|
|
95
|
+
else if (this.descriptors.entries.length === 0 && !options.descriptorState) {
|
|
96
|
+
for (const fd of [0, 1, 2])
|
|
97
|
+
this.specialDescriptors.set(fd, { stdio: fd });
|
|
98
|
+
}
|
|
58
99
|
}
|
|
59
100
|
imports() {
|
|
60
101
|
return {
|
|
@@ -115,17 +156,33 @@ export class WaliFileDescriptorTable {
|
|
|
115
156
|
writeEnvironmentFile(env, pid) {
|
|
116
157
|
if (!this.options.filesystem || Object.keys(env).length === 0)
|
|
117
158
|
return;
|
|
118
|
-
|
|
159
|
+
// PIDs are local to each WALI worker; several native programs can have
|
|
160
|
+
// PID 2 while sharing the same host filesystem.
|
|
161
|
+
const path = `/tmp/.browser-runtime-wali-env-${pid}-${crypto.randomUUID()}`;
|
|
119
162
|
try {
|
|
120
163
|
this.options.filesystem.mkdirSync("/tmp", { recursive: true });
|
|
121
164
|
}
|
|
122
165
|
catch { /* already present */ }
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
166
|
+
// The env file is one `name=value` per line, so it cannot carry a value
|
|
167
|
+
// with a line break or a name that is not an identifier. A shell's own
|
|
168
|
+
// parameters (`?`, a newline-valued IFS) reach here through every lane's
|
|
169
|
+
// spawn; refusing the whole spawn for them is what killed a script's
|
|
170
|
+
// `spawn("ffmpeg")`. They are dropped and named once.
|
|
171
|
+
const dropped = [];
|
|
172
|
+
const lines = [];
|
|
173
|
+
for (const [name, value] of Object.entries(env)) {
|
|
174
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(name) || /[\u0000\r\n]/u.test(value)) {
|
|
175
|
+
dropped.push(name);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
lines.push(`${name}=${value}`);
|
|
179
|
+
}
|
|
180
|
+
if (dropped.length > 0 && !reportedDroppedEnvironment) {
|
|
181
|
+
reportedDroppedEnvironment = true;
|
|
182
|
+
console.warn(`[WALI] environment entries a process cannot receive were dropped: ${dropped.join(", ")}`);
|
|
183
|
+
}
|
|
128
184
|
this.options.filesystem.writeFileSync(path, `${lines.join("\n")}\n`);
|
|
185
|
+
this.options.filesystem.chmodSync?.(path, 0o600);
|
|
129
186
|
return path;
|
|
130
187
|
}
|
|
131
188
|
copyCString(pointer, value, limit) {
|
|
@@ -171,6 +228,21 @@ export class WaliFileDescriptorTable {
|
|
|
171
228
|
exportBrowserProcessPipe(fd) {
|
|
172
229
|
return this.specialDescriptors.get(fd)?.browserProcessPipe;
|
|
173
230
|
}
|
|
231
|
+
copyForFork(memory) {
|
|
232
|
+
return new WaliFileDescriptorTable({ ...this.options, cwd: this.cwd, memory, descriptors: this.descriptors.copy() });
|
|
233
|
+
}
|
|
234
|
+
copyForExec(memory, stdio) {
|
|
235
|
+
const descriptors = this.descriptors.copy();
|
|
236
|
+
if (stdio) {
|
|
237
|
+
const streams = stdio.map(fd => this.descriptors.get(fd));
|
|
238
|
+
streams.forEach((description, fd) => {
|
|
239
|
+
if (description && stdio[fd] !== fd)
|
|
240
|
+
descriptors.set(fd, description);
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
descriptors.exec();
|
|
244
|
+
return new WaliFileDescriptorTable({ ...this.options, cwd: this.cwd, memory, descriptors });
|
|
245
|
+
}
|
|
174
246
|
currentWorkingDirectory() { return this.cwd; }
|
|
175
247
|
ioctl(fd, request) {
|
|
176
248
|
if (!this.hasFd(fd))
|
|
@@ -185,7 +257,7 @@ export class WaliFileDescriptorTable {
|
|
|
185
257
|
if (fd < 0 || !this.hasFd(fd))
|
|
186
258
|
return 0x20;
|
|
187
259
|
const special = this.specialDescriptors.get(fd);
|
|
188
|
-
if (special)
|
|
260
|
+
if (special && special.stdio === undefined)
|
|
189
261
|
return special.poll?.(events) ?? 0;
|
|
190
262
|
const stdio = this.stdioFd(fd);
|
|
191
263
|
let returned = 0;
|
|
@@ -196,9 +268,9 @@ export class WaliFileDescriptorTable {
|
|
|
196
268
|
return returned;
|
|
197
269
|
}
|
|
198
270
|
fs() {
|
|
199
|
-
if (!this.
|
|
271
|
+
if (!this.guestFilesystem)
|
|
200
272
|
throw errno("ENOSYS");
|
|
201
|
-
return this.
|
|
273
|
+
return this.guestFilesystem;
|
|
202
274
|
}
|
|
203
275
|
memoryBytes() { return new Uint8Array(this.options.memory().buffer); }
|
|
204
276
|
memoryView() { return new DataView(this.options.memory().buffer); }
|
|
@@ -276,6 +348,8 @@ export class WaliFileDescriptorTable {
|
|
|
276
348
|
}
|
|
277
349
|
const fs = this.fs();
|
|
278
350
|
const exists = fs.existsSync(path);
|
|
351
|
+
if ((flags & O_ACCMODE) !== 0 || (!exists && (flags & O_CREAT)))
|
|
352
|
+
this.assertWritable(path);
|
|
279
353
|
if (!exists && !(flags & O_CREAT))
|
|
280
354
|
throw errno("ENOENT");
|
|
281
355
|
if (exists && (flags & O_CREAT) && (flags & O_EXCL))
|
|
@@ -301,20 +375,37 @@ export class WaliFileDescriptorTable {
|
|
|
301
375
|
directory: stat.isDirectory(),
|
|
302
376
|
description: this.nextDescription++,
|
|
303
377
|
});
|
|
378
|
+
this.descriptors.entries[fd].flags = flags & 0x80000 ? 1 : 0;
|
|
304
379
|
return fd;
|
|
305
380
|
}
|
|
381
|
+
/**
|
|
382
|
+
* THE LOWEST DESCRIPTOR NOT CURRENTLY OPEN, which is what POSIX says `open`
|
|
383
|
+
* returns and `F_DUPFD` returns at or above its argument.
|
|
384
|
+
*
|
|
385
|
+
* This used to be a MONOTONIC CURSOR that never rewound, so a closed
|
|
386
|
+
* descriptor's NUMBER was gone for the life of the process and the table's
|
|
387
|
+
* maximum bounded a program's LIFETIME opens instead of its concurrent ones.
|
|
388
|
+
* MEASURED 2026-09-19 in Blender's WALI session, `descriptorMaximum: 9_999`:
|
|
389
|
+
* `os.stat` in a loop survived 40,000, `open(...).close()` in a loop raised
|
|
390
|
+
* `[Errno 24] No file descriptors available` after 9,176 with at most one
|
|
391
|
+
* file open at a time. The session's channel loop then went DEAF -- its
|
|
392
|
+
* `os.listdir` failed EMFILE forever -- and a call sat unanswered for 1,008
|
|
393
|
+
* seconds with the program alive and no error anywhere.
|
|
394
|
+
*
|
|
395
|
+
* Scanning costs one probe per open descriptor, and a process has a handful.
|
|
396
|
+
*/
|
|
306
397
|
allocateFd(minimum = 3) {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
this.
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
398
|
+
const maximum = this.options.descriptorMaximum ?? 65_535;
|
|
399
|
+
for (let fd = Math.max(minimum, 0); fd <= maximum; fd += 1) {
|
|
400
|
+
// `stdioFd` covers 0-2, this table's own aliases and the shared registry.
|
|
401
|
+
if (this.descriptors.get(fd) || this.files.has(fd) || this.specialDescriptors.has(fd)
|
|
402
|
+
|| this.stdioFd(fd) !== undefined)
|
|
403
|
+
continue;
|
|
404
|
+
return fd;
|
|
405
|
+
}
|
|
406
|
+
throw errno("EMFILE");
|
|
314
407
|
}
|
|
315
408
|
close(fd) {
|
|
316
|
-
if (fd >= 0 && fd <= 2)
|
|
317
|
-
return 0n;
|
|
318
409
|
if (this.stdioAliases.delete(fd)) {
|
|
319
410
|
this.descriptorStatusFlags.delete(fd);
|
|
320
411
|
this.setSharedStdioAlias(fd, undefined);
|
|
@@ -322,7 +413,6 @@ export class WaliFileDescriptorTable {
|
|
|
322
413
|
}
|
|
323
414
|
const special = this.specialDescriptors.get(fd);
|
|
324
415
|
if (special) {
|
|
325
|
-
special.close?.();
|
|
326
416
|
this.specialDescriptors.delete(fd);
|
|
327
417
|
return 0n;
|
|
328
418
|
}
|
|
@@ -366,8 +456,10 @@ export class WaliFileDescriptorTable {
|
|
|
366
456
|
throw errno("EISDIR");
|
|
367
457
|
if ((file.flags & O_ACCMODE) === O_WRONLY)
|
|
368
458
|
throw errno("EBADF");
|
|
369
|
-
const
|
|
370
|
-
const chunk =
|
|
459
|
+
const fs = this.fs();
|
|
460
|
+
const chunk = fs.readRangeSync
|
|
461
|
+
? fs.readRangeSync(file.path, file.position, length)
|
|
462
|
+
: fs.readFileSync(file.path).subarray(file.position, file.position + length);
|
|
371
463
|
this.copyIn(pointer, chunk);
|
|
372
464
|
file.position += chunk.byteLength;
|
|
373
465
|
return BigInt(chunk.byteLength);
|
|
@@ -392,14 +484,22 @@ export class WaliFileDescriptorTable {
|
|
|
392
484
|
throw errno("EISDIR");
|
|
393
485
|
if ((file.flags & O_ACCMODE) === 0)
|
|
394
486
|
throw errno("EBADF");
|
|
395
|
-
const
|
|
487
|
+
const fs = this.fs();
|
|
488
|
+
if (fs.writeRangeSync) {
|
|
489
|
+
if (file.flags & O_APPEND)
|
|
490
|
+
file.position = fs.statSync(file.path).size;
|
|
491
|
+
fs.writeRangeSync(file.path, file.position, chunk);
|
|
492
|
+
file.position += chunk.byteLength;
|
|
493
|
+
return BigInt(chunk.byteLength);
|
|
494
|
+
}
|
|
495
|
+
const current = fs.readFileSync(file.path);
|
|
396
496
|
if (file.flags & O_APPEND)
|
|
397
497
|
file.position = current.byteLength;
|
|
398
498
|
const size = Math.max(current.byteLength, file.position + chunk.byteLength);
|
|
399
499
|
const next = new Uint8Array(size);
|
|
400
500
|
next.set(current);
|
|
401
501
|
next.set(chunk, file.position);
|
|
402
|
-
|
|
502
|
+
fs.writeFileSync(file.path, next);
|
|
403
503
|
file.position += chunk.byteLength;
|
|
404
504
|
return BigInt(chunk.byteLength);
|
|
405
505
|
}
|
|
@@ -843,35 +943,47 @@ export class WaliFileDescriptorTable {
|
|
|
843
943
|
return -9n;
|
|
844
944
|
if (next === fd)
|
|
845
945
|
return BigInt(next);
|
|
846
|
-
this.
|
|
946
|
+
this.descriptors.set(next, this.descriptors.get(fd));
|
|
847
947
|
this.descriptorStatusFlags.set(next, this.statusFlags(fd));
|
|
848
948
|
this.setSharedStdioAlias(next, stdio);
|
|
849
949
|
return BigInt(next);
|
|
850
950
|
}
|
|
851
|
-
|
|
852
|
-
if (!file)
|
|
951
|
+
if (!this.descriptors.get(fd))
|
|
853
952
|
return -9n;
|
|
854
953
|
const next = target ?? this.allocateFd(minimum);
|
|
855
954
|
if (next < 0 || next > maximum)
|
|
856
955
|
return -9n;
|
|
857
|
-
|
|
956
|
+
if (next === fd)
|
|
957
|
+
return BigInt(next);
|
|
958
|
+
this.close(next);
|
|
959
|
+
this.descriptors.set(next, this.descriptors.get(fd));
|
|
858
960
|
return BigInt(next);
|
|
859
961
|
}
|
|
860
962
|
fcntl(fd, command, argument) {
|
|
861
963
|
if (!this.hasFd(fd))
|
|
862
964
|
return -9n;
|
|
863
965
|
const special = this.specialDescriptors.get(fd);
|
|
864
|
-
if (special) {
|
|
966
|
+
if (special && special.stdio === undefined && command !== 1 && command !== 2) {
|
|
865
967
|
if (command === 0 || command === 1030) {
|
|
866
968
|
return special.duplicate?.(Math.max(argument, 3), command === 1030) ?? -22n;
|
|
867
969
|
}
|
|
868
970
|
return special.fcntl?.(command, argument) ?? -22n;
|
|
869
971
|
}
|
|
870
972
|
const file = this.files.get(fd);
|
|
871
|
-
if (command === 0 || command === 1030)
|
|
872
|
-
|
|
873
|
-
|
|
973
|
+
if (command === 0 || command === 1030) {
|
|
974
|
+
const result = this.dup(fd, undefined, Math.max(argument, 3));
|
|
975
|
+
if (result >= 0n && command === 1030)
|
|
976
|
+
this.descriptors.entries[Number(result)].flags = 1;
|
|
977
|
+
return result;
|
|
978
|
+
}
|
|
979
|
+
if (command === 1)
|
|
980
|
+
return BigInt(this.descriptors.entries[fd]?.flags ?? 0);
|
|
981
|
+
if (command === 2) {
|
|
982
|
+
const entry = this.descriptors.entries[fd];
|
|
983
|
+
if (entry)
|
|
984
|
+
entry.flags = argument & 1;
|
|
874
985
|
return 0n;
|
|
986
|
+
}
|
|
875
987
|
if (command === 3)
|
|
876
988
|
return BigInt(this.statusFlags(fd));
|
|
877
989
|
if (command === 4) {
|
|
@@ -920,10 +1032,13 @@ export class WaliFileDescriptorTable {
|
|
|
920
1032
|
return 0n;
|
|
921
1033
|
}
|
|
922
1034
|
const blocking = command === F_SETLKW || command === F_OFD_SETLKW;
|
|
923
|
-
|
|
1035
|
+
const result = client.request({
|
|
924
1036
|
operation: "set", owner, namespace, path: file.path, range: requested.range,
|
|
925
1037
|
mode: requested.mode, pid: this.options.pid ?? 1, blocking,
|
|
926
|
-
}).result
|
|
1038
|
+
}).result;
|
|
1039
|
+
if (result >= 0)
|
|
1040
|
+
this.heldLock(namespace, owner, file.path);
|
|
1041
|
+
return BigInt(result);
|
|
927
1042
|
}
|
|
928
1043
|
catch (error) {
|
|
929
1044
|
return negativeErrno(error);
|
|
@@ -980,10 +1095,24 @@ export class WaliFileDescriptorTable {
|
|
|
980
1095
|
const mode = operation & LOCK_EX ? "write" : operation & LOCK_SH ? "read" : undefined;
|
|
981
1096
|
if (!mode || operation & ~(LOCK_SH | LOCK_EX | LOCK_NB | LOCK_UN))
|
|
982
1097
|
return -22n;
|
|
983
|
-
|
|
1098
|
+
const result = client.request({
|
|
984
1099
|
operation: "set", owner, namespace: "flock", path: file.path, range: { start: 0 },
|
|
985
1100
|
mode, pid: this.options.pid ?? 1, blocking: !(operation & LOCK_NB),
|
|
986
|
-
}).result
|
|
1101
|
+
}).result;
|
|
1102
|
+
if (result >= 0)
|
|
1103
|
+
this.heldLock("flock", owner, file.path);
|
|
1104
|
+
return BigInt(result);
|
|
1105
|
+
}
|
|
1106
|
+
// What this process has locked. A close releases only these: each release
|
|
1107
|
+
// is a synchronous request to the page's lock table, and a close of a file
|
|
1108
|
+
// never locked (git closes hundreds) paid three of them.
|
|
1109
|
+
posixLockedPaths = new Set();
|
|
1110
|
+
lockingOwners = new Set();
|
|
1111
|
+
heldLock(namespace, owner, path) {
|
|
1112
|
+
if (namespace === "posix")
|
|
1113
|
+
this.posixLockedPaths.add(path);
|
|
1114
|
+
else
|
|
1115
|
+
this.lockingOwners.add(owner);
|
|
987
1116
|
}
|
|
988
1117
|
lockOwner(namespace, file) {
|
|
989
1118
|
const base = this.options.lockOwner;
|
|
@@ -993,14 +1122,14 @@ export class WaliFileDescriptorTable {
|
|
|
993
1122
|
}
|
|
994
1123
|
releasePosixLocks(path) {
|
|
995
1124
|
const owner = this.options.lockOwner ? `${this.options.lockOwner}:posix` : undefined;
|
|
996
|
-
if (!owner || !this.options.fileLocks)
|
|
1125
|
+
if (!owner || !this.options.fileLocks || !this.posixLockedPaths.delete(path))
|
|
997
1126
|
return;
|
|
998
1127
|
this.options.fileLocks.request({
|
|
999
1128
|
operation: "unlock", owner, namespace: "posix", path, range: { start: 0 },
|
|
1000
1129
|
});
|
|
1001
1130
|
}
|
|
1002
1131
|
releaseLockOwner(owner) {
|
|
1003
|
-
if (owner && this.options.fileLocks)
|
|
1132
|
+
if (owner && this.options.fileLocks && this.lockingOwners.delete(owner))
|
|
1004
1133
|
this.options.fileLocks.request({ operation: "release-owner", owner });
|
|
1005
1134
|
}
|
|
1006
1135
|
poll(pointer, count, timeout) {
|
|
@@ -1029,17 +1158,18 @@ export class WaliFileDescriptorTable {
|
|
|
1029
1158
|
return BigInt(scan());
|
|
1030
1159
|
}
|
|
1031
1160
|
hasFd(fd) {
|
|
1032
|
-
return
|
|
1033
|
-
|| this.files.has(fd) || this.specialDescriptors.has(fd);
|
|
1161
|
+
return this.descriptors.get(fd) !== undefined;
|
|
1034
1162
|
}
|
|
1035
1163
|
stdioFd(fd) {
|
|
1036
|
-
if (
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1164
|
+
if (this.options.descriptorState) {
|
|
1165
|
+
const state = this.options.descriptorState;
|
|
1166
|
+
if (fd < 0 || fd >= 65_536)
|
|
1167
|
+
return;
|
|
1168
|
+
const id = Atomics.load(new Int32Array(state.table), fd);
|
|
1169
|
+
const stream = Atomics.load(new Int32Array(state.descriptions), id * 3 + 2) - 1;
|
|
1170
|
+
return stream >= 0 && stream <= 2 ? stream : undefined;
|
|
1171
|
+
}
|
|
1172
|
+
return this.specialDescriptors.get(fd)?.stdio;
|
|
1043
1173
|
}
|
|
1044
1174
|
setSharedStdioAlias(fd, target) {
|
|
1045
1175
|
if (fd < 0 || fd >= (this.options.stdioAliasRegistry?.length ?? 0))
|