@ai-ecoverse/slicc-kernel 1.124.7 → 1.124.9
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 +14 -1
- package/dist/kernel-worker.js +186 -67
- package/dist/kernel-worker.js.map +3 -3
- package/dist/node-process-worker.js +86 -23
- package/dist/node-process-worker.js.map +3 -3
- package/dist/node.js +186 -67
- package/dist/node.js.map +3 -3
- package/dist/process-worker.js +86 -23
- package/dist/process-worker.js.map +3 -3
- package/dist/types/fs/unlinked.d.ts +15 -1
- package/dist/types/kernel/host.d.ts +5 -3
- 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/process/wasi/wasi-fds.d.ts +1 -0
- package/dist/types/process/wasi/wasi-procfs.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 +2 -2
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.
|
|
@@ -1232,6 +1232,19 @@ A WASIX C program for `test/integration/sig-inherited.test.mjs` (#820), built on
|
|
|
1232
1232
|
|
|
1233
1233
|
**Build:** `WASIX_SYSROOT_33=/path/to/wasix-sysroot ./build.sh`, with `wasixcc` 0.4.7 and binaryen's `wasm-opt` on `PATH`. `bin/inherittest.wasm` is 39,068 bytes, sha256 `c561a52eb27b3c1b6204b997f4eec967b9f4869c6ab13e7915a5a94b326a710e`.
|
|
1234
1234
|
|
|
1235
|
+
### `sigjmptest`
|
|
1236
|
+
|
|
1237
|
+
A WASIX C program for `test/integration/sigjmp.test.mjs` ([#975](https://github.com/ai-ecoverse/slicc-kernel/issues/975), homescoop#1059), built on wasix-sysroot 2025.9.30-35. Each run takes a mode as its only argument:
|
|
1238
|
+
- `term`: a `SIGTERM` handler with `SIGUSR1` in its `sa_mask` sends itself `SIGUSR1`, which must wait until the handler returns: `term-done usr1`.
|
|
1239
|
+
- `longjmp`: `longjmp` out of a `SIGUSR1` handler that has `SIGUSR2` in `sa_mask`. It prints the mask after the jump, then unblocks everything and raises `SIGUSR1` again with a handler that returns.
|
|
1240
|
+
- `lsleep`, `lnanosleep`, `lread`, `lpoll`: the same with a `SIGALRM` that interrupts `sleep`, `nanosleep`, a `read` of an empty pipe, or `poll`. `sleep`, `nanosleep`, `read`, `poll` (no `l`) and `raise` use `sigsetjmp`/`siglongjmp`.
|
|
1241
|
+
- `direct`, `directmask`: `siglongjmp` with no signal at all. `fork` is `raise` in a forked child.
|
|
1242
|
+
- `hold` holds `flock` on `sigjmp.lock` for 3 seconds.
|
|
1243
|
+
|
|
1244
|
+
On -35, `sigsetjmp` is an ordinary libc function rather than the `setjmp` that LLVM's wasm setjmp/longjmp lowering rewrites, so every `siglongjmp` escapes the program (`[object WebAssembly.Exception]`, exit 70), even `direct`. That is homescoop#1059. The rows use plain `longjmp` until the sysroot fixes it.
|
|
1245
|
+
|
|
1246
|
+
**Build:** `WASIX_SYSROOT_35=/path/to/wasix-sysroot ./build.sh`, with `wasixcc` 0.4.7 and binaryen's `wasm-opt` 133 on `PATH`. Asyncify runs with `asyncify-ignore-indirect`, because functions with wasm `try` blocks can't be flattened. `bin/sigjmptest.wasm` is 52,240 bytes, sha256 `9768c2caafa0bcf55e4b9cd2c97a486d46496a275d4e9bd9d5998e163d630fbb`.
|
|
1247
|
+
|
|
1235
1248
|
### `handlertest`
|
|
1236
1249
|
|
|
1237
1250
|
A WASIX C program for `test/integration/handler-defer.test.mjs` (#971), built on wasix-sysroot 2025.9.30-33. Its `SIGTERM` handler counts how often and how deeply it runs. On its first run it sends itself `SIGTERM` again and then writes to stderr. `handlertest` installs it with no flags, and `handlertest nodefer` with `SA_NODEFER`. Either way it prints `the handler ran N times, at most D deep`.
|
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.9" : "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;
|
|
@@ -17684,6 +17750,7 @@ var VfsNode = class {
|
|
|
17684
17750
|
path;
|
|
17685
17751
|
orphaned;
|
|
17686
17752
|
id;
|
|
17753
|
+
ino;
|
|
17687
17754
|
onDirty;
|
|
17688
17755
|
onDirtyState;
|
|
17689
17756
|
counted = false;
|
|
@@ -18036,8 +18103,12 @@ var VfsNodes = class {
|
|
|
18036
18103
|
if (id2 !== void 0) this.know(node, id2);
|
|
18037
18104
|
return node;
|
|
18038
18105
|
}
|
|
18039
|
-
open(path, pin, writable2 = false, id2) {
|
|
18040
|
-
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;
|
|
18041
18112
|
node.opens++;
|
|
18042
18113
|
if (writable2) node.writers++;
|
|
18043
18114
|
return node;
|
|
@@ -18331,7 +18402,7 @@ function vfsFile(fs, opts, nodes) {
|
|
|
18331
18402
|
const access2 = opts.flags & O_ACCMODE;
|
|
18332
18403
|
const readable2 = access2 !== O_WRONLY;
|
|
18333
18404
|
const writable2 = access2 === O_WRONLY || access2 === O_RDWR;
|
|
18334
|
-
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);
|
|
18335
18406
|
let openError;
|
|
18336
18407
|
const atOpen = (op) => {
|
|
18337
18408
|
void node.serial(op).catch((err) => {
|
|
@@ -19101,6 +19172,7 @@ var WasmProcess = class {
|
|
|
19101
19172
|
const fd = this.fds.install(heldFile(), 3);
|
|
19102
19173
|
try {
|
|
19103
19174
|
const nodes = this.nodes;
|
|
19175
|
+
const gen = this.replacements?.generation() ?? 0;
|
|
19104
19176
|
const path = await this.openPath(req, marked);
|
|
19105
19177
|
if (inProc(path)) {
|
|
19106
19178
|
this.fds.installAt(fd, await this.procFile(req, path));
|
|
@@ -19125,7 +19197,15 @@ var WasmProcess = class {
|
|
|
19125
19197
|
}
|
|
19126
19198
|
});
|
|
19127
19199
|
} else {
|
|
19128
|
-
|
|
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
|
+
);
|
|
19129
19209
|
}
|
|
19130
19210
|
if (req.existing && !await present2(fs, path, true)) {
|
|
19131
19211
|
throw new KernelError("ENOENT");
|
|
@@ -19306,7 +19386,8 @@ var WasmProcess = class {
|
|
|
19306
19386
|
...req.truncate ? { truncate: true } : {},
|
|
19307
19387
|
...req.create ? { create: true } : {},
|
|
19308
19388
|
...req.pin ? { pin: req.pin } : {},
|
|
19309
|
-
...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 } : {}
|
|
19310
19391
|
},
|
|
19311
19392
|
this.nodes
|
|
19312
19393
|
);
|
|
@@ -19477,9 +19558,27 @@ var WasmProcess = class {
|
|
|
19477
19558
|
return false;
|
|
19478
19559
|
}
|
|
19479
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
|
+
}
|
|
19480
19580
|
async keptOr(path, handle) {
|
|
19481
|
-
|
|
19482
|
-
if (held !== void 0) return held;
|
|
19581
|
+
if (handle !== void 0) return this.keptHandle?.(handle) ?? path;
|
|
19483
19582
|
const kept = this.keptPath?.(path);
|
|
19484
19583
|
return kept !== void 0 && !await present2(this.options.fs, path, true) ? kept : path;
|
|
19485
19584
|
}
|
|
@@ -19489,25 +19588,37 @@ var WasmProcess = class {
|
|
|
19489
19588
|
await this.fds.dup2Clipboard(req.share, req.fd);
|
|
19490
19589
|
return;
|
|
19491
19590
|
}
|
|
19492
|
-
|
|
19493
|
-
|
|
19494
|
-
const
|
|
19495
|
-
|
|
19496
|
-
|
|
19497
|
-
|
|
19498
|
-
|
|
19499
|
-
|
|
19500
|
-
|
|
19501
|
-
|
|
19502
|
-
|
|
19503
|
-
|
|
19504
|
-
|
|
19505
|
-
|
|
19506
|
-
|
|
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)];
|
|
19507
19619
|
},
|
|
19508
|
-
|
|
19620
|
+
install
|
|
19509
19621
|
);
|
|
19510
|
-
this.fds.installAt(req.fd, file);
|
|
19511
19622
|
}
|
|
19512
19623
|
reserve(fd, min = 3, meta) {
|
|
19513
19624
|
if (fd === void 0) return this.fds.install(heldFile(meta), Math.max(3, min));
|
|
@@ -20160,6 +20271,12 @@ function inherited(opts) {
|
|
|
20160
20271
|
...opts.cred ? { cred: opts.cred } : {}
|
|
20161
20272
|
};
|
|
20162
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
|
+
}
|
|
20163
20280
|
function holdingToken(opts, holds2, caller) {
|
|
20164
20281
|
return mintSyncFsToken({
|
|
20165
20282
|
...opts.owners ? caller : { umask: caller.umask },
|
|
@@ -20167,6 +20284,7 @@ function holdingToken(opts, holds2, caller) {
|
|
|
20167
20284
|
...opts.links !== void 0 ? { links: opts.links } : {},
|
|
20168
20285
|
...opts.fresh ? { fresh: (path) => opts.fresh?.(path, caller.umask()) } : {},
|
|
20169
20286
|
...opts.source ? { source: opts.source } : {},
|
|
20287
|
+
...opts.replacements ? { replacements: opts.replacements } : {},
|
|
20170
20288
|
fs: opts.fs,
|
|
20171
20289
|
noLinks: opts.noLinks,
|
|
20172
20290
|
cwd: opts.cwd,
|
|
@@ -20248,6 +20366,7 @@ function threadInit(opts, program, sab, cred, thread, main) {
|
|
|
20248
20366
|
cred: copyCred(cred),
|
|
20249
20367
|
owners: opts.owners === true,
|
|
20250
20368
|
...opts.links !== void 0 ? { links: true } : {},
|
|
20369
|
+
...opts.changes ? { changes: opts.changes } : {},
|
|
20251
20370
|
...opts.ppid !== void 0 ? { ppid: opts.ppid } : {},
|
|
20252
20371
|
thread,
|
|
20253
20372
|
signals: main
|
|
@@ -20305,8 +20424,7 @@ function spawnWasmProcess(opts) {
|
|
|
20305
20424
|
created: created3,
|
|
20306
20425
|
holdsKept: async (path) => path.includes(`/${UNLINKED_DIR}/`) && (holds2.keeps(path) || await process.holds(path))
|
|
20307
20426
|
});
|
|
20308
|
-
process
|
|
20309
|
-
process.keptHandle = (handle2) => holds2.keptFor(handle2)?.hidden;
|
|
20427
|
+
heldBy(process, holds2, opts.replacements);
|
|
20310
20428
|
const worker = opts.createWorker();
|
|
20311
20429
|
const dying = new Dying();
|
|
20312
20430
|
const dispatch2 = (req, block) => dying.serve(
|
|
@@ -25426,6 +25544,7 @@ var Launcher = class {
|
|
|
25426
25544
|
nodes: stack.nodes,
|
|
25427
25545
|
held: this.held,
|
|
25428
25546
|
unlinkGate: stack.unlinked.gate,
|
|
25547
|
+
replacements: stack.unlinked,
|
|
25429
25548
|
statfs: (path) => this.mounts.statfs(path),
|
|
25430
25549
|
mounts: () => this.mounts.list(),
|
|
25431
25550
|
mount: (call, signal) => this.processMount(pid, call, signal),
|