@ai-ecoverse/slicc-kernel 1.124.6 → 1.124.8
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 +1 -1
- package/dist/kernel-worker.js +187 -67
- package/dist/kernel-worker.js.map +3 -3
- package/dist/node-process-worker.js +52 -19
- package/dist/node-process-worker.js.map +2 -2
- package/dist/node.js +187 -67
- package/dist/node.js.map +3 -3
- package/dist/process-worker.js +52 -19
- package/dist/process-worker.js.map +2 -2
- package/dist/types/fs/unlinked.d.ts +15 -1
- package/dist/types/kernel/host.d.ts +2 -1
- package/dist/types/kernel/process.d.ts +7 -0
- package/dist/types/kernel/vfs-file.d.ts +3 -1
- package/dist/types/mount/mount-fs.d.ts +1 -0
- package/dist/types/process/kernel-streams.d.ts +1 -0
- package/dist/types/realm/sync-fs-dispatch.d.ts +1 -0
- package/dist/types/realm/sync-fs-token-registry.d.ts +2 -1
- package/dist/types/realm/sync-fs-wire.d.ts +5 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -735,7 +735,7 @@ Programs mount and unmount with `mount(2)` and `umount2(2)`, so a `mount`/`umoun
|
|
|
735
735
|
|
|
736
736
|
### Exec
|
|
737
737
|
|
|
738
|
-
An exec'd program takes over the pid of the process it replaces, as on Linux: `getpid()` and `$$` in the new image, `/proc`, `ps`, `kill` and the parent's `waitpid` all agree on that pid, and the image's parent pid is the replaced process's. Its own children see that pid as their parent. A file removed or renamed over while programs hold it open keeps its bytes for them, as on Linux, whether the descriptors live in the kernel or a program opened the file in its own worker: every open, read-only ones included, is known to the kernel, and all holders share the one file (writes through one are seen through the others). The kernel does not copy it: as NFS does with its "silly rename", it moves the file into a hidden directory, `.slicc-unlinked/`, at the root of the mount it lives on (or of the root file system), and serves the holders' reads and writes from there (a worker's I/O on the old path while the path is gone). No file data is held in memory for it. The file is deleted when the last holder closes it or exits, and the directory goes as soon as it is empty. A file created at the path afterwards is the new file for everyone, also for the holder; a holder whose file another process renames (rather than removes) still uses the old path. A rename over a held file stays atomic for everyone else, as `rename(2)` is: moving the held file aside and the new one into its place are two steps, so a lookup of that name (`open`, `stat`, a read) that comes while they run waits for both, and one that started before them and missed the name in between looks again (also when it spelled the name through a symlinked parent, which the wait doesn't match by path: a miss is retried whenever any replacement finished meanwhile, up to three tries). Every open finds the old file or the new one, never no file (#903). A read through a held descriptor and the rename take the file's turn in order: lookups start to wait for the rename only once it has its turn on every held file it moves aside, so a read that came first finishes, and a rename over a file other processes keep reading never stalls them or itself (#980). A process that opens the file while the rename waits for its turn is a holder too: the rename looks for holders again once it has its turn, so that process keeps the old file as well, and a holder that closed meanwhile leaves nothing to move aside. The OPFS root does the same for its own two steps: a rename onto an existing name replaces the file with `move()` (or, in a browser without it, a copy into the name and a removal of the source), and a lookup that loses that race to a stale handle looks again. A `tmpfs` mount replaces the name in one step; on `fsa` and `hostfs` mounts it is the real file system's `rename`. Listings in the kernel leave `.slicc-unlinked` out, but on an `fsa` or `hostfs` mount it is visible on the real machine while a removed file is held (add `.slicc-unlinked/` to a `.gitignore` there). Each kernel names its entries after itself and holds a Web Lock for that name while it runs; when a mount is set up (and the root at start), the kernel removes the entries whose owner holds no lock, so a tab that died while holding a file leaves nothing behind for long, and entries of other live tabs on the same origin are left alone. Under the Node entry, which has no Web Locks, entries are named after the kernel's process id instead, and an entry is removed when that process is gone (`kill(pid, 0)` fails with `ESRCH`); a reused pid leaves the entry alone. Signals a program ignores stay ignored in the image it execs and in the children it spawns or forks, as POSIX has it, so `nohup cmd` and `trap '' TERM; exec cmd` keep `cmd` safe from them; caught signals go back to their default. The kernel holds what was handed down, so a new image that sets one of those signals back to the default with `signal(SIG_DFL)` still ignores it, until it installs a handler.
|
|
738
|
+
An exec'd program takes over the pid of the process it replaces, as on Linux: `getpid()` and `$$` in the new image, `/proc`, `ps`, `kill` and the parent's `waitpid` all agree on that pid, and the image's parent pid is the replaced process's. Its own children see that pid as their parent. A file removed or renamed over while programs hold it open keeps its bytes for them, as on Linux, whether the descriptors live in the kernel or a program opened the file in its own worker: every open, read-only ones included, is known to the kernel, and all holders share the one file (writes through one are seen through the others). The kernel does not copy it: as NFS does with its "silly rename", it moves the file into a hidden directory, `.slicc-unlinked/`, at the root of the mount it lives on (or of the root file system), and serves the holders' reads and writes from there (a worker's I/O on the old path while the path is gone). No file data is held in memory for it. The file is deleted when the last holder closes it or exits, and the directory goes as soon as it is empty. A file created at the path afterwards is the new file for everyone, also for the holder; a holder whose file another process renames (rather than removes) still uses the old path. A rename over a held file stays atomic for everyone else, as `rename(2)` is: moving the held file aside and the new one into its place are two steps, so a lookup of that name (`open`, `stat`, a read) that comes while they run waits for both, and one that started before them and missed the name in between looks again (also when it spelled the name through a symlinked parent, which the wait doesn't match by path: a miss is retried whenever any replacement finished meanwhile, up to three tries). Every open finds the old file or the new one, never no file (#903). A read through a held descriptor and the rename take the file's turn in order: lookups start to wait for the rename only once it has its turn on every held file it moves aside, so a read that came first finishes, and a rename over a file other processes keep reading never stalls them or itself (#980). A process that opens the file while the rename waits for its turn is a holder too: the rename looks for holders again once it has its turn, so that process keeps the old file as well, and a holder that closed meanwhile leaves nothing to move aside. The same holds for a directory that is removed or renamed over while programs hold files in it: the removal keeps looking for holders until nothing new appears, moving each held file aside, and lookups inside the directory wait for it. An open, a hand-over of a program's descriptor to the kernel (at a fork) or a hold that was under way while the directory went looks its path up again, so it gets the kept old file or ENOENT, never an empty file at the old name (#1013). An open of a path that is gone, with a handle the process no longer holds, is ENOENT rather than a descriptor for a missing file. The OPFS root does the same for its own two steps: a rename onto an existing name replaces the file with `move()` (or, in a browser without it, a copy into the name and a removal of the source), and a lookup that loses that race to a stale handle looks again. A `tmpfs` mount replaces the name in one step; on `fsa` and `hostfs` mounts it is the real file system's `rename`. Listings in the kernel leave `.slicc-unlinked` out, but on an `fsa` or `hostfs` mount it is visible on the real machine while a removed file is held (add `.slicc-unlinked/` to a `.gitignore` there). Each kernel names its entries after itself and holds a Web Lock for that name while it runs; when a mount is set up (and the root at start), the kernel removes the entries whose owner holds no lock, so a tab that died while holding a file leaves nothing behind for long, and entries of other live tabs on the same origin are left alone. Under the Node entry, which has no Web Locks, entries are named after the kernel's process id instead, and an entry is removed when that process is gone (`kill(pid, 0)` fails with `ESRCH`); a reused pid leaves the entry alone. Signals a program ignores stay ignored in the image it execs and in the children it spawns or forks, as POSIX has it, so `nohup cmd` and `trap '' TERM; exec cmd` keep `cmd` safe from them; caught signals go back to their default. The kernel holds what was handed down, so a new image that sets one of those signals back to the default with `signal(SIG_DFL)` still ignores it, until it installs a handler.
|
|
739
739
|
|
|
740
740
|
- **Emscripten:** `Module.sliccKernel.execve(file, argv, env, cwd)` runs `file` as this process's new image on its fds 0, 1 and 2, waits for it, and returns its wait status, or a negative errno when it cannot start. `env` and `cwd` may be `null` for this process's own. An exec shim detects it with `typeof Module.sliccKernel.execve === 'function'` and exits with the status it returns, as `execve` never returns on success. Older shims spawn the program and then call `Module.sliccKernel.execWait(pid)`; that works the same way: a new program asks the kernel for its pid before `main`, and the kernel answers once its parent has made its next call, so an image an older shim execs also reports the pid it replaced (the wait starts once the program is loaded; should the parent make no call within 100 ms after that, the kernel answers anyway, and the image may then report its own pid; `launcher.identities` counts the programs that asked and those answered by that timeout).
|
|
741
741
|
- **WASIX:** `proc_exec` does this by itself.
|
package/dist/kernel-worker.js
CHANGED
|
@@ -6625,7 +6625,7 @@ function satisfies(version, range2) {
|
|
|
6625
6625
|
}
|
|
6626
6626
|
|
|
6627
6627
|
// src/version.ts
|
|
6628
|
-
var KERNEL_VERSION = true ? "1.124.
|
|
6628
|
+
var KERNEL_VERSION = true ? "1.124.8" : "0.0.0-dev";
|
|
6629
6629
|
var OS_RELEASE = "6.0.0-slicc";
|
|
6630
6630
|
function procVersion() {
|
|
6631
6631
|
return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
|
|
@@ -7465,6 +7465,7 @@ var LOOKUP_OPS = [
|
|
|
7465
7465
|
"realpath"
|
|
7466
7466
|
];
|
|
7467
7467
|
var within4 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
|
|
7468
|
+
var TOUCHED_MAX = 4096;
|
|
7468
7469
|
var parentOf5 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
|
|
7469
7470
|
var lockName = (instance) => `slicc-unlinked:${instance}`;
|
|
7470
7471
|
var dirOf = (root) => root === "/" ? `/${UNLINKED_DIR}` : `${root}/${UNLINKED_DIR}`;
|
|
@@ -7503,6 +7504,9 @@ var UnlinkedKeeper = class {
|
|
|
7503
7504
|
queue = Promise.resolve();
|
|
7504
7505
|
count = 0;
|
|
7505
7506
|
replacing = /* @__PURE__ */ new Map();
|
|
7507
|
+
gen = 0;
|
|
7508
|
+
floor = 0;
|
|
7509
|
+
touched = /* @__PURE__ */ new Map();
|
|
7506
7510
|
replaced = 0;
|
|
7507
7511
|
links = /* @__PURE__ */ new Map();
|
|
7508
7512
|
linked = /* @__PURE__ */ new Map();
|
|
@@ -7529,28 +7533,28 @@ var UnlinkedKeeper = class {
|
|
|
7529
7533
|
const { replacing } = this;
|
|
7530
7534
|
const replace = async (target, op) => {
|
|
7531
7535
|
const { promise, resolve: resolve2 } = Promise.withResolvers();
|
|
7536
|
+
keeper.touch(target);
|
|
7532
7537
|
let mine;
|
|
7533
7538
|
const gated = () => {
|
|
7534
7539
|
const before = replacing.get(target);
|
|
7535
|
-
mine = { done: Promise.all([before?.done, promise]).then(() => void 0) };
|
|
7540
|
+
mine = { done: Promise.all([before?.done, promise]).then(() => void 0), sealed: false };
|
|
7536
7541
|
replacing.set(target, mine);
|
|
7537
7542
|
};
|
|
7543
|
+
const sealed = () => {
|
|
7544
|
+
if (mine) mine.sealed = true;
|
|
7545
|
+
};
|
|
7538
7546
|
try {
|
|
7539
|
-
await op(gated);
|
|
7547
|
+
await op(gated, sealed);
|
|
7540
7548
|
} finally {
|
|
7541
7549
|
keeper.replaced += 1;
|
|
7550
|
+
keeper.touch(target);
|
|
7542
7551
|
resolve2();
|
|
7543
7552
|
if (mine && replacing.get(target) === mine) replacing.delete(target);
|
|
7544
7553
|
}
|
|
7545
7554
|
};
|
|
7546
7555
|
const unreplaced = async (path, op) => {
|
|
7547
7556
|
const at = fs.resolvePath("/", path);
|
|
7548
|
-
const pending =
|
|
7549
|
-
for (let p = at; replacing.size > 0; p = parentOf5(p)) {
|
|
7550
|
-
await replacing.get(p)?.done;
|
|
7551
|
-
if (p === "/") return;
|
|
7552
|
-
}
|
|
7553
|
-
};
|
|
7557
|
+
const pending = () => keeper.settled(at);
|
|
7554
7558
|
for (let attempt = 1; ; attempt++) {
|
|
7555
7559
|
await pending();
|
|
7556
7560
|
const seen = keeper.replaced;
|
|
@@ -7584,8 +7588,8 @@ var UnlinkedKeeper = class {
|
|
|
7584
7588
|
lstat: (path) => unreplaced(path, async () => counted2(await fs.lstat(path))),
|
|
7585
7589
|
async rm(path, options) {
|
|
7586
7590
|
const target = fs.resolvePath("/", path);
|
|
7587
|
-
await replace(target, async (gated) => {
|
|
7588
|
-
const pinned = await keeper.pin(target, gated);
|
|
7591
|
+
await replace(target, async (gated, sealed) => {
|
|
7592
|
+
const pinned = await keeper.pin(target, gated, sealed);
|
|
7589
7593
|
if (pinned.moved.has(target)) pinned.commit();
|
|
7590
7594
|
else await keeper.settle(pinned, () => fs.rm(path, options));
|
|
7591
7595
|
});
|
|
@@ -7593,8 +7597,8 @@ var UnlinkedKeeper = class {
|
|
|
7593
7597
|
async rename(from, to) {
|
|
7594
7598
|
const target = fs.resolvePath("/", to);
|
|
7595
7599
|
if (target === fs.resolvePath("/", from)) return fs.rename(from, to);
|
|
7596
|
-
await replace(target, async (gated) => {
|
|
7597
|
-
const pinned = await keeper.pin(target, gated);
|
|
7600
|
+
await replace(target, async (gated, sealed) => {
|
|
7601
|
+
const pinned = await keeper.pin(target, gated, sealed);
|
|
7598
7602
|
await keeper.settle(pinned, () => fs.rename(from, to));
|
|
7599
7603
|
});
|
|
7600
7604
|
},
|
|
@@ -7602,6 +7606,39 @@ var UnlinkedKeeper = class {
|
|
|
7602
7606
|
readdirStat: async (path) => (await fs.readdirStat(path)).filter(([name2]) => name2 !== UNLINKED_DIR).map(([name2, st]) => [name2, st && counted2(st)])
|
|
7603
7607
|
};
|
|
7604
7608
|
}
|
|
7609
|
+
async settled(path) {
|
|
7610
|
+
for (let p = path; this.replacing.size > 0; p = parentOf5(p)) {
|
|
7611
|
+
await this.replacing.get(p)?.done;
|
|
7612
|
+
if (p === "/") return;
|
|
7613
|
+
}
|
|
7614
|
+
}
|
|
7615
|
+
generation() {
|
|
7616
|
+
return this.gen;
|
|
7617
|
+
}
|
|
7618
|
+
changedSince(path, gen) {
|
|
7619
|
+
if (gen < this.floor) return true;
|
|
7620
|
+
for (let p = path; this.touched.size > 0; p = parentOf5(p)) {
|
|
7621
|
+
if ((this.touched.get(p) ?? 0) > gen) return true;
|
|
7622
|
+
if (p === "/") return false;
|
|
7623
|
+
}
|
|
7624
|
+
return false;
|
|
7625
|
+
}
|
|
7626
|
+
touch(target) {
|
|
7627
|
+
this.gen += 1;
|
|
7628
|
+
this.touched.delete(target);
|
|
7629
|
+
this.touched.set(target, this.gen);
|
|
7630
|
+
if (this.touched.size <= TOUCHED_MAX) return;
|
|
7631
|
+
const [oldest, at] = this.touched.entries().next().value;
|
|
7632
|
+
this.touched.delete(oldest);
|
|
7633
|
+
this.floor = at;
|
|
7634
|
+
}
|
|
7635
|
+
missed(path) {
|
|
7636
|
+
for (let p = path; this.replacing.size > 0; p = parentOf5(p)) {
|
|
7637
|
+
if (this.replacing.get(p)?.sealed) return true;
|
|
7638
|
+
if (p === "/") return false;
|
|
7639
|
+
}
|
|
7640
|
+
return false;
|
|
7641
|
+
}
|
|
7605
7642
|
async settle(pinned, op) {
|
|
7606
7643
|
try {
|
|
7607
7644
|
await op();
|
|
@@ -7622,33 +7659,41 @@ var UnlinkedKeeper = class {
|
|
|
7622
7659
|
}
|
|
7623
7660
|
return held;
|
|
7624
7661
|
}
|
|
7625
|
-
async pin(target, gated = () => void 0) {
|
|
7662
|
+
async pin(target, gated = () => void 0, sealed = () => void 0) {
|
|
7626
7663
|
const paths = [];
|
|
7627
7664
|
const releases = [];
|
|
7628
|
-
|
|
7629
|
-
|
|
7630
|
-
|
|
7631
|
-
|
|
7632
|
-
if (paths.some((path) => path > fresh2[0])) {
|
|
7633
|
-
for (const release of releases.splice(0)) release();
|
|
7634
|
-
fresh2.push(...paths.splice(0));
|
|
7665
|
+
const take = async () => {
|
|
7666
|
+
for (; ; ) {
|
|
7667
|
+
const fresh2 = [...this.heldWithin(target).keys()].filter((path) => !paths.includes(path));
|
|
7668
|
+
if (fresh2.length === 0) return;
|
|
7635
7669
|
fresh2.sort();
|
|
7670
|
+
if (paths.some((path) => path > fresh2[0])) {
|
|
7671
|
+
for (const release of releases.splice(0)) release();
|
|
7672
|
+
fresh2.push(...paths.splice(0));
|
|
7673
|
+
fresh2.sort();
|
|
7674
|
+
}
|
|
7675
|
+
for (const path of fresh2) {
|
|
7676
|
+
paths.push(path);
|
|
7677
|
+
releases.push(await this.gate.acquire(path));
|
|
7678
|
+
}
|
|
7636
7679
|
}
|
|
7637
|
-
|
|
7638
|
-
|
|
7639
|
-
releases.push(await this.gate.acquire(path));
|
|
7640
|
-
}
|
|
7641
|
-
}
|
|
7680
|
+
};
|
|
7681
|
+
await take();
|
|
7642
7682
|
gated();
|
|
7643
|
-
const
|
|
7644
|
-
const held = new Map(
|
|
7645
|
-
paths.flatMap((path) => now.has(path) ? [[path, now.get(path)]] : [])
|
|
7646
|
-
);
|
|
7683
|
+
const held = /* @__PURE__ */ new Map();
|
|
7647
7684
|
const hidden = /* @__PURE__ */ new Map();
|
|
7648
|
-
for (
|
|
7649
|
-
|
|
7650
|
-
|
|
7685
|
+
for (; ; ) {
|
|
7686
|
+
await take();
|
|
7687
|
+
const now = this.heldWithin(target);
|
|
7688
|
+
const next3 = paths.filter((path) => now.has(path) && !held.has(path));
|
|
7689
|
+
if (next3.length === 0) break;
|
|
7690
|
+
for (const path of next3) {
|
|
7691
|
+
held.set(path, now.get(path));
|
|
7692
|
+
const at = await this.hide(path);
|
|
7693
|
+
if (at !== void 0) hidden.set(path, at);
|
|
7694
|
+
}
|
|
7651
7695
|
}
|
|
7696
|
+
sealed();
|
|
7652
7697
|
const done4 = () => {
|
|
7653
7698
|
for (const release of releases) release();
|
|
7654
7699
|
};
|
|
@@ -10314,6 +10359,9 @@ var HeldPaths = class extends Map {
|
|
|
10314
10359
|
keptFor(handle) {
|
|
10315
10360
|
return this.keptHandles.get(handle);
|
|
10316
10361
|
}
|
|
10362
|
+
holdsHandle(handle) {
|
|
10363
|
+
return this.handles.has(handle);
|
|
10364
|
+
}
|
|
10317
10365
|
keeps(hidden) {
|
|
10318
10366
|
for (const file of this.uses.keys()) if (file.hidden === hidden) return true;
|
|
10319
10367
|
for (const file of this.unlinked.values()) if (file.hidden === hidden) return true;
|
|
@@ -11760,10 +11808,16 @@ async function run(entry, path, req) {
|
|
|
11760
11808
|
return done;
|
|
11761
11809
|
case "exists":
|
|
11762
11810
|
return json2(await fs.exists(path));
|
|
11763
|
-
case "stat":
|
|
11764
|
-
|
|
11765
|
-
|
|
11766
|
-
return json2(
|
|
11811
|
+
case "stat": {
|
|
11812
|
+
const gen = entry.replacements?.generation();
|
|
11813
|
+
const st = statJson(await shownAs(entry, path, await fs.stat(path)));
|
|
11814
|
+
return json2(gen === void 0 ? st : { ...st, gen });
|
|
11815
|
+
}
|
|
11816
|
+
case "lstat": {
|
|
11817
|
+
const gen = entry.replacements?.generation();
|
|
11818
|
+
const st = statJson(await shownAs(entry, path, await fs.lstat(path)));
|
|
11819
|
+
return json2(gen === void 0 ? st : { ...st, gen });
|
|
11820
|
+
}
|
|
11767
11821
|
case "readdir":
|
|
11768
11822
|
return json2(await fs.readdir(path));
|
|
11769
11823
|
case "readdir-stat":
|
|
@@ -11938,14 +11992,26 @@ async function opening(entry, path, mode) {
|
|
|
11938
11992
|
await need(g, "open", path, want, { directory: false });
|
|
11939
11993
|
}
|
|
11940
11994
|
}
|
|
11995
|
+
async function stillThere(entry, path, req) {
|
|
11996
|
+
if (req.gen === void 0 || !entry.replacements?.changedSince(path, req.gen)) return;
|
|
11997
|
+
const st = await entry.fs.stat(path).catch(() => void 0);
|
|
11998
|
+
if (!st) throw syncError("ENOENT", path);
|
|
11999
|
+
}
|
|
11941
12000
|
async function holdOp(entry, path, req) {
|
|
11942
12001
|
const mode = req.mode ?? 0;
|
|
11943
12002
|
const [on, open] = [(mode & 1) === 1, (mode & 2) === 2];
|
|
11944
12003
|
if (on && open) {
|
|
11945
|
-
|
|
11946
|
-
|
|
11947
|
-
|
|
11948
|
-
|
|
12004
|
+
for (; ; ) {
|
|
12005
|
+
const r = entry.replacements;
|
|
12006
|
+
await r?.settled(path);
|
|
12007
|
+
const gen = r?.generation() ?? 0;
|
|
12008
|
+
await opening(entry, path, mode);
|
|
12009
|
+
const real = await entry.fs.realpath?.(path, true).catch(() => path);
|
|
12010
|
+
await entry.source?.open(path, real);
|
|
12011
|
+
await stillThere(entry, path, req);
|
|
12012
|
+
if (!r || !r.missed(path) && !r.changedSince(path, gen))
|
|
12013
|
+
return json2(entry.hold?.(path, on, open, real) ?? null);
|
|
12014
|
+
}
|
|
11949
12015
|
}
|
|
11950
12016
|
entry.hold?.(path, on, open, void 0, req.handle);
|
|
11951
12017
|
return done;
|
|
@@ -12778,6 +12844,8 @@ var spinning = (globalThis.navigator?.hardwareConcurrency ?? 0) >= SPIN_CORES;
|
|
|
12778
12844
|
var EARLY_WHOLE_FILE = 64 * 1024;
|
|
12779
12845
|
|
|
12780
12846
|
// src/process/process-fds.ts
|
|
12847
|
+
var REFUSED = /* @__PURE__ */ new Set([wasiErrno("EACCES"), wasiErrno("EPERM")]);
|
|
12848
|
+
var OWN_ERRORS = 128 | 131072;
|
|
12781
12849
|
var field3 = (text) => (text || "none").replace(/[\s\\]/g, (c) => `\\${c.charCodeAt(0).toString(8).padStart(3, "0")}`);
|
|
12782
12850
|
var pinnedRoot;
|
|
12783
12851
|
var rootKind = () => globalThis.navigator?.storage ? "opfs" : "memory";
|
|
@@ -17682,6 +17750,7 @@ var VfsNode = class {
|
|
|
17682
17750
|
path;
|
|
17683
17751
|
orphaned;
|
|
17684
17752
|
id;
|
|
17753
|
+
ino;
|
|
17685
17754
|
onDirty;
|
|
17686
17755
|
onDirtyState;
|
|
17687
17756
|
counted = false;
|
|
@@ -18034,8 +18103,12 @@ var VfsNodes = class {
|
|
|
18034
18103
|
if (id2 !== void 0) this.know(node, id2);
|
|
18035
18104
|
return node;
|
|
18036
18105
|
}
|
|
18037
|
-
open(path, pin, writable2 = false, id2) {
|
|
18038
|
-
const
|
|
18106
|
+
open(path, pin, writable2 = false, id2, ino) {
|
|
18107
|
+
const found = this.named(path, id2);
|
|
18108
|
+
const same = !found || ino === void 0 || found.ino === void 0 || found.ino === ino;
|
|
18109
|
+
if (found && !same) this.orphan(found);
|
|
18110
|
+
const node = (same ? found : void 0) ?? this.create(path, id2, new VfsNode(this.fs, path, void 0, false, false, pin));
|
|
18111
|
+
node.ino ??= ino;
|
|
18039
18112
|
node.opens++;
|
|
18040
18113
|
if (writable2) node.writers++;
|
|
18041
18114
|
return node;
|
|
@@ -18329,7 +18402,7 @@ function vfsFile(fs, opts, nodes) {
|
|
|
18329
18402
|
const access2 = opts.flags & O_ACCMODE;
|
|
18330
18403
|
const readable2 = access2 !== O_WRONLY;
|
|
18331
18404
|
const writable2 = access2 === O_WRONLY || access2 === O_RDWR;
|
|
18332
|
-
const node = !nodes || opts.orphan ? new VfsNode(fs, opts.path, opts.contents, opts.orphan === true, opts.dirty === true) : opts.contents !== void 0 ? nodes.adopt(opts.path, opts.contents, opts.dirty === true, writable2, opts.id) : nodes.open(opts.path, opts.pin, writable2, opts.id);
|
|
18405
|
+
const node = !nodes || opts.orphan ? new VfsNode(fs, opts.path, opts.contents, opts.orphan === true, opts.dirty === true) : opts.contents !== void 0 ? nodes.adopt(opts.path, opts.contents, opts.dirty === true, writable2, opts.id) : nodes.open(opts.path, opts.pin, writable2, opts.id, opts.ino);
|
|
18333
18406
|
let openError;
|
|
18334
18407
|
const atOpen = (op) => {
|
|
18335
18408
|
void node.serial(op).catch((err) => {
|
|
@@ -19099,6 +19172,7 @@ var WasmProcess = class {
|
|
|
19099
19172
|
const fd = this.fds.install(heldFile(), 3);
|
|
19100
19173
|
try {
|
|
19101
19174
|
const nodes = this.nodes;
|
|
19175
|
+
const gen = this.replacements?.generation() ?? 0;
|
|
19102
19176
|
const path = await this.openPath(req, marked);
|
|
19103
19177
|
if (inProc(path)) {
|
|
19104
19178
|
this.fds.installAt(fd, await this.procFile(req, path));
|
|
@@ -19123,7 +19197,15 @@ var WasmProcess = class {
|
|
|
19123
19197
|
}
|
|
19124
19198
|
});
|
|
19125
19199
|
} else {
|
|
19126
|
-
|
|
19200
|
+
await this.unmissed(
|
|
19201
|
+
() => this.openPath(req, marked),
|
|
19202
|
+
async (at) => {
|
|
19203
|
+
await this.stillOpen(at, req.handle);
|
|
19204
|
+
return nodes.identify(at);
|
|
19205
|
+
},
|
|
19206
|
+
(id2, at) => this.fds.installAt(fd, this.openVfsFile({ ...open, path: at }, id2, fs)),
|
|
19207
|
+
{ at: path, gen }
|
|
19208
|
+
);
|
|
19127
19209
|
}
|
|
19128
19210
|
if (req.existing && !await present2(fs, path, true)) {
|
|
19129
19211
|
throw new KernelError("ENOENT");
|
|
@@ -19304,7 +19386,8 @@ var WasmProcess = class {
|
|
|
19304
19386
|
...req.truncate ? { truncate: true } : {},
|
|
19305
19387
|
...req.create ? { create: true } : {},
|
|
19306
19388
|
...req.pin ? { pin: req.pin } : {},
|
|
19307
|
-
...id2 !== void 0 && !req.orphan ? { id: id2 } : {}
|
|
19389
|
+
...id2 !== void 0 && !req.orphan ? { id: id2 } : {},
|
|
19390
|
+
...req.ino !== void 0 && !req.orphan ? { ino: req.ino } : {}
|
|
19308
19391
|
},
|
|
19309
19392
|
this.nodes
|
|
19310
19393
|
);
|
|
@@ -19475,9 +19558,27 @@ var WasmProcess = class {
|
|
|
19475
19558
|
return false;
|
|
19476
19559
|
}
|
|
19477
19560
|
keptHandle;
|
|
19561
|
+
holdsHandle;
|
|
19562
|
+
replacements;
|
|
19563
|
+
async unmissed(path, look, make, first) {
|
|
19564
|
+
let given = first;
|
|
19565
|
+
for (; ; ) {
|
|
19566
|
+
const r = this.replacements;
|
|
19567
|
+
const gen = given?.gen ?? r?.generation() ?? 0;
|
|
19568
|
+
const at = given?.at ?? await path();
|
|
19569
|
+
given = void 0;
|
|
19570
|
+
await r?.settled(at);
|
|
19571
|
+
const found = await look(at);
|
|
19572
|
+
if (!r || !r.missed(at) && !r.changedSince(at, gen)) return make(found, at);
|
|
19573
|
+
}
|
|
19574
|
+
}
|
|
19575
|
+
async stillOpen(at, handle) {
|
|
19576
|
+
if (handle === void 0 || this.keptHandle?.(handle) !== void 0) return;
|
|
19577
|
+
if (this.holdsHandle?.(handle) !== false) return;
|
|
19578
|
+
if (!await present2(this.options.fs, at, true)) throw new KernelError("ENOENT");
|
|
19579
|
+
}
|
|
19478
19580
|
async keptOr(path, handle) {
|
|
19479
|
-
|
|
19480
|
-
if (held !== void 0) return held;
|
|
19581
|
+
if (handle !== void 0) return this.keptHandle?.(handle) ?? path;
|
|
19481
19582
|
const kept = this.keptPath?.(path);
|
|
19482
19583
|
return kept !== void 0 && !await present2(this.options.fs, path, true) ? kept : path;
|
|
19483
19584
|
}
|
|
@@ -19487,25 +19588,37 @@ var WasmProcess = class {
|
|
|
19487
19588
|
await this.fds.dup2Clipboard(req.share, req.fd);
|
|
19488
19589
|
return;
|
|
19489
19590
|
}
|
|
19490
|
-
|
|
19491
|
-
|
|
19492
|
-
const
|
|
19493
|
-
|
|
19494
|
-
|
|
19495
|
-
|
|
19496
|
-
|
|
19497
|
-
|
|
19498
|
-
|
|
19499
|
-
|
|
19500
|
-
|
|
19501
|
-
|
|
19502
|
-
|
|
19503
|
-
|
|
19504
|
-
|
|
19591
|
+
const asked = req.path;
|
|
19592
|
+
if (asked === void 0) throw new KernelError("EINVAL");
|
|
19593
|
+
const install = ([path, id2]) => {
|
|
19594
|
+
if (!this.fds.get(req.fd).file.held) throw new KernelError("EBADF");
|
|
19595
|
+
const file = vfsFile(
|
|
19596
|
+
this.options.fs,
|
|
19597
|
+
{
|
|
19598
|
+
path,
|
|
19599
|
+
flags: req.flags ?? 0,
|
|
19600
|
+
position: req.position ?? 0,
|
|
19601
|
+
...req.contents !== void 0 ? { contents: req.contents } : {},
|
|
19602
|
+
...req.orphan ? { orphan: true } : {},
|
|
19603
|
+
...req.dirty ? { dirty: true } : {},
|
|
19604
|
+
...req.pin ? { pin: req.pin } : {},
|
|
19605
|
+
...id2 !== void 0 ? { id: id2 } : {},
|
|
19606
|
+
...req.ino !== void 0 && path === asked ? { ino: req.ino } : {}
|
|
19607
|
+
},
|
|
19608
|
+
this.nodes
|
|
19609
|
+
);
|
|
19610
|
+
this.fds.installAt(req.fd, file);
|
|
19611
|
+
};
|
|
19612
|
+
if (req.orphan) return install([asked, void 0]);
|
|
19613
|
+
await this.unmissed(
|
|
19614
|
+
() => this.nodes.targetKey(asked),
|
|
19615
|
+
async (target) => {
|
|
19616
|
+
const at = await this.keptOr(target, req.handle);
|
|
19617
|
+
await this.stillOpen(at, req.handle);
|
|
19618
|
+
return [at, await this.nodes.identify(at)];
|
|
19505
19619
|
},
|
|
19506
|
-
|
|
19620
|
+
install
|
|
19507
19621
|
);
|
|
19508
|
-
this.fds.installAt(req.fd, file);
|
|
19509
19622
|
}
|
|
19510
19623
|
reserve(fd, min = 3, meta) {
|
|
19511
19624
|
if (fd === void 0) return this.fds.install(heldFile(meta), Math.max(3, min));
|
|
@@ -20158,6 +20271,12 @@ function inherited(opts) {
|
|
|
20158
20271
|
...opts.cred ? { cred: opts.cred } : {}
|
|
20159
20272
|
};
|
|
20160
20273
|
}
|
|
20274
|
+
function heldBy(process, holds2, replacements) {
|
|
20275
|
+
process.keptPath = (path) => holds2.keptAt(path)?.hidden;
|
|
20276
|
+
process.keptHandle = (handle) => holds2.keptFor(handle)?.hidden;
|
|
20277
|
+
process.holdsHandle = (handle) => holds2.holdsHandle(handle);
|
|
20278
|
+
if (replacements) process.replacements = replacements;
|
|
20279
|
+
}
|
|
20161
20280
|
function holdingToken(opts, holds2, caller) {
|
|
20162
20281
|
return mintSyncFsToken({
|
|
20163
20282
|
...opts.owners ? caller : { umask: caller.umask },
|
|
@@ -20165,6 +20284,7 @@ function holdingToken(opts, holds2, caller) {
|
|
|
20165
20284
|
...opts.links !== void 0 ? { links: opts.links } : {},
|
|
20166
20285
|
...opts.fresh ? { fresh: (path) => opts.fresh?.(path, caller.umask()) } : {},
|
|
20167
20286
|
...opts.source ? { source: opts.source } : {},
|
|
20287
|
+
...opts.replacements ? { replacements: opts.replacements } : {},
|
|
20168
20288
|
fs: opts.fs,
|
|
20169
20289
|
noLinks: opts.noLinks,
|
|
20170
20290
|
cwd: opts.cwd,
|
|
@@ -20303,8 +20423,7 @@ function spawnWasmProcess(opts) {
|
|
|
20303
20423
|
created: created3,
|
|
20304
20424
|
holdsKept: async (path) => path.includes(`/${UNLINKED_DIR}/`) && (holds2.keeps(path) || await process.holds(path))
|
|
20305
20425
|
});
|
|
20306
|
-
process
|
|
20307
|
-
process.keptHandle = (handle2) => holds2.keptFor(handle2)?.hidden;
|
|
20426
|
+
heldBy(process, holds2, opts.replacements);
|
|
20308
20427
|
const worker = opts.createWorker();
|
|
20309
20428
|
const dying = new Dying();
|
|
20310
20429
|
const dispatch2 = (req, block) => dying.serve(
|
|
@@ -25424,6 +25543,7 @@ var Launcher = class {
|
|
|
25424
25543
|
nodes: stack.nodes,
|
|
25425
25544
|
held: this.held,
|
|
25426
25545
|
unlinkGate: stack.unlinked.gate,
|
|
25546
|
+
replacements: stack.unlinked,
|
|
25427
25547
|
statfs: (path) => this.mounts.statfs(path),
|
|
25428
25548
|
mounts: () => this.mounts.list(),
|
|
25429
25549
|
mount: (call, signal) => this.processMount(pid, call, signal),
|