@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
|
@@ -8,12 +8,35 @@ import { encodeBase64, decodeBase64 } from "@volter/browser-runtime/browser-base
|
|
|
8
8
|
*/
|
|
9
9
|
class NodeMap extends Map {
|
|
10
10
|
names = new Map();
|
|
11
|
+
paths = new Map();
|
|
12
|
+
pathsFor(node) { return this.paths.get(node) ?? new Set(); }
|
|
11
13
|
static rooted() {
|
|
12
14
|
const nodes = new NodeMap();
|
|
13
15
|
nodes.set("/", rootNode());
|
|
14
16
|
return nodes;
|
|
15
17
|
}
|
|
16
18
|
set(path, node) {
|
|
19
|
+
const previous = super.get(path);
|
|
20
|
+
if (previous && previous !== node && previous.ino === node.ino) {
|
|
21
|
+
// A write replaces the content version, not the inode's names. Every
|
|
22
|
+
// alias observes the new version without a scan of the whole tree.
|
|
23
|
+
replaceNode(previous, node);
|
|
24
|
+
node = previous;
|
|
25
|
+
}
|
|
26
|
+
else if (previous !== node) {
|
|
27
|
+
if (previous) {
|
|
28
|
+
const aliases = this.paths.get(previous);
|
|
29
|
+
aliases.delete(path);
|
|
30
|
+
if (!aliases.size)
|
|
31
|
+
this.paths.delete(previous);
|
|
32
|
+
}
|
|
33
|
+
let aliases = this.paths.get(node);
|
|
34
|
+
if (!aliases) {
|
|
35
|
+
aliases = new Set();
|
|
36
|
+
this.paths.set(node, aliases);
|
|
37
|
+
}
|
|
38
|
+
aliases.add(path);
|
|
39
|
+
}
|
|
17
40
|
if (!this.has(path) && path !== "/") {
|
|
18
41
|
const directory = parent(path);
|
|
19
42
|
let names = this.names.get(directory);
|
|
@@ -26,8 +49,13 @@ class NodeMap extends Map {
|
|
|
26
49
|
return super.set(path, node);
|
|
27
50
|
}
|
|
28
51
|
delete(path) {
|
|
52
|
+
const node = super.get(path);
|
|
29
53
|
if (!super.delete(path))
|
|
30
54
|
return false;
|
|
55
|
+
const aliases = this.paths.get(node);
|
|
56
|
+
aliases.delete(path);
|
|
57
|
+
if (!aliases.size)
|
|
58
|
+
this.paths.delete(node);
|
|
31
59
|
const directory = parent(path);
|
|
32
60
|
const names = this.names.get(directory);
|
|
33
61
|
names?.delete(path.slice(directory === "/" ? 1 : directory.length + 1));
|
|
@@ -38,6 +66,7 @@ class NodeMap extends Map {
|
|
|
38
66
|
clear() {
|
|
39
67
|
super.clear();
|
|
40
68
|
this.names.clear();
|
|
69
|
+
this.paths.clear();
|
|
41
70
|
}
|
|
42
71
|
/** The names directly under a directory. */
|
|
43
72
|
namesIn(directory) {
|
|
@@ -62,6 +91,10 @@ class NodeMap extends Map {
|
|
|
62
91
|
/** Small transferable filesystem used inside WALI process workers. */
|
|
63
92
|
export class SnapshotWaliFileSystem {
|
|
64
93
|
nodes = NodeMap.rooted();
|
|
94
|
+
// WasmFS's OpenFileState holds a File independently of the directory.
|
|
95
|
+
// Adapted from The Emscripten Authors' file_table.cpp/file.h (MIT/NCSA);
|
|
96
|
+
// our owner keeps the inode and its names once across all process tables.
|
|
97
|
+
opened = new Map();
|
|
65
98
|
/**
|
|
66
99
|
* The project's store, when the tree is over one (ADR-0042): its index
|
|
67
100
|
* answers every path this tree has not itself touched, and a path is made a
|
|
@@ -140,6 +173,7 @@ export class SnapshotWaliFileSystem {
|
|
|
140
173
|
node.atimeMs = node.mtimeMs = entry.mtimeMs;
|
|
141
174
|
}
|
|
142
175
|
}
|
|
176
|
+
filesystem.restoreHardLinks(snapshot);
|
|
143
177
|
filesystem.mutated = false;
|
|
144
178
|
});
|
|
145
179
|
return filesystem;
|
|
@@ -316,7 +350,7 @@ export class SnapshotWaliFileSystem {
|
|
|
316
350
|
const node = this.node(normalized);
|
|
317
351
|
if (!node)
|
|
318
352
|
return undefined;
|
|
319
|
-
const details = { mode: node.mode, mtimeMs: node.mtimeMs };
|
|
353
|
+
const details = { mode: node.mode, mtimeMs: node.mtimeMs, inode: node.ino };
|
|
320
354
|
if (node.type === "directory")
|
|
321
355
|
return { path: normalized, type: "directory", ...details };
|
|
322
356
|
// Raw for the live channel to the page (ADR-0046): a copy, whose buffer the post transfers.
|
|
@@ -358,6 +392,11 @@ export class SnapshotWaliFileSystem {
|
|
|
358
392
|
this.mkdirSync(normalized, { recursive: true });
|
|
359
393
|
}
|
|
360
394
|
else if (entry.content !== undefined) {
|
|
395
|
+
const kept = previous.get(normalized);
|
|
396
|
+
if (kept?.type === "file") {
|
|
397
|
+
this.mkdirSync(parent(normalized), { recursive: true });
|
|
398
|
+
this.nodes.set(normalized, kept);
|
|
399
|
+
}
|
|
361
400
|
this.writeFileSync(normalized, decodeBase64(entry.content));
|
|
362
401
|
// RESTORED, NOT WRITTEN. These bytes came FROM the page; sending
|
|
363
402
|
// them back is the whole cost `dirty` exists to avoid.
|
|
@@ -387,8 +426,30 @@ export class SnapshotWaliFileSystem {
|
|
|
387
426
|
this.markMutation(normalized);
|
|
388
427
|
}
|
|
389
428
|
}
|
|
429
|
+
this.restoreHardLinks(snapshot);
|
|
430
|
+
for (const node of new Set(previous.values()))
|
|
431
|
+
this.releaseUnlinkedNode(node);
|
|
390
432
|
});
|
|
391
433
|
}
|
|
434
|
+
restoreHardLinks(snapshot) {
|
|
435
|
+
const inodes = new Map();
|
|
436
|
+
for (const entry of snapshot.files) {
|
|
437
|
+
if (entry.type !== "file" || !Number.isSafeInteger(entry.inode) || entry.inode < 2)
|
|
438
|
+
continue;
|
|
439
|
+
const path = normalize(entry.path);
|
|
440
|
+
const node = this.node(path);
|
|
441
|
+
if (node?.type !== "file")
|
|
442
|
+
continue;
|
|
443
|
+
const inode = entry.inode;
|
|
444
|
+
const existing = inodes.get(inode);
|
|
445
|
+
if (existing)
|
|
446
|
+
this.nodes.set(path, existing);
|
|
447
|
+
// Snapshot inode numbers describe alias groups, not this owner's
|
|
448
|
+
// allocator. Keep local identities unique, including directories.
|
|
449
|
+
else
|
|
450
|
+
inodes.set(inode, node);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
392
453
|
/**
|
|
393
454
|
* The tree for a peer that holds NOTHING: every file's bytes, except the
|
|
394
455
|
* ones still in the store, which such a peer reads from the store itself.
|
|
@@ -407,7 +468,7 @@ export class SnapshotWaliFileSystem {
|
|
|
407
468
|
? { path, type: "directory", mode: node.mode, mtimeMs: node.mtimeMs }
|
|
408
469
|
: node.load && !node.dirty
|
|
409
470
|
? this.sized(path, node)
|
|
410
|
-
: { path, type: "file", content: encodeBase64(node.content ?? new Uint8Array()), mode: node.mode, mtimeMs: node.mtimeMs }).concat(this.untouchedStoreEntries()) };
|
|
471
|
+
: { path, type: "file", content: encodeBase64(node.content ?? new Uint8Array()), mode: node.mode, mtimeMs: node.mtimeMs, inode: node.ino }).concat(this.untouchedStoreEntries()) };
|
|
411
472
|
}
|
|
412
473
|
/** The store's entries this tree has not made nodes of, as sized entries: their bytes are the store's. */
|
|
413
474
|
untouchedStoreEntries() {
|
|
@@ -445,11 +506,11 @@ export class SnapshotWaliFileSystem {
|
|
|
445
506
|
return { files: [...this.nodes.entries()].map(([path, node]) => node.type === "directory"
|
|
446
507
|
? { path, type: "directory", mode: node.mode, mtimeMs: node.mtimeMs }
|
|
447
508
|
: node.dirty
|
|
448
|
-
? { path, type: "file", content: encodeBase64(node.content ?? new Uint8Array()), mode: node.mode, mtimeMs: node.mtimeMs }
|
|
509
|
+
? { path, type: "file", content: encodeBase64(node.content ?? new Uint8Array()), mode: node.mode, mtimeMs: node.mtimeMs, inode: node.ino }
|
|
449
510
|
: this.sized(path, node)).concat(this.untouchedStoreEntries()) };
|
|
450
511
|
}
|
|
451
512
|
sized(path, node) {
|
|
452
|
-
return { path, type: "file", size: node.content?.byteLength ?? node.size ?? 0, mode: node.mode, mtimeMs: node.mtimeMs };
|
|
513
|
+
return { path, type: "file", size: node.content?.byteLength ?? node.size ?? 0, mode: node.mode, mtimeMs: node.mtimeMs, inode: node.ino };
|
|
453
514
|
}
|
|
454
515
|
existsSync(path) { return this.node(normalize(path)) !== undefined; }
|
|
455
516
|
statSync(path) {
|
|
@@ -457,6 +518,9 @@ export class SnapshotWaliFileSystem {
|
|
|
457
518
|
const node = this.node(normalized);
|
|
458
519
|
if (!node)
|
|
459
520
|
throw new Error(`ENOENT: no such file or directory, stat '${normalized}'`);
|
|
521
|
+
return this.statNode(node);
|
|
522
|
+
}
|
|
523
|
+
statNode(node) {
|
|
460
524
|
const size = node.content?.byteLength ?? node.size ?? 0;
|
|
461
525
|
return {
|
|
462
526
|
isFile: () => node.type === "file",
|
|
@@ -466,7 +530,7 @@ export class SnapshotWaliFileSystem {
|
|
|
466
530
|
mtimeMs: node.mtimeMs,
|
|
467
531
|
atimeMs: node.atimeMs,
|
|
468
532
|
ctimeMs: node.mtimeMs,
|
|
469
|
-
nlink: 1,
|
|
533
|
+
nlink: node.type === "file" ? this.nodes.pathsFor(node).size : 1,
|
|
470
534
|
uid: 1000,
|
|
471
535
|
gid: 1000,
|
|
472
536
|
dev: 1,
|
|
@@ -477,6 +541,60 @@ export class SnapshotWaliFileSystem {
|
|
|
477
541
|
};
|
|
478
542
|
}
|
|
479
543
|
lstatSync(path) { return this.statSync(path); }
|
|
544
|
+
openFileSync(path) {
|
|
545
|
+
const normalized = normalize(path), node = this.node(normalized);
|
|
546
|
+
if (!node)
|
|
547
|
+
throw new Error(`ENOENT: no such file, open '${normalized}'`);
|
|
548
|
+
if (node.type !== "file")
|
|
549
|
+
throw new Error(`EISDIR: illegal operation on a directory, open '${normalized}'`);
|
|
550
|
+
return this.retainNode(node);
|
|
551
|
+
}
|
|
552
|
+
retainNode(node) {
|
|
553
|
+
this.opened.set(node, (this.opened.get(node) ?? 0) + 1);
|
|
554
|
+
let closed = false;
|
|
555
|
+
const check = () => { if (closed)
|
|
556
|
+
throw new Error("EBADF: closed inode handle"); };
|
|
557
|
+
return {
|
|
558
|
+
stat: () => { check(); return this.statNode(node); },
|
|
559
|
+
read: (offset, length) => {
|
|
560
|
+
check();
|
|
561
|
+
return node.range && node.content === undefined ? node.range(offset, length)
|
|
562
|
+
: this.bytesOf(node).subarray(offset, offset + length);
|
|
563
|
+
},
|
|
564
|
+
write: (offset, data) => {
|
|
565
|
+
check();
|
|
566
|
+
this.mutate(() => { replaceNode(node, this.writeNode(node, offset, data)); this.markNodeMutation(node); });
|
|
567
|
+
},
|
|
568
|
+
truncate: (length) => {
|
|
569
|
+
check();
|
|
570
|
+
this.mutate(() => { replaceNode(node, this.truncateNode(node, length)); this.markNodeMutation(node); });
|
|
571
|
+
},
|
|
572
|
+
chmod: (mode) => { check(); this.mutate(() => { node.mode = mode & 0o7777; this.markNodeMutation(node); }); },
|
|
573
|
+
retain: () => { check(); return this.retainNode(node); },
|
|
574
|
+
close: () => {
|
|
575
|
+
if (closed)
|
|
576
|
+
return;
|
|
577
|
+
closed = true;
|
|
578
|
+
const left = this.opened.get(node) - 1;
|
|
579
|
+
if (left)
|
|
580
|
+
this.opened.set(node, left);
|
|
581
|
+
else {
|
|
582
|
+
this.opened.delete(node);
|
|
583
|
+
this.releaseUnlinkedNode(node);
|
|
584
|
+
}
|
|
585
|
+
},
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
releaseUnlinkedNode(node) {
|
|
589
|
+
if (!this.opened.has(node) && !this.nodes.pathsFor(node).size && node.dirty && node.content) {
|
|
590
|
+
this.release(node.content);
|
|
591
|
+
delete node.content;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
markNodeMutation(node) {
|
|
595
|
+
for (const path of this.nodes.pathsFor(node))
|
|
596
|
+
this.markMutation(path);
|
|
597
|
+
}
|
|
480
598
|
async readFile(path) { return this.readFileSync(path); }
|
|
481
599
|
readFileSync(path) {
|
|
482
600
|
const normalized = normalize(path);
|
|
@@ -555,42 +673,45 @@ export class SnapshotWaliFileSystem {
|
|
|
555
673
|
const existing = removed ? this.nodes.get(normalized) : this.node(normalized);
|
|
556
674
|
if (existing?.type === "directory")
|
|
557
675
|
throw new Error(`EISDIR: illegal operation on a directory, write '${normalized}'`);
|
|
558
|
-
|
|
559
|
-
const end = offset + data.byteLength;
|
|
560
|
-
let content;
|
|
561
|
-
if (end <= current.byteLength) {
|
|
562
|
-
// In place only in bytes this tree owns: a loaded file's may be a view of a shared layer.
|
|
563
|
-
content = existing?.dirty ? current : current.slice();
|
|
564
|
-
}
|
|
565
|
-
else if (end <= current.buffer.byteLength - current.byteOffset && existing?.dirty) {
|
|
566
|
-
// Room in a buffer this store already grew for the file (a dirty
|
|
567
|
-
// node's buffer is ours; a loaded or snapshot one may be shared).
|
|
568
|
-
content = new Uint8Array(current.buffer, current.byteOffset, end);
|
|
569
|
-
content.fill(0, current.byteLength, offset);
|
|
570
|
-
}
|
|
571
|
-
else {
|
|
572
|
-
const spare = this.takeSpare(end);
|
|
573
|
-
content = new Uint8Array(spare ?? new ArrayBuffer(Math.max(end, current.byteLength * 2, 4096)), 0, end);
|
|
574
|
-
content.set(current);
|
|
575
|
-
// A spare buffer holds what its last file did; a new one is zero.
|
|
576
|
-
if (spare)
|
|
577
|
-
content.fill(0, current.byteLength, offset);
|
|
578
|
-
if (existing?.dirty)
|
|
579
|
-
this.release(current);
|
|
580
|
-
}
|
|
581
|
-
content.set(data, offset);
|
|
582
|
-
this.nodes.set(normalized, {
|
|
583
|
-
type: "file",
|
|
584
|
-
content,
|
|
585
|
-
dirty: true,
|
|
586
|
-
mode: existing?.type === "file" ? existing.mode : 0o644,
|
|
587
|
-
atimeMs: realtimeMilliseconds(),
|
|
588
|
-
mtimeMs: realtimeMilliseconds(),
|
|
589
|
-
ino: existing?.type === "file" ? existing.ino : this.nextInode++,
|
|
590
|
-
});
|
|
676
|
+
this.nodes.set(normalized, this.writeNode(existing, offset, data));
|
|
591
677
|
this.markMutation(normalized);
|
|
592
678
|
});
|
|
593
679
|
}
|
|
680
|
+
writeNode(existing, offset, data) {
|
|
681
|
+
const current = existing ? this.bytesOf(existing) : new Uint8Array();
|
|
682
|
+
const end = offset + data.byteLength;
|
|
683
|
+
let content;
|
|
684
|
+
if (end <= current.byteLength) {
|
|
685
|
+
// In place only in bytes this tree owns: a loaded file's may be a view of a shared layer.
|
|
686
|
+
content = existing?.dirty ? current : current.slice();
|
|
687
|
+
}
|
|
688
|
+
else if (end <= current.buffer.byteLength - current.byteOffset && existing?.dirty) {
|
|
689
|
+
// Room in a buffer this store already grew for the file (a dirty
|
|
690
|
+
// node's buffer is ours; a loaded or snapshot one may be shared).
|
|
691
|
+
content = new Uint8Array(current.buffer, current.byteOffset, end);
|
|
692
|
+
content.fill(0, current.byteLength, offset);
|
|
693
|
+
}
|
|
694
|
+
else {
|
|
695
|
+
const spare = this.takeSpare(end);
|
|
696
|
+
content = new Uint8Array(spare ?? new ArrayBuffer(Math.max(end, current.byteLength * 2, 4096)), 0, end);
|
|
697
|
+
content.set(current);
|
|
698
|
+
// A spare buffer holds what its last file did; a new one is zero.
|
|
699
|
+
if (spare)
|
|
700
|
+
content.fill(0, current.byteLength, offset);
|
|
701
|
+
if (existing?.dirty)
|
|
702
|
+
this.release(current);
|
|
703
|
+
}
|
|
704
|
+
content.set(data, offset);
|
|
705
|
+
return {
|
|
706
|
+
type: "file",
|
|
707
|
+
content,
|
|
708
|
+
dirty: true,
|
|
709
|
+
mode: existing?.type === "file" ? existing.mode : 0o644,
|
|
710
|
+
atimeMs: realtimeMilliseconds(),
|
|
711
|
+
mtimeMs: realtimeMilliseconds(),
|
|
712
|
+
ino: existing?.type === "file" ? existing.ino : this.nextInode++,
|
|
713
|
+
};
|
|
714
|
+
}
|
|
594
715
|
/**
|
|
595
716
|
* A file cut or extended to `length`, in place where its bytes are this
|
|
596
717
|
* tree's own: a rewrite (`O_TRUNC`, then writes) keeps the buffer the file
|
|
@@ -608,39 +729,42 @@ export class SnapshotWaliFileSystem {
|
|
|
608
729
|
throw new Error(`ENOENT: no such file, truncate '${normalized}'`);
|
|
609
730
|
if (existing.type !== "file")
|
|
610
731
|
throw new Error(`EISDIR: illegal operation on a directory, truncate '${normalized}'`);
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
732
|
+
this.nodes.set(normalized, this.truncateNode(existing, length));
|
|
733
|
+
this.markMutation(normalized);
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
truncateNode(existing, length) {
|
|
737
|
+
let content;
|
|
738
|
+
if (length === 0 && existing.content === undefined) {
|
|
739
|
+
// Emptied before it was ever read: the store's bytes are not needed at all.
|
|
740
|
+
content = new Uint8Array();
|
|
741
|
+
}
|
|
742
|
+
else {
|
|
743
|
+
const current = this.bytesOf(existing);
|
|
744
|
+
const capacity = current.buffer.byteLength - current.byteOffset;
|
|
745
|
+
if (existing.dirty && length <= capacity && (capacity <= SPARE_MINIMUM || length * 4 >= capacity)) {
|
|
746
|
+
content = new Uint8Array(current.buffer, current.byteOffset, length);
|
|
747
|
+
content.fill(0, current.byteLength);
|
|
615
748
|
}
|
|
616
749
|
else {
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
// Cut far below what the buffer holds: the file stops holding it, and
|
|
625
|
-
// it goes where the next file to grow may take it back (a rewrite's
|
|
626
|
-
// own writes, usually), within the spares' bound.
|
|
627
|
-
content = new Uint8Array(length);
|
|
628
|
-
content.set(current.subarray(0, length));
|
|
629
|
-
if (existing.dirty)
|
|
630
|
-
this.release(current);
|
|
631
|
-
}
|
|
750
|
+
// Cut far below what the buffer holds: the file stops holding it, and
|
|
751
|
+
// it goes where the next file to grow may take it back (a rewrite's
|
|
752
|
+
// own writes, usually), within the spares' bound.
|
|
753
|
+
content = new Uint8Array(length);
|
|
754
|
+
content.set(current.subarray(0, length));
|
|
755
|
+
if (existing.dirty)
|
|
756
|
+
this.release(current);
|
|
632
757
|
}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
});
|
|
758
|
+
}
|
|
759
|
+
return {
|
|
760
|
+
type: "file",
|
|
761
|
+
content,
|
|
762
|
+
dirty: true,
|
|
763
|
+
mode: existing.mode,
|
|
764
|
+
atimeMs: realtimeMilliseconds(),
|
|
765
|
+
mtimeMs: realtimeMilliseconds(),
|
|
766
|
+
ino: existing.ino,
|
|
767
|
+
};
|
|
644
768
|
}
|
|
645
769
|
/**
|
|
646
770
|
* Buffers of files this tree owned and dropped (replaced by a rename,
|
|
@@ -757,11 +881,32 @@ export class SnapshotWaliFileSystem {
|
|
|
757
881
|
throw new Error(`ENOENT: no such file, unlink '${normalized}'`);
|
|
758
882
|
if (node.type !== "file")
|
|
759
883
|
throw new Error(`EISDIR: illegal operation on a directory, unlink '${normalized}'`);
|
|
760
|
-
if (node.dirty && node.content)
|
|
761
|
-
this.release(node.content);
|
|
762
884
|
this.nodes.delete(normalized);
|
|
885
|
+
this.releaseUnlinkedNode(node);
|
|
763
886
|
this.hide(normalized);
|
|
764
887
|
this.markMutation(normalized);
|
|
888
|
+
for (const alias of this.nodes.pathsFor(node))
|
|
889
|
+
this.markMutation(alias);
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
linkSync(source, destination) {
|
|
893
|
+
this.mutate(() => {
|
|
894
|
+
const from = normalize(source), to = normalize(destination);
|
|
895
|
+
const node = this.node(from);
|
|
896
|
+
if (!node)
|
|
897
|
+
throw new Error(`ENOENT: no such file, link '${from}'`);
|
|
898
|
+
if (node.type === "directory")
|
|
899
|
+
throw new Error(`EPERM: cannot hard-link directory '${from}'`);
|
|
900
|
+
if (this.node(to))
|
|
901
|
+
throw new Error(`EEXIST: destination exists, link '${to}'`);
|
|
902
|
+
const directory = this.node(parent(to));
|
|
903
|
+
if (!directory)
|
|
904
|
+
throw new Error(`ENOENT: no such directory, link '${to}'`);
|
|
905
|
+
if (directory.type !== "directory")
|
|
906
|
+
throw new Error(`ENOTDIR: not a directory, link '${to}'`);
|
|
907
|
+
this.unhide(to);
|
|
908
|
+
this.nodes.set(to, node);
|
|
909
|
+
this.markMutation(to);
|
|
765
910
|
});
|
|
766
911
|
}
|
|
767
912
|
rmdirSync(path) {
|
|
@@ -788,6 +933,8 @@ export class SnapshotWaliFileSystem {
|
|
|
788
933
|
const node = this.node(source);
|
|
789
934
|
if (!node)
|
|
790
935
|
throw new Error(`ENOENT: no such file or directory, rename '${source}'`);
|
|
936
|
+
if (this.node(destination) === node)
|
|
937
|
+
return; // Two names of one inode: Linux rename is a no-op.
|
|
791
938
|
this.mkdirSync(parent(destination), { recursive: true });
|
|
792
939
|
// What the store holds under the source moves too: it is made nodes here first.
|
|
793
940
|
if (this.lower && !this.isHidden(source)) {
|
|
@@ -811,9 +958,9 @@ export class SnapshotWaliFileSystem {
|
|
|
811
958
|
}
|
|
812
959
|
const target = `${destination}${path.slice(source.length)}`;
|
|
813
960
|
const replaced = this.nodes.get(target);
|
|
814
|
-
if (replaced?.type === "file" && replaced.dirty && replaced.content && replaced !== value)
|
|
815
|
-
this.release(replaced.content);
|
|
816
961
|
this.nodes.set(target, value);
|
|
962
|
+
if (replaced && replaced !== value)
|
|
963
|
+
this.releaseUnlinkedNode(replaced);
|
|
817
964
|
this.markMutation(path);
|
|
818
965
|
this.markMutation(target);
|
|
819
966
|
}
|
|
@@ -860,6 +1007,9 @@ export class SnapshotWaliFileSystem {
|
|
|
860
1007
|
this.markMutation(normalized);
|
|
861
1008
|
});
|
|
862
1009
|
}
|
|
1010
|
+
utimesMsSync(path, atimeMs, mtimeMs) {
|
|
1011
|
+
this.utimesSync(path, atimeMs, mtimeMs);
|
|
1012
|
+
}
|
|
863
1013
|
mutate(operation) {
|
|
864
1014
|
this.mutationDepth += 1;
|
|
865
1015
|
try {
|
|
@@ -875,7 +1025,12 @@ export class SnapshotWaliFileSystem {
|
|
|
875
1025
|
}
|
|
876
1026
|
markMutation(path) {
|
|
877
1027
|
this.mutated = true;
|
|
878
|
-
|
|
1028
|
+
const normalized = normalize(path);
|
|
1029
|
+
this.changedPaths?.add(normalized);
|
|
1030
|
+
const node = this.nodes.get(normalized);
|
|
1031
|
+
if (node)
|
|
1032
|
+
for (const alias of this.nodes.pathsFor(node))
|
|
1033
|
+
this.changedPaths?.add(alias);
|
|
879
1034
|
}
|
|
880
1035
|
}
|
|
881
1036
|
/** A store's file this large is read by range until the program writes it (ADR-0048); a smaller one is read whole, once. */
|
|
@@ -889,6 +1044,19 @@ const RANGE_UNCACHED_MINIMUM = 1 << 20;
|
|
|
889
1044
|
const SPARE_MINIMUM = 64 * 1024;
|
|
890
1045
|
const SPARE_COUNT = 2;
|
|
891
1046
|
const SPARE_TOTAL = 32 * 1024 * 1024;
|
|
1047
|
+
/** Replace an inode's content version without invalidating open descriptions. */
|
|
1048
|
+
function replaceNode(target, source) {
|
|
1049
|
+
if (target === source)
|
|
1050
|
+
return;
|
|
1051
|
+
delete target.content;
|
|
1052
|
+
delete target.load;
|
|
1053
|
+
delete target.range;
|
|
1054
|
+
delete target.size;
|
|
1055
|
+
delete target.dirty;
|
|
1056
|
+
delete target.fromStore;
|
|
1057
|
+
delete target.link;
|
|
1058
|
+
Object.assign(target, source);
|
|
1059
|
+
}
|
|
892
1060
|
function rootNode() {
|
|
893
1061
|
const now = realtimeMilliseconds();
|
|
894
1062
|
return { type: "directory", mode: 0o755, atimeMs: now, mtimeMs: now, ino: 1 };
|