@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 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
 
@@ -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([found.path]);
2320
- if (found.handle?.kind === "directory") {
2321
- await this.meta.update(found.path, (entry) => entry ?? vacatedAt(found.path));
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.0" : "0.0.0-dev";
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 bytes = missing4(err) && files.has(path) && await absent2(path) ? bytesOf2(path) : void 0;
9839
- if (bytes === void 0) throw err;
9840
- return make(bytes);
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(path, b))),
9845
- lstat: (path) => fs.lstat(path).catch(fallback(path, (b) => statOf(path, b))),
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 MAX_HOPS2 = 40;
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 > MAX_HOPS2) return false;
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 baseOf2 = (path) => path.slice(path.lastIndexOf("/") + 1);
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 parentOf6 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
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) : parentOf6(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: baseOf2(root) });
10123
+ noted(watch, { type, filename: baseOf3(root) });
10072
10124
  if (type === "rename") noted(watch, { ended: true });
10073
10125
  return true;
10074
10126
  }
10075
- if (parentOf6(path) === root) noted(watch, { type, filename: baseOf2(path) });
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 || parentOf6(path) === root || watch.recursive && under3(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 parentOf7 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
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(parentOf7(path));
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(parentOf7(real));
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(parentOf7(real), target), hops + 1, create);
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 = parentOf7(real);
11799
- const above = statOf2(await this.table.getattr(mount, dir, parentOf7(rel2)), dir, mount);
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, parentOf7(at)), at.slice(at.lastIndexOf("/") + 1));
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 MAX_HOPS3 = 40;
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 > MAX_HOPS3) return void 0;
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 parentOf8 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
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 = parentOf8(`${root === "/" ? "" : root}/${note.filename}`);
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
  })