@ai-ecoverse/slicc-kernel 1.140.0 → 1.140.2
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 +92 -34
- package/dist/kernel-worker.js.map +3 -3
- package/dist/meta.js +5 -3
- package/dist/meta.js.map +2 -2
- package/dist/node-process-worker.js +86 -12
- package/dist/node-process-worker.js.map +3 -3
- package/dist/node.js +87 -31
- package/dist/node.js.map +3 -3
- package/dist/process-worker.js +86 -12
- package/dist/process-worker.js.map +3 -3
- package/dist/types/fs/meta.d.ts +3 -3
- 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-wire.d.ts +1 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -330,7 +330,7 @@ On top of preview1 a WASI program gets:
|
|
|
330
330
|
A kernel without the imports answers `ENOSYS`, and the libc reports `SI_USER` with pid and uid 0, as before. **SIGCHLD:** with `SIGCHLD` set to `SIG_IGN` (by the program, or inherited), or with `SA_NOCLDWAIT` in the flags `slicc.sigaction_set` gets for `SIGCHLD`, an exited child is reaped at once and never stays a zombie: `waitpid` for it is `ECHILD`, and `wait` blocks until every child has exited, then fails with `ECHILD`, as on Linux. `SIGCHLD` is still sent. An explicit `SIG_DFL` turns it back off. A child that a process waits on for `exec`, or that `kernel.run` and the run calls collect, is never reaped that way.
|
|
331
331
|
- **Pipes** have no file type for WASI and WASIX programs' `fstat` (`st_mode` type 0), because WASI has no FIFO file type. A socket type, as kernels 1.47.3 to 1.48.1 gave them, made Ruby treat a `popen` pipe as a socket (`close_write` then never ends the child's input). The kernel's `slicc_fs.fd_mode` answers `S_IFIFO | 0600` (Linux numbering) for a pipe, and wasix-sysroot 2025.9.30-21 and later take the type from it, so programs built on it see a FIFO (`S_ISFIFO`); older libcs keep only the permission bits. Until then Python's asyncio refuses a pipe for a subprocess (`Pipe transport is only for pipes, sockets and character devices`). They are read and written as pipes; socket calls on them fail with `ENOTSOCK`, and `isatty()` is false. Emscripten programs, bash included, see a FIFO. The same holds for any kernel descriptor that is neither seekable nor a terminal or device, such as captured output.
|
|
332
332
|
- **Sockets**: the WASIX socket calls (`sock_open`, `sock_pair` for an `AF_UNIX` stream or datagram pair, `bind`, `listen`, `connect`, `accept`, options, local and peer names) are the kernel's loopback sockets, and preview1's `sock_recv` / `sock_send` / `sock_shutdown` work on them and on inherited ones. Writing to a socket whose peer is gone fails with `EPIPE`, through `fd_write` as through `sock_send`, as if `MSG_NOSIGNAL` were set. A write to a pipe, FIFO or terminal whose reader is gone follows the program's `SIGPIPE` disposition, as the kernel knows it (#810): at the default it ends the program with status 141; ignored (also an ignore inherited across `exec` and spawn, as bash's `trap '' PIPE` passes on), the write fails with `EPIPE`; with a handler, the handler runs on the thread that wrote and then the write fails with `EPIPE` (#820); blocked, `SIGPIPE` stays pending and the write fails with `EPIPE`. An Emscripten program's write to a pipe, and its `send` without `MSG_NOSIGNAL`, follow the same rules, except that the kernel doesn't see an Emscripten program's signal mask, so a blocked `SIGPIPE` there still ends it. A WASIX program's libc reads an inherited ignore back through `slicc.sigaction_inherited` (wasix-sysroot -32 and later; see below). An Emscripten program's shim reads it from `Module.sliccKernel.sigInherited()` (#965, homescoop#973). That's a mask with bit n set for signal n (not bit n-1, as the WASIX import has it) holding only inherited `SIG_IGN`s, never `SIGKILL` or `SIGSTOP`. The shim seeds its `sigaction` table from it in a constructor that runs before any user constructor. The kernel puts the function on `Module.sliccKernel` before the glue runs constructors, ahead of the rest of `sliccKernel`. On a kernel without the function it is undefined, and the shim keeps the defaults. HTTP and HTTPS leave through the realm proxy that `https_proxy` names, as for any other program; `resolve` answers `/etc/hosts` names and literal addresses, and the [uplink](#uplink)'s names. `sock_open(SOCK_DGRAM)` on `AF_INET`/`AF_INET6` gives a [datagram socket](#datagram-sockets-udp); `sock_send_to` and `sock_recv_from` carry its addresses, and `RECV_DATA_TRUNCATED` is set in `ro_flags` when a datagram did not fit.
|
|
333
|
-
- **Diagnostics**: a trap ends the program with 134 and its message on stderr; a program that crashes its worker otherwise (a JS stack overflow, `RangeError: Maximum call stack size exceeded`, or any other uncaught error) ends with 70, and its message goes to the process's own fd 2 too, so `2>` catches it (#1237), or to the session's stderr when it has no fd 2 to write; with `SLICC_WASM_BACKTRACE=1` the wasm frames follow, named from the module's name section, or from a sidecar for a module shipped without one (`<module>.names` beside it, or the same path in a `<package>-names` package). `SLICC_WASI_STATS=1` makes a program print its calls, counted and timed, as it ends.
|
|
333
|
+
- **Diagnostics**: a trap ends the program with 134 and its message on stderr; a program that crashes its worker otherwise (a JS stack overflow, `RangeError: Maximum call stack size exceeded`, or any other uncaught error) ends with 70, and its message goes to the process's own fd 2 too, so `2>` catches it (#1237), or to the session's stderr when it has no fd 2 to write; with `SLICC_WASM_BACKTRACE=1` the wasm frames follow, named from the module's name section, or from a sidecar for a module shipped without one (`<module>.names` beside it, or the same path in a `<package>-names` package). A sidecar that doesn't fit the module, naming another number of functions than the module has (imported and defined), gets one warning line before the trap message, and the frames keep their raw indices instead of wrong names (#1239). `SLICC_WASI_STATS=1` makes a program print its calls, counted and timed, as it ends.
|
|
334
334
|
|
|
335
335
|
### Program imports
|
|
336
336
|
|
|
@@ -1121,7 +1121,7 @@ A character device (`0666`), there in every `users` mode, through which programs
|
|
|
1121
1121
|
|
|
1122
1122
|
### `/proc`
|
|
1123
1123
|
|
|
1124
|
-
Every process of the kernel has `/proc/<pid>/` with `cmdline`, `comm`, `stat`, `statm` and `status`, in the formats procps reads, and `exe`, a link to the file the process was started from: the glue or `.wasm` that ran, as resolved (a copy run by its own path names the copy, a symlink its target) (#1264). As on Linux without ptrace access, reading another process's `exe` link is `EACCES` unless the reader is root or its uid and gid match all of the target's real, effective and saved ids; `cmdline` and `status` stay readable, and `/proc/self` is a link to the `/proc/<pid>` of the process that reads it, as on Linux (`readlink /proc/self` is its pid, the one it was exec'd under included). They come from the kernel's process table when they are opened, so a terminal sees the processes of every client. A process that exits while a program reads the `/proc` directory doesn't fail or cut short the listing: its pid is still listed, and opening its files fails with `ENOENT`, which `ps`, `pgrep` and `pidof` skip, as on Linux; an exec'd program shows under the pid its parent knows. `/proc/uptime`, `/proc/loadavg`, `/proc/stat` and `/proc/meminfo` are there too, and `/proc/mounts`. `/proc/version` names the kernel, Linux-shaped, with the slicc-kernel version that built it: `Linux version 6.0.0-slicc (slicc-kernel <version>) #1 SMP PREEMPT_DYNAMIC`. `/proc/sys/kernel/osrelease` is `6.0.0-slicc` and `/proc/sys/kernel/ostype` is `Linux`. The version is the one npm publishes, set when the package is built (`0.0.0-dev` when run from the sources). A JS program reads it as `ctx.kernel.version`. A process with no parent in the kernel (one a page, a client or the Node entry started) has parent 1, as children of init do, in `getppid()`, `/proc/<pid>/stat` and `ps` alike, and so does a process whose parent has ended, waited for or not (`getppid()` asks the kernel each time; a shell's `$PPID` keeps the value it started with, as on Linux). Pids, parents, process groups, sessions, command lines, terminals, start and boot times are real, and so are the credentials in `status` (`Uid:`, `Gid:`, `Groups:`; a process with a tag, see `kernel.run`, also has `Tag:\t<tag>` after `Gid:`, shown to root and to the same uid only, with newlines, tabs and carriage returns written `\n`, `\t` and `\r`) and each process's memory: the size of its wasm memory (`VSZ` and `RSS` alike, as wasm has no paging), which `/proc/meminfo` counts as used. CPU times are measured per process, its threads and the images it exec'd included: time a worker spends waiting for the kernel's answer is system time (at most 20 ms of each call; the rest of a longer one, such as a write into a full pipe, counts as blocked), time it spends blocked (a read, a `poll` or `select` that waits, a sleep, `wait`, a futex, while stopped) is neither, from the moment the call actually starts to wait (a call that could wait but answers at once, such as a `select` with a zero timeout or a ready descriptor, a `waitpid` with `WNOHANG`, or a futex wait whose value has already changed, is system time), and the rest is user time; a process's times stop when it ends, a call still in flight then adds nothing, so a loop that never calls the kernel shows as user time while it runs; they show in `/proc/<pid>/stat`, `ps` and `ps()`, and the times of the children a process has waited for (theirs and their own waited-for children's, as POSIX counts them) show as its `cutime` and `cstime` in `/proc/<pid>/stat`; a stopped process is `T` there, in `/proc/<pid>/status` (`T (stopped)`) and in `ps()`; a WASI or WASIX program's `CLOCK_PROCESS_CPUTIME_ID` and `CLOCK_THREAD_CPUTIME_ID` (`clock_time_get`) count only the time its thread runs, its own waits and blocked kernel calls left out (a sleep is not CPU time), per thread for now, so a threaded program's process clock is its calling thread's; and `/proc/stat` counts every process of the kernel, the reaped ones included, and `/proc/loadavg` averages the processes not blocked, sampled every 5 seconds. A JS program's time isn't measured yet. `MemTotal` (from `navigator.deviceMemory`, else 4 GiB) is a placeholder. A process that isn't root sees only its own user's processes (see [Users](#users)). WASI and WASIX programs see the same `/proc`, read-only (opening a file in it for writing, or linking one of its files elsewhere, fails with `EROFS`), from every thread, also through a symlink that points into it (where every change, `mkdir`, `rename`, `unlink`, `chmod`, a hard link from it or into it, and so on, fails too: with `EROFS` when the root file system has a physical `/proc`, which stays hidden and untouched, and otherwise with the `ENOENT` of the missing directory, since that check is only paid for when a physical `/proc` exists; with one, a symlink whose target passes `..` after a file or a missing name, such as `/f/../proc`, fails with `ENOTDIR` or `ENOENT`, as on Linux), and `/` lists it, so a WASI program gets a preopen for it; `/proc/self` is the program's own pid there too (the pid an exec'd program took over). WASI and WASIX programs also have `/proc/<pid>/task/<tid>/stat` for each of a process's threads (the main thread is tid 1), whose state is `S` while the thread is blocked in the kernel (a `sem_wait`, a blocking read, a sleep, a futex) and `R` otherwise (`T` and `Z` as for the process). JS programs read the same `/proc` through `ctx.fs` and `ctx.open` (see [JS programs](#js-programs)). Neither has `/proc/<pid>/fd` yet.
|
|
1124
|
+
Every process of the kernel has `/proc/<pid>/` with `cmdline`, `comm`, `stat`, `statm` and `status`, in the formats procps reads, and `exe`, a link to the file the process was started from: the glue or `.wasm` that ran, as resolved (a copy run by its own path names the copy, a symlink its target) (#1264). For WASI and WASIX programs, as for Emscripten ones, `stat`, `open` and reading `/proc/self/exe` act on the program file, while `lstat` and `readlink` show the link (#1273). As on Linux without ptrace access, reading another process's `exe` link is `EACCES` unless the reader is root or its uid and gid match all of the target's real, effective and saved ids; `cmdline` and `status` stay readable, and `/proc/self` is a link to the `/proc/<pid>` of the process that reads it, as on Linux (`readlink /proc/self` is its pid, the one it was exec'd under included). They come from the kernel's process table when they are opened, so a terminal sees the processes of every client. A process that exits while a program reads the `/proc` directory doesn't fail or cut short the listing: its pid is still listed, and opening its files fails with `ENOENT`, which `ps`, `pgrep` and `pidof` skip, as on Linux; an exec'd program shows under the pid its parent knows. `/proc/uptime`, `/proc/loadavg`, `/proc/stat` and `/proc/meminfo` are there too, and `/proc/mounts`. `/proc/version` names the kernel, Linux-shaped, with the slicc-kernel version that built it: `Linux version 6.0.0-slicc (slicc-kernel <version>) #1 SMP PREEMPT_DYNAMIC`. `/proc/sys/kernel/osrelease` is `6.0.0-slicc` and `/proc/sys/kernel/ostype` is `Linux`. The version is the one npm publishes, set when the package is built (`0.0.0-dev` when run from the sources). A JS program reads it as `ctx.kernel.version`. A process with no parent in the kernel (one a page, a client or the Node entry started) has parent 1, as children of init do, in `getppid()`, `/proc/<pid>/stat` and `ps` alike, and so does a process whose parent has ended, waited for or not (`getppid()` asks the kernel each time; a shell's `$PPID` keeps the value it started with, as on Linux). Pids, parents, process groups, sessions, command lines, terminals, start and boot times are real, and so are the credentials in `status` (`Uid:`, `Gid:`, `Groups:`; a process with a tag, see `kernel.run`, also has `Tag:\t<tag>` after `Gid:`, shown to root and to the same uid only, with newlines, tabs and carriage returns written `\n`, `\t` and `\r`) and each process's memory: the size of its wasm memory (`VSZ` and `RSS` alike, as wasm has no paging), which `/proc/meminfo` counts as used. CPU times are measured per process, its threads and the images it exec'd included: time a worker spends waiting for the kernel's answer is system time (at most 20 ms of each call; the rest of a longer one, such as a write into a full pipe, counts as blocked), time it spends blocked (a read, a `poll` or `select` that waits, a sleep, `wait`, a futex, while stopped) is neither, from the moment the call actually starts to wait (a call that could wait but answers at once, such as a `select` with a zero timeout or a ready descriptor, a `waitpid` with `WNOHANG`, or a futex wait whose value has already changed, is system time), and the rest is user time; a process's times stop when it ends, a call still in flight then adds nothing, so a loop that never calls the kernel shows as user time while it runs; they show in `/proc/<pid>/stat`, `ps` and `ps()`, and the times of the children a process has waited for (theirs and their own waited-for children's, as POSIX counts them) show as its `cutime` and `cstime` in `/proc/<pid>/stat`; a stopped process is `T` there, in `/proc/<pid>/status` (`T (stopped)`) and in `ps()`; a WASI or WASIX program's `CLOCK_PROCESS_CPUTIME_ID` and `CLOCK_THREAD_CPUTIME_ID` (`clock_time_get`) count only the time its thread runs, its own waits and blocked kernel calls left out (a sleep is not CPU time), per thread for now, so a threaded program's process clock is its calling thread's; and `/proc/stat` counts every process of the kernel, the reaped ones included, and `/proc/loadavg` averages the processes not blocked, sampled every 5 seconds. A JS program's time isn't measured yet. `MemTotal` (from `navigator.deviceMemory`, else 4 GiB) is a placeholder. A process that isn't root sees only its own user's processes (see [Users](#users)). WASI and WASIX programs see the same `/proc`, read-only (opening a file in it for writing, or linking one of its files elsewhere, fails with `EROFS`), from every thread, also through a symlink that points into it (where every change, `mkdir`, `rename`, `unlink`, `chmod`, a hard link from it or into it, and so on, fails too: with `EROFS` when the root file system has a physical `/proc`, which stays hidden and untouched, and otherwise with the `ENOENT` of the missing directory, since that check is only paid for when a physical `/proc` exists; with one, a symlink whose target passes `..` after a file or a missing name, such as `/f/../proc`, fails with `ENOTDIR` or `ENOENT`, as on Linux), and `/` lists it, so a WASI program gets a preopen for it; `/proc/self` is the program's own pid there too (the pid an exec'd program took over). WASI and WASIX programs also have `/proc/<pid>/task/<tid>/stat` for each of a process's threads (the main thread is tid 1), whose state is `S` while the thread is blocked in the kernel (a `sem_wait`, a blocking read, a sleep, a futex) and `R` otherwise (`T` and `Z` as for the process). JS programs read the same `/proc` through `ctx.fs` and `ctx.open` (see [JS programs](#js-programs)). Neither has `/proc/<pid>/fd` yet.
|
|
1125
1125
|
|
|
1126
1126
|
The kernel writes `/etc/passwd` and `/etc/group` when it starts (see [Users](#users)). While the root has none of them, or no `/etc/mtab`, reading them gets per-process ones: root is uid 0 and the realm user, uid 1000, is named after `USER`, or `web_user` (Emscripten's default) without one, so it matches `$USER`, with `HOME` as its home, so `id`, `whoami`, `ls -l` and `ps` show names. Nothing is written to OPFS, and a real file there wins.
|
|
1127
1127
|
|
package/dist/kernel-worker.js
CHANGED
|
@@ -118,8 +118,9 @@ var MemoryMeta = class {
|
|
|
118
118
|
this.entries.set(to, moved(root(carried.find((entry) => entry.path === from)), to));
|
|
119
119
|
if (left) this.entries.set(from, left);
|
|
120
120
|
}
|
|
121
|
-
async remove(paths) {
|
|
121
|
+
async remove(paths, left) {
|
|
122
122
|
for (const path of paths) this.entries.deleteTree(path);
|
|
123
|
+
if (left) this.entries.set(left.path, left);
|
|
123
124
|
}
|
|
124
125
|
absent(path) {
|
|
125
126
|
return !this.entries.has(path);
|
|
@@ -265,9 +266,9 @@ var IndexedDbMeta = class {
|
|
|
265
266
|
}
|
|
266
267
|
removing = /* @__PURE__ */ new Map();
|
|
267
268
|
removeTimer;
|
|
268
|
-
async remove(paths) {
|
|
269
|
+
async remove(paths, left) {
|
|
269
270
|
this.settleRemoves();
|
|
270
|
-
return this.removeNow(paths);
|
|
271
|
+
return this.removeNow(paths, left);
|
|
271
272
|
}
|
|
272
273
|
async removeFile(path) {
|
|
273
274
|
const known = this.cache.get(path);
|
|
@@ -281,14 +282,16 @@ var IndexedDbMeta = class {
|
|
|
281
282
|
REMOVE_WAIT_MS
|
|
282
283
|
);
|
|
283
284
|
}
|
|
284
|
-
async removeNow(paths) {
|
|
285
|
+
async removeNow(paths, left) {
|
|
285
286
|
const [transaction, store] = this.transaction("readwrite");
|
|
286
287
|
for (const path of paths) {
|
|
287
288
|
store.delete(path);
|
|
288
289
|
store.delete(descendants(path));
|
|
289
290
|
}
|
|
291
|
+
if (left) store.put(left);
|
|
290
292
|
await settle(transaction, () => void 0);
|
|
291
293
|
this.changed({ trees: paths });
|
|
294
|
+
if (left) this.cache.set(left.path, left);
|
|
292
295
|
}
|
|
293
296
|
settleRemoves() {
|
|
294
297
|
if (this.removing.size > 0) void this.flushRemoves().catch(() => void 0);
|
|
@@ -2316,10 +2319,10 @@ var OpfsFs = class {
|
|
|
2316
2319
|
return;
|
|
2317
2320
|
}
|
|
2318
2321
|
}
|
|
2319
|
-
await this.meta.remove(
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2322
|
+
await this.meta.remove(
|
|
2323
|
+
[found.path],
|
|
2324
|
+
found.handle?.kind === "directory" ? vacatedAt(found.path) : void 0
|
|
2325
|
+
);
|
|
2323
2326
|
this.touch(Date.now(), parentOf3(found.path));
|
|
2324
2327
|
}
|
|
2325
2328
|
links() {
|
|
@@ -7125,7 +7128,7 @@ function satisfies(version, range2) {
|
|
|
7125
7128
|
}
|
|
7126
7129
|
|
|
7127
7130
|
// src/version.ts
|
|
7128
|
-
var KERNEL_VERSION = true ? "1.140.
|
|
7131
|
+
var KERNEL_VERSION = true ? "1.140.2" : "0.0.0-dev";
|
|
7129
7132
|
var OS_RELEASE = "6.0.0-slicc";
|
|
7130
7133
|
function procVersion() {
|
|
7131
7134
|
return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
|
|
@@ -9808,6 +9811,9 @@ function devCreation(path, cred) {
|
|
|
9808
9811
|
// src/fs/etc-files.ts
|
|
9809
9812
|
var HOSTNAME_PATH = "/etc/hostname";
|
|
9810
9813
|
var S_IFREG3 = 32768;
|
|
9814
|
+
var MAX_HOPS2 = 40;
|
|
9815
|
+
var parentOf6 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
|
|
9816
|
+
var baseOf2 = (path) => path.slice(path.lastIndexOf("/") + 1);
|
|
9811
9817
|
function missing4(err) {
|
|
9812
9818
|
return err?.code === "ENOENT";
|
|
9813
9819
|
}
|
|
@@ -9828,21 +9834,53 @@ function statOf(path, bytes) {
|
|
|
9828
9834
|
gid: 0
|
|
9829
9835
|
};
|
|
9830
9836
|
}
|
|
9837
|
+
function linkFollower(fs, files) {
|
|
9838
|
+
const bases = new Set([...files.keys()].map(baseOf2));
|
|
9839
|
+
const linkOf2 = (path) => fs.readlink(path).then(
|
|
9840
|
+
(link2) => link2,
|
|
9841
|
+
() => void 0
|
|
9842
|
+
);
|
|
9843
|
+
const viaParent = async (path) => {
|
|
9844
|
+
if (!bases.has(baseOf2(path)) || !fs.realpath) return path;
|
|
9845
|
+
const parent = parentOf6(path);
|
|
9846
|
+
const dir = await fs.realpath(parent, true).catch(() => parent);
|
|
9847
|
+
return fs.resolvePath(dir, baseOf2(path));
|
|
9848
|
+
};
|
|
9849
|
+
return async (path) => {
|
|
9850
|
+
let current = path;
|
|
9851
|
+
for (let hop = 0; hop < MAX_HOPS2 && !files.has(current); hop++) {
|
|
9852
|
+
const link2 = await linkOf2(current);
|
|
9853
|
+
if (link2 === void 0) return viaParent(current);
|
|
9854
|
+
current = fs.resolvePath(parentOf6(current), link2);
|
|
9855
|
+
}
|
|
9856
|
+
return current;
|
|
9857
|
+
};
|
|
9858
|
+
}
|
|
9859
|
+
function materializer(fs, listedAbsent) {
|
|
9860
|
+
return async (path) => {
|
|
9861
|
+
const bytes = await listedAbsent(path);
|
|
9862
|
+
if (bytes !== void 0) await fs.writeFile(path, bytes);
|
|
9863
|
+
};
|
|
9864
|
+
}
|
|
9831
9865
|
function withEtcFiles(fs, files) {
|
|
9832
9866
|
const bytesOf2 = (path) => {
|
|
9833
9867
|
const text = files.get(path)?.();
|
|
9834
9868
|
return text === void 0 ? void 0 : new TextEncoder().encode(text);
|
|
9835
9869
|
};
|
|
9836
9870
|
const absent2 = (path) => fs.lstat(path).then(() => false, missing4);
|
|
9871
|
+
const follow2 = linkFollower(fs, files);
|
|
9872
|
+
const listedAbsent = async (path) => files.has(path) && await absent2(path) ? bytesOf2(path) : void 0;
|
|
9837
9873
|
const fallback = (path, make) => async (err) => {
|
|
9838
|
-
const
|
|
9839
|
-
|
|
9840
|
-
|
|
9874
|
+
const at = missing4(err) ? await follow2(path) : void 0;
|
|
9875
|
+
const bytes = at === void 0 ? void 0 : await listedAbsent(at);
|
|
9876
|
+
if (bytes === void 0 || at === void 0) throw err;
|
|
9877
|
+
return make(bytes, at);
|
|
9841
9878
|
};
|
|
9842
9879
|
const ranged2 = rangedOps(fs);
|
|
9880
|
+
const materialize = materializer(fs, listedAbsent);
|
|
9843
9881
|
const own = {
|
|
9844
|
-
stat: (path) => fs.stat(path).catch(fallback(path, (b) => statOf(
|
|
9845
|
-
lstat: (path) => fs.lstat(path).catch(fallback(path, (b) => statOf(
|
|
9882
|
+
stat: (path) => fs.stat(path).catch(fallback(path, (b, at) => statOf(at, b))),
|
|
9883
|
+
lstat: (path) => fs.lstat(path).catch(fallback(path, (b, at) => statOf(at, b))),
|
|
9846
9884
|
exists: async (path) => await fs.exists(path) || files.has(path) && await absent2(path),
|
|
9847
9885
|
...fs.createExclusive ? {
|
|
9848
9886
|
createExclusive: async (path, owner) => {
|
|
@@ -9855,9 +9893,23 @@ function withEtcFiles(fs, files) {
|
|
|
9855
9893
|
}
|
|
9856
9894
|
} : {},
|
|
9857
9895
|
readFile: (path) => fs.readFile(path).catch(fallback(path, (b) => new TextDecoder().decode(b))),
|
|
9896
|
+
...fs.append ? {
|
|
9897
|
+
append: async (path, bytes) => {
|
|
9898
|
+
if (files.has(path)) await materialize(path);
|
|
9899
|
+
return fs.append.call(fs, path, bytes);
|
|
9900
|
+
}
|
|
9901
|
+
} : {},
|
|
9858
9902
|
readFileBuffer: (path) => fs.readFileBuffer(path).catch(fallback(path, (b) => b)),
|
|
9859
9903
|
...ranged2 ? {
|
|
9860
|
-
pread: (path, offset, length2, version) => ranged2.pread(path, offset, length2, version).catch(fallback(path, (b) => b.slice(offset, offset + length2)))
|
|
9904
|
+
pread: (path, offset, length2, version) => ranged2.pread(path, offset, length2, version).catch(fallback(path, (b) => b.slice(offset, offset + length2))),
|
|
9905
|
+
pwrite: async (path, offset, bytes, transfer) => {
|
|
9906
|
+
if (files.has(path)) await materialize(path);
|
|
9907
|
+
return ranged2.pwrite(path, offset, bytes, transfer);
|
|
9908
|
+
},
|
|
9909
|
+
truncate: async (path, size) => {
|
|
9910
|
+
if (files.has(path)) await materialize(path);
|
|
9911
|
+
return ranged2.truncate(path, size);
|
|
9912
|
+
}
|
|
9861
9913
|
} : {}
|
|
9862
9914
|
};
|
|
9863
9915
|
return new Proxy(fs, {
|
|
@@ -9990,7 +10042,7 @@ var FreshModules = class {
|
|
|
9990
10042
|
};
|
|
9991
10043
|
|
|
9992
10044
|
// src/fs/physical-path.ts
|
|
9993
|
-
var
|
|
10045
|
+
var MAX_HOPS3 = 40;
|
|
9994
10046
|
function start(path) {
|
|
9995
10047
|
return { done: [], todo: path.split("/").filter((p) => p !== ""), hops: 0 };
|
|
9996
10048
|
}
|
|
@@ -10015,7 +10067,7 @@ function rest(walk2, part) {
|
|
|
10015
10067
|
return lexical(`/${[...walk2.done, part, ...walk2.todo].join("/")}`);
|
|
10016
10068
|
}
|
|
10017
10069
|
function follow(walk2, target) {
|
|
10018
|
-
if (++walk2.hops >
|
|
10070
|
+
if (++walk2.hops > MAX_HOPS3) return false;
|
|
10019
10071
|
if (target.startsWith("/")) walk2.done.length = 0;
|
|
10020
10072
|
walk2.todo = [...target.split("/").filter((p) => p !== ""), ...walk2.todo];
|
|
10021
10073
|
return true;
|
|
@@ -10038,13 +10090,13 @@ async function physicalPathAsync(fs, path) {
|
|
|
10038
10090
|
|
|
10039
10091
|
// src/fs/watch.ts
|
|
10040
10092
|
var WATCH_NOTES_LIMIT = 1e3;
|
|
10041
|
-
var
|
|
10093
|
+
var baseOf3 = (path) => path.slice(path.lastIndexOf("/") + 1);
|
|
10042
10094
|
var WATCH_BATCH_LIMIT = 1e3;
|
|
10043
10095
|
var under3 = (path, dir) => dir === "/" || path.startsWith(`${dir}/`);
|
|
10044
|
-
var
|
|
10096
|
+
var parentOf7 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
|
|
10045
10097
|
function touches(watcher, changed) {
|
|
10046
10098
|
return watcher.paths.some(
|
|
10047
|
-
(watched) => changed === watched || under3(watched, changed) || (watcher.recursive ? under3(changed, watched) :
|
|
10099
|
+
(watched) => changed === watched || under3(watched, changed) || (watcher.recursive ? under3(changed, watched) : parentOf7(changed) === watched)
|
|
10048
10100
|
);
|
|
10049
10101
|
}
|
|
10050
10102
|
var REALS_KEPT = 4096;
|
|
@@ -10068,11 +10120,11 @@ function route(watch, type, path) {
|
|
|
10068
10120
|
}
|
|
10069
10121
|
function routed(watch, root, type, path) {
|
|
10070
10122
|
if (path === root || type === "rename" && root !== "/" && under3(root, path)) {
|
|
10071
|
-
noted(watch, { type, filename:
|
|
10123
|
+
noted(watch, { type, filename: baseOf3(root) });
|
|
10072
10124
|
if (type === "rename") noted(watch, { ended: true });
|
|
10073
10125
|
return true;
|
|
10074
10126
|
}
|
|
10075
|
-
if (
|
|
10127
|
+
if (parentOf7(path) === root) noted(watch, { type, filename: baseOf3(path) });
|
|
10076
10128
|
else if (watch.recursive && under3(path, root)) {
|
|
10077
10129
|
noted(watch, { type, filename: path.slice(root === "/" ? 1 : root.length + 1) });
|
|
10078
10130
|
} else return false;
|
|
@@ -10148,7 +10200,7 @@ var FsWatchers = class {
|
|
|
10148
10200
|
covers(path) {
|
|
10149
10201
|
for (const watch of this.typed) {
|
|
10150
10202
|
for (const root of watch.roots) {
|
|
10151
|
-
if (path === root ||
|
|
10203
|
+
if (path === root || parentOf7(path) === root || watch.recursive && under3(path, root))
|
|
10152
10204
|
return true;
|
|
10153
10205
|
}
|
|
10154
10206
|
}
|
|
@@ -11317,7 +11369,7 @@ var HeldPaths = class extends Map {
|
|
|
11317
11369
|
return this.revoked.has(path);
|
|
11318
11370
|
}
|
|
11319
11371
|
};
|
|
11320
|
-
var
|
|
11372
|
+
var parentOf8 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
|
|
11321
11373
|
var readonly = (mount) => mount.caps.readonly === true || mount.options.ro !== void 0;
|
|
11322
11374
|
function inodeless(mount, attr2) {
|
|
11323
11375
|
if (attr2.ino !== void 0) return false;
|
|
@@ -11617,7 +11669,7 @@ var MountTable = class {
|
|
|
11617
11669
|
for (const [key, cached] of [...mount.attrs])
|
|
11618
11670
|
if (within6(key, path) || linked2(cached.value)) mount.attrs.delete(key);
|
|
11619
11671
|
mount.lists.delete(path);
|
|
11620
|
-
mount.lists.delete(
|
|
11672
|
+
mount.lists.delete(parentOf8(path));
|
|
11621
11673
|
for (const [key, cached] of [...mount.lists])
|
|
11622
11674
|
if (within6(key, path) || cached.value.some((e) => linked2(e.attr))) mount.lists.delete(key);
|
|
11623
11675
|
}
|
|
@@ -11737,7 +11789,7 @@ var MountFs = class _MountFs {
|
|
|
11737
11789
|
const real = normalizePath(path);
|
|
11738
11790
|
if (this.at(real)) return real;
|
|
11739
11791
|
try {
|
|
11740
|
-
await this.base.stat(
|
|
11792
|
+
await this.base.stat(parentOf8(real));
|
|
11741
11793
|
return real;
|
|
11742
11794
|
} catch (err) {
|
|
11743
11795
|
const moved2 = redirectOf(err);
|
|
@@ -11779,7 +11831,7 @@ var MountFs = class _MountFs {
|
|
|
11779
11831
|
if (this.rejectLinks) throw errnoError("ELOOP", path);
|
|
11780
11832
|
if (hops >= MAX_LINKS7) throw errnoError("ELOOP", path);
|
|
11781
11833
|
const target = await found.mount.conn.call({ op: "readlink", path: found.rel });
|
|
11782
|
-
return this.follow(this.base.resolvePath(
|
|
11834
|
+
return this.follow(this.base.resolvePath(parentOf8(real), target), hops + 1, create);
|
|
11783
11835
|
}
|
|
11784
11836
|
async open(mount, rel2, write, owner) {
|
|
11785
11837
|
return await mount.conn.call({
|
|
@@ -11795,8 +11847,8 @@ var MountFs = class _MountFs {
|
|
|
11795
11847
|
async driverOwner(found, real, owner, directory) {
|
|
11796
11848
|
const { mount, rel: rel2 } = found;
|
|
11797
11849
|
if (!owner || !mount.owners || !mount.caps.owners) return void 0;
|
|
11798
|
-
const dir =
|
|
11799
|
-
const above = statOf2(await this.table.getattr(mount, dir,
|
|
11850
|
+
const dir = parentOf8(real);
|
|
11851
|
+
const above = statOf2(await this.table.getattr(mount, dir, parentOf8(rel2)), dir, mount);
|
|
11800
11852
|
const setgid = (above.mode & S_ISGID4) !== 0;
|
|
11801
11853
|
let mode = owner.mode;
|
|
11802
11854
|
if (directory && setgid) mode = (mode ?? 493) | S_ISGID4;
|
|
@@ -12302,7 +12354,7 @@ var MountFs = class _MountFs {
|
|
|
12302
12354
|
}
|
|
12303
12355
|
async named(path) {
|
|
12304
12356
|
const at = normalizePath(path);
|
|
12305
|
-
return joinName(await realDir(this,
|
|
12357
|
+
return joinName(await realDir(this, parentOf8(at)), at.slice(at.lastIndexOf("/") + 1));
|
|
12306
12358
|
}
|
|
12307
12359
|
async link(source, target, follow2 = false) {
|
|
12308
12360
|
const from = follow2 ? await realTarget(this, normalizePath(source)) : await this.named(source);
|
|
@@ -14188,6 +14240,12 @@ var ProcTree = class {
|
|
|
14188
14240
|
if (node.kind !== "dir") throw syncError("ENOTDIR", path);
|
|
14189
14241
|
return node.names();
|
|
14190
14242
|
}
|
|
14243
|
+
escape(path) {
|
|
14244
|
+
const node = this.node(this.parts(path, true));
|
|
14245
|
+
if (node?.kind !== "link" || !node.target.startsWith("/")) return void 0;
|
|
14246
|
+
if (node.denied) throw syncError("EACCES", path);
|
|
14247
|
+
return node.target;
|
|
14248
|
+
}
|
|
14191
14249
|
readlink(path) {
|
|
14192
14250
|
const node = this.find(path, false);
|
|
14193
14251
|
if (node.kind !== "link") throw syncError("EINVAL", path);
|
|
@@ -14203,7 +14261,7 @@ var ProcTree = class {
|
|
|
14203
14261
|
}
|
|
14204
14262
|
}
|
|
14205
14263
|
};
|
|
14206
|
-
var
|
|
14264
|
+
var MAX_HOPS4 = 40;
|
|
14207
14265
|
function* procWalk(path, follow2) {
|
|
14208
14266
|
let done5 = [];
|
|
14209
14267
|
let todo = path.split("/").filter((part) => part !== "");
|
|
@@ -14222,7 +14280,7 @@ function* procWalk(path, follow2) {
|
|
|
14222
14280
|
if (!follow2 && todo.length === 0) return void 0;
|
|
14223
14281
|
const target = yield { at, check: todo.find((next3) => next3 !== ".") === ".." };
|
|
14224
14282
|
if (typeof target === "string") {
|
|
14225
|
-
if (++hops >
|
|
14283
|
+
if (++hops > MAX_HOPS4) return void 0;
|
|
14226
14284
|
[done5, todo] = throughLink(target, done5, todo);
|
|
14227
14285
|
} else {
|
|
14228
14286
|
broken ??= refusalOf(target);
|
|
@@ -21882,7 +21940,7 @@ function isWatchSyscall(req) {
|
|
|
21882
21940
|
return WATCHING.has(req.op);
|
|
21883
21941
|
}
|
|
21884
21942
|
var WATCHES_MAX = 8192;
|
|
21885
|
-
var
|
|
21943
|
+
var parentOf9 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
|
|
21886
21944
|
function visibleTo2(watch, root, visible) {
|
|
21887
21945
|
return {
|
|
21888
21946
|
close: () => watch.close(),
|
|
@@ -21892,7 +21950,7 @@ function visibleTo2(watch, root, visible) {
|
|
|
21892
21950
|
const shown = await Promise.all(
|
|
21893
21951
|
notes.map(async (note) => {
|
|
21894
21952
|
if (!("filename" in note) || !note.filename.includes("/")) return true;
|
|
21895
|
-
const dir =
|
|
21953
|
+
const dir = parentOf9(`${root === "/" ? "" : root}/${note.filename}`);
|
|
21896
21954
|
if (!seen.has(dir)) seen.set(dir, visible(dir));
|
|
21897
21955
|
return seen.get(dir);
|
|
21898
21956
|
})
|