@ai-ecoverse/slicc-kernel 1.98.1 → 1.98.3
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 +2 -2
- package/dist/kernel-worker.js +162 -33
- package/dist/kernel-worker.js.map +3 -3
- package/dist/node-process-worker.js +87 -20
- package/dist/node-process-worker.js.map +2 -2
- package/dist/node.js +162 -33
- package/dist/node.js.map +3 -3
- package/dist/process-worker.js +87 -20
- package/dist/process-worker.js.map +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -535,8 +535,8 @@ Directories are renamed with `FileSystemHandle.move()` where available, else by
|
|
|
535
535
|
- **Capabilities:** `readonly`, `symlinks`, `links` (hard links: the driver answers `link(from, to)`, reports `nlink` in its attributes, and gives every attribute a stable `ino`, the same for all names of a file; a mount whose driver offers `links` but answers an attribute without `ino` loses them: `link` is `EPERM` there and `st_nlink` is 1), `chmod`, `owners`, `ranges`, `maxIo` (the largest read or write per message), `maxFile`, `listingStats`, `sessions`, `linkTimes`, and `attrTtl`/`entryTtl` (in ms, default 1000). `sessions` promises that a handle opened for writing keeps its writes to itself until it is released, so the kernel keeps one open per file being written instead of one per write (the `fsa` driver has it). `linkTimes` promises that `setattr` with `mtime` on a symlink sets the link's own time. Without it, setting a link's own times (`touch -h`, `utimensat` with `AT_SYMLINK_NOFOLLOW`) fails with `EOPNOTSUPP` and never reaches the target. tmpfs has it; hostfs does not. Setting times through a link follows it in the kernel, so `setattr` gets the target. `ranges` (driver protocol 1.1) promises that a write lands in the file at once, with no copy of the whole file on `open` or `release`, and that `setattr` takes `size`. `owners` (driver protocol 1.2) promises that the driver stores owners: its attributes carry `uid` and `gid`, `open` with `create`, `mkdir` and `symlink` may carry the new path's `uid`, `gid` and `mode` (the `owner` of the open flags, the second or third handler argument), a new path without them takes its directory's owner, and `setattr` takes `uid` and `gid`. tmpfs has it.
|
|
536
536
|
- **Caching:** the kernel caches attributes and listings for those TTLs, and a driver can push `invalidate` for changes made outside.
|
|
537
537
|
- **File contents:** on a mount with `ranges`, as `tmpfs` is, and on the root file system when it is in OPFS (in a browser with sync access handles) or an in-memory root, a program reads and writes a file in 64 KiB pages: reads come from the driver 1 MiB at a time, and changed pages go back in 1 MiB runs once 8 MiB have changed, at `fsync`, and when the file is closed (from the kernel's own descriptors also 250 ms after a write). A file of any size the mount can hold works, with memory to spare for about one more copy of it. In OPFS a write opens a sync access handle for just that write. An `fsa` mount (a picked folder) has no sync access handles, and Chrome copies the whole file into a swap file for every writable stream, so it writes through one stream per open file: the first write back opens it, later runs go into it at their offsets, and it is committed, one copy of the file, when the last process writing the file closes it (or exits), before another process reads what was written, and before a rename, unlink or unmount. Until then other processes see the new size but read the old bytes outside what was written. Elsewhere (`hostfs` with an older proxy, and a root outside OPFS), file contents are read whole when a program opens a file and written back when it closes it. Before a program's next call into the kernel (any wait, read of a pipe or terminal, sleep or spawn), its changed pages also go back, so other processes and the embedder see a write as soon as the writer does anything but compute: a daemon's pidfile, a log another process follows. On a whole-file mount that holds only for files of up to 64 KiB; a larger one goes back at close, fsync and exit, as before. Beyond that, both give close-to-open consistency, as NFS has. A process that ends without closing its files keeps what it wrote: on exit, on a trap, and when a signal kills it. For a killed WASI program, the kernel lets its worker write its changed pages back before `waitpid` reports the death. The only exception is a worker that makes no call into the kernel for 2 seconds after the signal; it is stopped without that write-back.
|
|
538
|
-
- **One file, several processes** ([#665](https://github.com/ai-ecoverse/slicc-kernel/issues/665)): a process that keeps a file open sees what another process writes to it. Every change the kernel applies to a file (a write back, a truncate, an append, a rename or a removal) bumps a change ring the kernel shares with all its processes. Before each read, write or seek to the end, a process's open file checks the ring (one atomic load) and, if its file changed, drops its clean pages and reads the size again, keeping its own unwritten changes. A write by one process is visible to another one's next read once it has gone back to the kernel: at the latest at the writer's next `fsync`, close, exit or call into the kernel (see above), so sqlite, which `fsync`s every commit, sees another connection's commit at its next transaction. A write through a file the kernel holds (a JS program's `ctx.open`, a WASI program's file after a fork or spawn) is in the kernel already, so other processes see it at their next read, before it is written back. A file the kernel itself holds open (a JS program's file, a WASI program's file after a fork or spawn) is the one copy: reads, writes, truncates and appends by path from other processes go to it, and their writes go through to storage at once, so a commit is in storage (and in `kernel.readFile`) when the committing process returns from `fsync`. `O_APPEND` writes go to the kernel at once, and the kernel picks the offset, so appenders in several processes keep every line. A file system without ranged I/O (`hostfs` with an older proxy, a root outside OPFS) rereads a file another process changed, but writes it back whole, so two writers there still need locks.
|
|
539
|
-
- **Locks between processes** ([#665](https://github.com/ai-ecoverse/slicc-kernel/issues/665)): an Emscripten program's `fcntl` byte-range locks (`F_SETLK`, `F_SETLKW`, `F_GETLK`) are the kernel's, so sqlite3 connections in different processes exclude each other: a second writer gets `database is locked` while the first holds its transaction, or waits with a busy timeout. The kernel keeps one table keyed by file identity (device and inode, so every name of a hard link is the same file), with POSIX semantics: locks belong to the process (an `exec`'d program keeps them under its pid; a forked child doesn't get them), a conflicting `F_SETLK` fails with `EAGAIN`, `F_GETLK` reports the holder's pid and range, `F_SETLKW` waits in the kernel without holding up anything else and a signal ends the wait with `EINTR`, a wait that would close a cycle between waiting processes fails with `EDEADLK`, a write lock on a descriptor not open for writing (or a read lock on one not open for reading) is `EBADF`, closing any descriptor of the file drops the process's locks on it, and exit releases them all. Before an unlock, the process writes back what it changed, so the next holder reads it. An open file's directory entry is checked against the change ring too: a name another process removed or replaced is looked up again, so sqlite's check for a hot journal sees the other connection's commit. A WASI imports module's `tryLock`/`unlock` keeps its behaviour (whole file, per process, keyed by path). Limits: locks are per kernel, not across tabs ([#675](https://github.com/ai-ecoverse/slicc-kernel/issues/675)); and a WASIX program's `fcntl` locks (python's `sqlite3`) are no-ops in its libc that never reach the kernel, until wasix-libc gets a kernel import for them ([homescoop#407](https://github.com/ai-ecoverse/homescoop/issues/407)), so python's sqlite doesn't exclude other processes yet.
|
|
538
|
+
- **One file, several processes** ([#665](https://github.com/ai-ecoverse/slicc-kernel/issues/665)): a process that keeps a file open sees what another process writes to it. Every change the kernel applies to a file (a write back, a truncate, an append, a rename or a removal) bumps a change ring the kernel shares with all its processes. Before each read, write or seek to the end, a process's open file checks the ring (one atomic load) and, if its file changed, drops its clean pages and reads the size again, keeping its own unwritten changes. A write by one process is visible to another one's next read once it has gone back to the kernel: at the latest at the writer's next `fsync`, close, exit or call into the kernel (see above), so sqlite, which `fsync`s every commit, sees another connection's commit at its next transaction. A write through a file the kernel holds (a JS program's `ctx.open`, a WASI program's file after a fork or spawn) is in the kernel already, so other processes see it at their next read, before it is written back. A file the kernel itself holds open (a JS program's file, a WASI program's file after a fork or spawn) is the one copy: reads, writes, truncates and appends by path from other processes go to it, and their writes go through to storage at once, so a commit is in storage (and in `kernel.readFile`) when the committing process returns from `fsync`. `O_APPEND` writes go to the kernel at once, and the kernel picks the offset, so appenders in several processes keep every line. A file system without ranged I/O (`hostfs` with an older proxy, a root outside OPFS) rereads a file another process changed, but writes it back whole, so two writers there still need locks. The same file under two names is one file too ([#669](https://github.com/ai-ecoverse/slicc-kernel/issues/669)): a write by path through a symlink reaches the file the kernel holds under its real path (resolved without following links in a `sudo` process's no-links view, so an untrusted link is still refused), a WASI program's open file also watches its real path (resolved once, the first time another file changes), and an open file with more than one hard link checks its size, version and mtime when anything changed, so a write through another name is seen. Limits for now: an alias on a mount that has no `realpath` is still only the name it was opened by; another tab's writes reach this tab's processes only as far as the root's change notifications (OPFS's `BroadcastChannel` watch) carry them.
|
|
539
|
+
- **Locks between processes** ([#665](https://github.com/ai-ecoverse/slicc-kernel/issues/665)): an Emscripten program's `fcntl` byte-range locks (`F_SETLK`, `F_SETLKW`, `F_GETLK`) are the kernel's, so sqlite3 connections in different processes exclude each other: a second writer gets `database is locked` while the first holds its transaction, or waits with a busy timeout. The kernel keeps one table keyed by file identity (device and inode, so every name of a hard link is the same file), with POSIX semantics: locks belong to the process (an `exec`'d program keeps them under its pid; a forked child doesn't get them), a conflicting `F_SETLK` fails with `EAGAIN`, `F_GETLK` reports the holder's pid and range, `F_SETLKW` waits in the kernel without holding up anything else and a signal ends the wait with `EINTR`, a wait that would close a cycle between waiting processes fails with `EDEADLK`, a write lock on a descriptor not open for writing (or a read lock on one not open for reading) is `EBADF`, closing any descriptor of the file drops the process's locks on it, and exit releases them all. Before an unlock, the process writes back what it changed, so the next holder reads it. An open file's directory entry is checked against the change ring too: a name another process removed or replaced is looked up again, so sqlite's check for a hot journal sees the other connection's commit. A WASI imports module's `tryLock`/`unlock` keeps its behaviour (whole file, per process, keyed by path). Limits: locks are per kernel, not across tabs ([#675](https://github.com/ai-ecoverse/slicc-kernel/issues/675)); and a WASIX program's `fcntl` locks (python's `sqlite3`) are no-ops in its libc that never reach the kernel, until wasix-libc gets a kernel import for them ([homescoop#407](https://github.com/ai-ecoverse/homescoop/issues/407)), so python's sqlite doesn't exclude other processes yet. An Emscripten program's `flock()` is a stub compiled into its libc that never reaches the kernel either, until a homescoop shim routes it to a kernel import ([#680](https://github.com/ai-ecoverse/slicc-kernel/issues/680)). A lock belongs to the file its descriptor was opened on: a file renamed over the name later, or the name's removal, doesn't move it.
|
|
540
540
|
- **Rules on a mount:**
|
|
541
541
|
- a rename across mounts is `EXDEV`, so `mv` copies;
|
|
542
542
|
- a symlink on the root file system that leads onto a mount, such as `~/proj` → `/mnt/drive/proj`, or a chain from a mount to the root and back, is followed onto the mount by every operation, in every runtime: reads, writes, `stat`, listings, `mkdir`, removals, renames, `chmod`, `chown` and times. Removing the link itself removes the link. Before [#547](https://github.com/ai-ecoverse/slicc-kernel/issues/547) was fixed, the kernel resolved such a link against the directory under the mount point for JS and WASI programs, the embedder's `fs` and attached clients (Emscripten programs weren't affected): reads failed with `ENOENT`, and writes went into that hidden directory, which shows again once the mount is gone;
|
package/dist/kernel-worker.js
CHANGED
|
@@ -8843,9 +8843,19 @@ function touches(watcher, changed) {
|
|
|
8843
8843
|
(watched) => changed === watched || under3(watched, changed) || (watcher.recursive ? under3(changed, watched) : parentOf6(changed) === watched)
|
|
8844
8844
|
);
|
|
8845
8845
|
}
|
|
8846
|
+
var REALS_KEPT = 4096;
|
|
8846
8847
|
var FsWatchers = class {
|
|
8847
8848
|
watchers = /* @__PURE__ */ new Set();
|
|
8848
8849
|
taps = [];
|
|
8850
|
+
reals = /* @__PURE__ */ new Map();
|
|
8851
|
+
reshapes = [];
|
|
8852
|
+
onReshape(listener) {
|
|
8853
|
+
this.reshapes.push(listener);
|
|
8854
|
+
}
|
|
8855
|
+
reshaped() {
|
|
8856
|
+
this.reals.clear();
|
|
8857
|
+
for (const listener of this.reshapes) listener();
|
|
8858
|
+
}
|
|
8849
8859
|
tap(listener) {
|
|
8850
8860
|
this.taps.push(listener);
|
|
8851
8861
|
}
|
|
@@ -8889,8 +8899,41 @@ var FsWatchers = class {
|
|
|
8889
8899
|
this.changed(...changed(...args));
|
|
8890
8900
|
}
|
|
8891
8901
|
};
|
|
8902
|
+
const reals = this.reals;
|
|
8903
|
+
const realOf = async (path) => {
|
|
8904
|
+
const at = normalizePath(path);
|
|
8905
|
+
const known = reals.get(at);
|
|
8906
|
+
if (known !== void 0) return known;
|
|
8907
|
+
if (!fs.realpath) return at;
|
|
8908
|
+
let real;
|
|
8909
|
+
try {
|
|
8910
|
+
real = await fs.realpath(at, true);
|
|
8911
|
+
} catch {
|
|
8912
|
+
return at;
|
|
8913
|
+
}
|
|
8914
|
+
if (reals.size >= REALS_KEPT) reals.clear();
|
|
8915
|
+
reals.set(at, real);
|
|
8916
|
+
return real;
|
|
8917
|
+
};
|
|
8918
|
+
const content = (run2) => async (...args) => {
|
|
8919
|
+
try {
|
|
8920
|
+
await run2(...args);
|
|
8921
|
+
} finally {
|
|
8922
|
+
const at = normalizePath(args[0]);
|
|
8923
|
+
const real = await realOf(at);
|
|
8924
|
+
this.changed(...real === at ? [at] : [at, real]);
|
|
8925
|
+
}
|
|
8926
|
+
};
|
|
8927
|
+
const reshaping = (run2) => async (...args) => {
|
|
8928
|
+
this.reshaped();
|
|
8929
|
+
try {
|
|
8930
|
+
await run2(...args);
|
|
8931
|
+
} finally {
|
|
8932
|
+
this.reshaped();
|
|
8933
|
+
}
|
|
8934
|
+
};
|
|
8892
8935
|
const overrides = {
|
|
8893
|
-
writeFile:
|
|
8936
|
+
writeFile: content(fs.writeFile.bind(fs)),
|
|
8894
8937
|
mkdir: async (path, options) => {
|
|
8895
8938
|
const created3 = [];
|
|
8896
8939
|
if (options?.recursive) {
|
|
@@ -8906,13 +8949,15 @@ var FsWatchers = class {
|
|
|
8906
8949
|
this.changed(...created3, path);
|
|
8907
8950
|
}
|
|
8908
8951
|
},
|
|
8909
|
-
rm: after(fs.rm.bind(fs), (path) => [path]),
|
|
8910
|
-
rename: after(fs.rename.bind(fs), (from, to) => [from, to]),
|
|
8911
|
-
link:
|
|
8912
|
-
(
|
|
8913
|
-
|
|
8952
|
+
rm: reshaping(after(fs.rm.bind(fs), (path) => [path])),
|
|
8953
|
+
rename: reshaping(after(fs.rename.bind(fs), (from, to) => [from, to])),
|
|
8954
|
+
link: reshaping(
|
|
8955
|
+
after(
|
|
8956
|
+
(from, to, follow2) => fs.link(from, to, follow2),
|
|
8957
|
+
(_from, to) => [to]
|
|
8958
|
+
)
|
|
8914
8959
|
),
|
|
8915
|
-
symlink: after(fs.symlink.bind(fs), (_target, path) => [path]),
|
|
8960
|
+
symlink: reshaping(after(fs.symlink.bind(fs), (_target, path) => [path])),
|
|
8916
8961
|
chmod: after(fs.chmod.bind(fs), (path) => [path]),
|
|
8917
8962
|
...fs.chown ? { chown: after(fs.chown.bind(fs), (path) => [path]) } : {},
|
|
8918
8963
|
utimes: after(fs.utimes.bind(fs), (path) => [path]),
|
|
@@ -8923,8 +8968,8 @@ var FsWatchers = class {
|
|
|
8923
8968
|
};
|
|
8924
8969
|
const ranged2 = rangedOps(fs);
|
|
8925
8970
|
if (ranged2) {
|
|
8926
|
-
overrides.pwrite =
|
|
8927
|
-
overrides.truncate =
|
|
8971
|
+
overrides.pwrite = content(ranged2.pwrite);
|
|
8972
|
+
overrides.truncate = content(ranged2.truncate);
|
|
8928
8973
|
}
|
|
8929
8974
|
return new Proxy(fs, {
|
|
8930
8975
|
get(target, key, receiver) {
|
|
@@ -9692,6 +9737,7 @@ var MountTable = class {
|
|
|
9692
9737
|
};
|
|
9693
9738
|
mounted = mount;
|
|
9694
9739
|
this.mounts.set(target, mount);
|
|
9740
|
+
this.deps.reshaped?.();
|
|
9695
9741
|
this.notes.delete(target);
|
|
9696
9742
|
return this.list().find((m) => m.target === target);
|
|
9697
9743
|
}
|
|
@@ -9706,6 +9752,7 @@ var MountTable = class {
|
|
|
9706
9752
|
if (!detach && this.deps.busy(at)) throw errnoError("EBUSY", `${at} has open files`);
|
|
9707
9753
|
this.notes.delete(at);
|
|
9708
9754
|
this.mounts.delete(at);
|
|
9755
|
+
this.deps.reshaped?.();
|
|
9709
9756
|
const waits = [...this.sessions].filter(([, s]) => s.mount === mount).map(([real]) => this.commit(real));
|
|
9710
9757
|
for (const c of this.committing.values()) if (c.mount === mount) waits.push(c.done);
|
|
9711
9758
|
const close = () => {
|
|
@@ -12035,7 +12082,7 @@ var PATH_OPS = new Set(SYNC_FS_OPS);
|
|
|
12035
12082
|
var EARLY_WHOLE_FILE = 64 * 1024;
|
|
12036
12083
|
|
|
12037
12084
|
// src/version.ts
|
|
12038
|
-
var KERNEL_VERSION = true ? "1.98.
|
|
12085
|
+
var KERNEL_VERSION = true ? "1.98.3" : "0.0.0-dev";
|
|
12039
12086
|
var OS_RELEASE = "6.0.0-slicc";
|
|
12040
12087
|
function procVersion() {
|
|
12041
12088
|
return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
|
|
@@ -14899,6 +14946,7 @@ var VfsNodes = class {
|
|
|
14899
14946
|
if (!this.byPath.has(to)) this.byPath.set(to, node);
|
|
14900
14947
|
}
|
|
14901
14948
|
know(node, id2) {
|
|
14949
|
+
this.reshaped();
|
|
14902
14950
|
node.id = id2;
|
|
14903
14951
|
if (!this.byId.has(id2)) this.byId.set(id2, node);
|
|
14904
14952
|
}
|
|
@@ -14908,6 +14956,7 @@ var VfsNodes = class {
|
|
|
14908
14956
|
return out;
|
|
14909
14957
|
}
|
|
14910
14958
|
forget(node) {
|
|
14959
|
+
this.reshaped();
|
|
14911
14960
|
for (const p of this.namesOf(node)) this.byPath.delete(p);
|
|
14912
14961
|
if (node.id !== void 0 && this.byId.get(node.id) === node) this.byId.delete(node.id);
|
|
14913
14962
|
}
|
|
@@ -14922,6 +14971,38 @@ var VfsNodes = class {
|
|
|
14922
14971
|
if (node.path === path) node.renamedTo(others[0]);
|
|
14923
14972
|
return true;
|
|
14924
14973
|
}
|
|
14974
|
+
holdsAny() {
|
|
14975
|
+
return this.byPath.size > 0;
|
|
14976
|
+
}
|
|
14977
|
+
epoch = 0;
|
|
14978
|
+
plain = /* @__PURE__ */ new Map();
|
|
14979
|
+
reshaped() {
|
|
14980
|
+
this.epoch++;
|
|
14981
|
+
this.plain.clear();
|
|
14982
|
+
}
|
|
14983
|
+
plainAt(path) {
|
|
14984
|
+
return this.plain.get(path) === this.epoch;
|
|
14985
|
+
}
|
|
14986
|
+
get shape() {
|
|
14987
|
+
return this.epoch;
|
|
14988
|
+
}
|
|
14989
|
+
notePlain(path, shape) {
|
|
14990
|
+
if (shape !== this.epoch) return;
|
|
14991
|
+
if (this.plain.size >= 4096) this.plain.clear();
|
|
14992
|
+
this.plain.set(path, shape);
|
|
14993
|
+
}
|
|
14994
|
+
holdsName(name2) {
|
|
14995
|
+
for (const path of this.byPath.keys())
|
|
14996
|
+
if (path.slice(path.lastIndexOf("/") + 1) === name2) return true;
|
|
14997
|
+
return false;
|
|
14998
|
+
}
|
|
14999
|
+
holdsLinked() {
|
|
15000
|
+
return this.byId.size > 0;
|
|
15001
|
+
}
|
|
15002
|
+
holdingId(id2) {
|
|
15003
|
+
const node = this.byId.get(id2);
|
|
15004
|
+
return node && !node.orphaned ? node : void 0;
|
|
15005
|
+
}
|
|
14925
15006
|
holding(path) {
|
|
14926
15007
|
if (this.byPath.size === 0) return void 0;
|
|
14927
15008
|
const node = this.byPath.get(path);
|
|
@@ -14933,6 +15014,7 @@ var VfsNodes = class {
|
|
|
14933
15014
|
return node;
|
|
14934
15015
|
}
|
|
14935
15016
|
create(path, id2, node) {
|
|
15017
|
+
this.reshaped();
|
|
14936
15018
|
node.onDirty = () => this.changed?.(node.path);
|
|
14937
15019
|
this.byPath.set(path, node);
|
|
14938
15020
|
if (id2 !== void 0) this.know(node, id2);
|
|
@@ -15074,11 +15156,35 @@ async function refuseReadonly(fs, path, flags2, opts = {}) {
|
|
|
15074
15156
|
const st = own ?? await stat(await targetDir(fs, path));
|
|
15075
15157
|
if (st?.readonly) throw new KernelError("EROFS");
|
|
15076
15158
|
}
|
|
15159
|
+
var baseName = (path) => path.slice(path.lastIndexOf("/") + 1);
|
|
15160
|
+
async function byRealPath(fs, nodes, at) {
|
|
15161
|
+
const real = await fs.realpath?.(at, true).catch(() => void 0);
|
|
15162
|
+
return real === void 0 || real === at ? void 0 : nodes.holding(real);
|
|
15163
|
+
}
|
|
15164
|
+
async function aliasOf(fs, nodes, at) {
|
|
15165
|
+
if (nodes.holdsName(baseName(at))) {
|
|
15166
|
+
const named = await byRealPath(fs, nodes, at);
|
|
15167
|
+
if (named) return named;
|
|
15168
|
+
}
|
|
15169
|
+
const st = await fs.lstat(at).catch(() => void 0);
|
|
15170
|
+
if (!st) return void 0;
|
|
15171
|
+
if (st.isSymbolicLink) return byRealPath(fs, nodes, at);
|
|
15172
|
+
if (st.ino === void 0 || (st.nlink ?? 1) < 2 || !nodes.holdsLinked()) return void 0;
|
|
15173
|
+
return nodes.holdingId(`${st.dev ?? 0}:${st.ino}`);
|
|
15174
|
+
}
|
|
15175
|
+
async function heldAt(fs, nodes, at) {
|
|
15176
|
+
const held = nodes.holding(at);
|
|
15177
|
+
if (held || !nodes.holdsAny() || nodes.plainAt(at)) return held;
|
|
15178
|
+
const shape = nodes.shape;
|
|
15179
|
+
const found = await aliasOf(fs, nodes, at);
|
|
15180
|
+
if (!found) nodes.notePlain(at, shape);
|
|
15181
|
+
return found;
|
|
15182
|
+
}
|
|
15077
15183
|
function heldIo(fs, nodes, changed) {
|
|
15078
15184
|
const ranged2 = rangedOps(fs);
|
|
15079
|
-
const node = (path) =>
|
|
15185
|
+
const node = (path) => heldAt(fs, nodes, fs.resolvePath("/", path));
|
|
15080
15186
|
const write = async (path, op, fallback) => {
|
|
15081
|
-
const held = node(path);
|
|
15187
|
+
const held = await node(path);
|
|
15082
15188
|
if (!held) return fallback();
|
|
15083
15189
|
const out = await held.serial(async () => {
|
|
15084
15190
|
const done4 = await op(held);
|
|
@@ -15086,6 +15192,7 @@ function heldIo(fs, nodes, changed) {
|
|
|
15086
15192
|
return done4;
|
|
15087
15193
|
});
|
|
15088
15194
|
changed(fs.resolvePath("/", path));
|
|
15195
|
+
if (held.path !== fs.resolvePath("/", path)) changed(held.path);
|
|
15089
15196
|
return out;
|
|
15090
15197
|
};
|
|
15091
15198
|
return {
|
|
@@ -15110,17 +15217,17 @@ function heldIo(fs, nodes, changed) {
|
|
|
15110
15217
|
),
|
|
15111
15218
|
async stat(path) {
|
|
15112
15219
|
const st = await fs.stat(path);
|
|
15113
|
-
const held = st.isFile ? node(path) : void 0;
|
|
15220
|
+
const held = st.isFile ? await node(path) : void 0;
|
|
15114
15221
|
return held ? { ...st, size: await held.serial(() => held.size()) } : st;
|
|
15115
15222
|
},
|
|
15116
15223
|
async readFileBuffer(path) {
|
|
15117
|
-
const held = node(path);
|
|
15224
|
+
const held = await node(path);
|
|
15118
15225
|
if (!held) return fs.readFileBuffer(path);
|
|
15119
15226
|
return held.serial(async () => held.pread(await held.size(), 0));
|
|
15120
15227
|
},
|
|
15121
15228
|
...ranged2 ? {
|
|
15122
|
-
pread: (path, at, n, version) => {
|
|
15123
|
-
const held = node(path);
|
|
15229
|
+
pread: async (path, at, n, version) => {
|
|
15230
|
+
const held = await node(path);
|
|
15124
15231
|
return held ? held.serial(() => held.pread(n, at)) : ranged2.pread(path, at, n, version);
|
|
15125
15232
|
},
|
|
15126
15233
|
pwrite: (path, at, bytes, transfer) => write(
|
|
@@ -15168,9 +15275,9 @@ function keepingOpen(fs, nodes, changed = () => {
|
|
|
15168
15275
|
nodes.renamed(a, b);
|
|
15169
15276
|
});
|
|
15170
15277
|
},
|
|
15171
|
-
writeFile: (path, content, owner) => {
|
|
15278
|
+
writeFile: async (path, content, owner) => {
|
|
15172
15279
|
const at = fs.resolvePath("/", path);
|
|
15173
|
-
const held = nodes
|
|
15280
|
+
const held = await heldAt(fs, nodes, at);
|
|
15174
15281
|
if (!held) return nodes.onEntry(at, () => fs.writeFile(path, content, owner));
|
|
15175
15282
|
const bytes = typeof content === "string" ? new TextEncoder().encode(content) : content;
|
|
15176
15283
|
return held.serial(async () => {
|
|
@@ -15178,6 +15285,7 @@ function keepingOpen(fs, nodes, changed = () => {
|
|
|
15178
15285
|
await held.pwrite(bytes, 0);
|
|
15179
15286
|
await held.flush();
|
|
15180
15287
|
changed(at);
|
|
15288
|
+
if (held.path !== at) changed(held.path);
|
|
15181
15289
|
});
|
|
15182
15290
|
},
|
|
15183
15291
|
createExclusive: async (path, owner) => nodes.createExclusive(await nodes.entryKey(fs.resolvePath("/", path)), owner),
|
|
@@ -15413,6 +15521,11 @@ function clipboardLine(text) {
|
|
|
15413
15521
|
}
|
|
15414
15522
|
return out.length > 200 ? `${out.slice(0, 200)}\u2026` : out;
|
|
15415
15523
|
}
|
|
15524
|
+
var nextOpenKey = 1;
|
|
15525
|
+
var fileKeys = /* @__PURE__ */ new WeakMap();
|
|
15526
|
+
function identityKey(st, path) {
|
|
15527
|
+
return st.ino === void 0 ? path : `${st.dev ?? 0}:${st.ino}`;
|
|
15528
|
+
}
|
|
15416
15529
|
var DYING = { ok: false, errno: "EINTR", message: "the process is being killed" };
|
|
15417
15530
|
var HOSTS_ONLY = new Resolver({ routes: new Routes() });
|
|
15418
15531
|
var WasmProcess = class {
|
|
@@ -15658,10 +15771,11 @@ var WasmProcess = class {
|
|
|
15658
15771
|
this.lockOwners.add(owner);
|
|
15659
15772
|
return owner;
|
|
15660
15773
|
}
|
|
15774
|
+
pathKeys = /* @__PURE__ */ new Map();
|
|
15661
15775
|
async lockPath(target, want) {
|
|
15662
15776
|
if (target.fd !== void 0) {
|
|
15663
15777
|
const st = await this.fds.get(target.fd).file.stat?.();
|
|
15664
|
-
if (!st
|
|
15778
|
+
if (!st) throw new KernelError("EBADF");
|
|
15665
15779
|
return st.path;
|
|
15666
15780
|
}
|
|
15667
15781
|
if (target.path === void 0) throw new KernelError("EINVAL");
|
|
@@ -15676,11 +15790,22 @@ var WasmProcess = class {
|
|
|
15676
15790
|
return path;
|
|
15677
15791
|
}
|
|
15678
15792
|
async lockKey(target, kind) {
|
|
15793
|
+
const file = target.fd === void 0 ? void 0 : this.fds.get(target.fd);
|
|
15794
|
+
const known = file ? fileKeys.get(file) : this.pathKeys.get(target.path ?? "");
|
|
15795
|
+
if (kind === "close" && !file && target.path !== void 0) this.pathKeys.delete(target.path);
|
|
15679
15796
|
const want = kind === "read" ? R_OK : kind === "write" ? W_OK : 0;
|
|
15680
15797
|
const path = await this.lockPath(target, want);
|
|
15681
|
-
|
|
15682
|
-
|
|
15683
|
-
|
|
15798
|
+
if (known !== void 0) return known;
|
|
15799
|
+
const st = await this.options.fs.stat?.(path).catch((err) => {
|
|
15800
|
+
if (!file) throw err;
|
|
15801
|
+
return void 0;
|
|
15802
|
+
});
|
|
15803
|
+
if (!st && !file) throw new KernelError("ENOENT");
|
|
15804
|
+
const key = st ? identityKey(st, path) : `open:${nextOpenKey++}`;
|
|
15805
|
+
if (kind === "close") return key;
|
|
15806
|
+
if (file) fileKeys.set(file, key);
|
|
15807
|
+
else if (this.pathKeys.size < 4096) this.pathKeys.set(path, key);
|
|
15808
|
+
return key;
|
|
15684
15809
|
}
|
|
15685
15810
|
async dropRecordLocks(fd) {
|
|
15686
15811
|
const { locks } = this.options;
|
|
@@ -20220,7 +20345,7 @@ function usersMode(mode = "off") {
|
|
|
20220
20345
|
function tagOption(tag) {
|
|
20221
20346
|
if (tag !== void 0 && typeof tag !== "string") throw new TypeError("a tag must be a string");
|
|
20222
20347
|
}
|
|
20223
|
-
function
|
|
20348
|
+
function baseName2(path) {
|
|
20224
20349
|
return path.slice(path.lastIndexOf("/") + 1);
|
|
20225
20350
|
}
|
|
20226
20351
|
function packageRootOf(path, roots) {
|
|
@@ -20263,7 +20388,7 @@ function appendedRoots(given) {
|
|
|
20263
20388
|
}
|
|
20264
20389
|
function plainEnv(words2) {
|
|
20265
20390
|
const next2 = words2[1];
|
|
20266
|
-
if (
|
|
20391
|
+
if (baseName2(words2[0] ?? "") !== "env" || next2 === void 0) return false;
|
|
20267
20392
|
return next2 === "-S" || !next2.startsWith("-") && !next2.includes("=");
|
|
20268
20393
|
}
|
|
20269
20394
|
function shebangCommand(words2) {
|
|
@@ -20279,9 +20404,9 @@ function shebangCommand(words2) {
|
|
|
20279
20404
|
function interpreter(head) {
|
|
20280
20405
|
const words2 = shebang(head);
|
|
20281
20406
|
if (!words2) return "node";
|
|
20282
|
-
if (
|
|
20407
|
+
if (baseName2(words2[0]) === "env") words2.shift();
|
|
20283
20408
|
if (words2[0] === "-S") words2.shift();
|
|
20284
|
-
return
|
|
20409
|
+
return baseName2(words2[0] ?? "");
|
|
20285
20410
|
}
|
|
20286
20411
|
function expandDefaults(defaults, env) {
|
|
20287
20412
|
const out = {};
|
|
@@ -20455,6 +20580,9 @@ var Launcher = class {
|
|
|
20455
20580
|
this.sealed.userOf = async (uid) => (await this.users.lookup(uid))?.name;
|
|
20456
20581
|
if (this.owners) options.fs.trackOwners?.();
|
|
20457
20582
|
this.watchers.tap((paths) => this.changes.publish(...paths));
|
|
20583
|
+
this.watchers.onReshape(() => {
|
|
20584
|
+
for (const nodes of this.openFiles) nodes.reshaped();
|
|
20585
|
+
});
|
|
20458
20586
|
this.mounts = new MountTable({
|
|
20459
20587
|
open: (type, spec) => this.driver(type, spec),
|
|
20460
20588
|
busy: (target) => [...this.openFiles].some((nodes) => nodes.holds(target)) || heldUnder(this.held, target),
|
|
@@ -20462,6 +20590,7 @@ var Launcher = class {
|
|
|
20462
20590
|
if (paths.some((path) => this.pnpmChain.has(path))) this.pnpmStale();
|
|
20463
20591
|
this.watchers.changed(...paths);
|
|
20464
20592
|
},
|
|
20593
|
+
reshaped: () => this.watchers.reshaped(),
|
|
20465
20594
|
owners: this.owners
|
|
20466
20595
|
});
|
|
20467
20596
|
this.mounted = this.mounts.wrap(options.fs);
|
|
@@ -20719,22 +20848,22 @@ var Launcher = class {
|
|
|
20719
20848
|
const head = await this.head(glue);
|
|
20720
20849
|
if (!head) return void 0;
|
|
20721
20850
|
if (isWasm(head)) {
|
|
20722
|
-
const owner2 = await this.wasmOwner(glue,
|
|
20851
|
+
const owner2 = await this.wasmOwner(glue, baseName2(file).replace(/\.wasm$/, ""), scope);
|
|
20723
20852
|
if (owner2) return { ...targetOf(owner2), ...asked };
|
|
20724
20853
|
return {
|
|
20725
20854
|
abi: "wasi",
|
|
20726
20855
|
glue,
|
|
20727
20856
|
wasm: glue,
|
|
20728
|
-
argv0:
|
|
20857
|
+
argv0: baseName2(argv0 || file).replace(/\.wasm$/, ""),
|
|
20729
20858
|
...asked
|
|
20730
20859
|
};
|
|
20731
20860
|
}
|
|
20732
20861
|
if (interpreter(head) !== "node") return void 0;
|
|
20733
20862
|
const wasm = modulePath(glue);
|
|
20734
20863
|
if (await this.base.exists(wasm)) {
|
|
20735
|
-
return { abi: "emscripten", glue, wasm, argv0:
|
|
20864
|
+
return { abi: "emscripten", glue, wasm, argv0: baseName2(argv0 || file), ...asked };
|
|
20736
20865
|
}
|
|
20737
|
-
const owner = await this.glueOwner(glue,
|
|
20866
|
+
const owner = await this.glueOwner(glue, baseName2(file), scope);
|
|
20738
20867
|
return owner && { ...targetOf(owner), ...asked };
|
|
20739
20868
|
}
|
|
20740
20869
|
async glueOwner(glue, name2, scope) {
|
|
@@ -20744,11 +20873,11 @@ var Launcher = class {
|
|
|
20744
20873
|
return owners.find((c) => c.name === name2) ?? owners[0];
|
|
20745
20874
|
}
|
|
20746
20875
|
async wasmOwner(wasm, name2, scope) {
|
|
20747
|
-
const leaf =
|
|
20876
|
+
const leaf = baseName2(wasm);
|
|
20748
20877
|
const real = await physicalPathAsync(this.base, wasm);
|
|
20749
20878
|
const owners = [];
|
|
20750
20879
|
for (const command of (await this.commands(scope)).values()) {
|
|
20751
|
-
if (command.abi !== "wasi" || command.script ||
|
|
20880
|
+
if (command.abi !== "wasi" || command.script || baseName2(command.wasm) !== leaf) continue;
|
|
20752
20881
|
if (command.wasm === wasm || await physicalPathAsync(this.base, command.wasm) === real)
|
|
20753
20882
|
owners.push(command);
|
|
20754
20883
|
}
|
|
@@ -21812,7 +21941,7 @@ var Launcher = class {
|
|
|
21812
21941
|
const path = normalizePath(given);
|
|
21813
21942
|
if (!await this.base.exists(path)) return void 0;
|
|
21814
21943
|
if (path === "/") return path;
|
|
21815
|
-
return joinName(await realDir(this.base, parentOf4(path)),
|
|
21944
|
+
return joinName(await realDir(this.base, parentOf4(path)), baseName2(path));
|
|
21816
21945
|
}
|
|
21817
21946
|
pnpmReady() {
|
|
21818
21947
|
if (this.pnpmMade !== void 0) return this.pnpmMade;
|