@ai-ecoverse/slicc-kernel 1.140.1 → 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
@@ -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
 
@@ -7128,7 +7128,7 @@ function satisfies(version, range2) {
7128
7128
  }
7129
7129
 
7130
7130
  // src/version.ts
7131
- var KERNEL_VERSION = true ? "1.140.1" : "0.0.0-dev";
7131
+ var KERNEL_VERSION = true ? "1.140.2" : "0.0.0-dev";
7132
7132
  var OS_RELEASE = "6.0.0-slicc";
7133
7133
  function procVersion() {
7134
7134
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -9811,6 +9811,9 @@ function devCreation(path, cred) {
9811
9811
  // src/fs/etc-files.ts
9812
9812
  var HOSTNAME_PATH = "/etc/hostname";
9813
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);
9814
9817
  function missing4(err) {
9815
9818
  return err?.code === "ENOENT";
9816
9819
  }
@@ -9831,21 +9834,53 @@ function statOf(path, bytes) {
9831
9834
  gid: 0
9832
9835
  };
9833
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
+ }
9834
9865
  function withEtcFiles(fs, files) {
9835
9866
  const bytesOf2 = (path) => {
9836
9867
  const text = files.get(path)?.();
9837
9868
  return text === void 0 ? void 0 : new TextEncoder().encode(text);
9838
9869
  };
9839
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;
9840
9873
  const fallback = (path, make) => async (err) => {
9841
- const bytes = missing4(err) && files.has(path) && await absent2(path) ? bytesOf2(path) : void 0;
9842
- if (bytes === void 0) throw err;
9843
- 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);
9844
9878
  };
9845
9879
  const ranged2 = rangedOps(fs);
9880
+ const materialize = materializer(fs, listedAbsent);
9846
9881
  const own = {
9847
- stat: (path) => fs.stat(path).catch(fallback(path, (b) => statOf(path, b))),
9848
- 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))),
9849
9884
  exists: async (path) => await fs.exists(path) || files.has(path) && await absent2(path),
9850
9885
  ...fs.createExclusive ? {
9851
9886
  createExclusive: async (path, owner) => {
@@ -9858,9 +9893,23 @@ function withEtcFiles(fs, files) {
9858
9893
  }
9859
9894
  } : {},
9860
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
+ } : {},
9861
9902
  readFileBuffer: (path) => fs.readFileBuffer(path).catch(fallback(path, (b) => b)),
9862
9903
  ...ranged2 ? {
9863
- 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
+ }
9864
9913
  } : {}
9865
9914
  };
9866
9915
  return new Proxy(fs, {
@@ -9993,7 +10042,7 @@ var FreshModules = class {
9993
10042
  };
9994
10043
 
9995
10044
  // src/fs/physical-path.ts
9996
- var MAX_HOPS2 = 40;
10045
+ var MAX_HOPS3 = 40;
9997
10046
  function start(path) {
9998
10047
  return { done: [], todo: path.split("/").filter((p) => p !== ""), hops: 0 };
9999
10048
  }
@@ -10018,7 +10067,7 @@ function rest(walk2, part) {
10018
10067
  return lexical(`/${[...walk2.done, part, ...walk2.todo].join("/")}`);
10019
10068
  }
10020
10069
  function follow(walk2, target) {
10021
- if (++walk2.hops > MAX_HOPS2) return false;
10070
+ if (++walk2.hops > MAX_HOPS3) return false;
10022
10071
  if (target.startsWith("/")) walk2.done.length = 0;
10023
10072
  walk2.todo = [...target.split("/").filter((p) => p !== ""), ...walk2.todo];
10024
10073
  return true;
@@ -10041,13 +10090,13 @@ async function physicalPathAsync(fs, path) {
10041
10090
 
10042
10091
  // src/fs/watch.ts
10043
10092
  var WATCH_NOTES_LIMIT = 1e3;
10044
- var baseOf2 = (path) => path.slice(path.lastIndexOf("/") + 1);
10093
+ var baseOf3 = (path) => path.slice(path.lastIndexOf("/") + 1);
10045
10094
  var WATCH_BATCH_LIMIT = 1e3;
10046
10095
  var under3 = (path, dir) => dir === "/" || path.startsWith(`${dir}/`);
10047
- var parentOf6 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
10096
+ var parentOf7 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
10048
10097
  function touches(watcher, changed) {
10049
10098
  return watcher.paths.some(
10050
- (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)
10051
10100
  );
10052
10101
  }
10053
10102
  var REALS_KEPT = 4096;
@@ -10071,11 +10120,11 @@ function route(watch, type, path) {
10071
10120
  }
10072
10121
  function routed(watch, root, type, path) {
10073
10122
  if (path === root || type === "rename" && root !== "/" && under3(root, path)) {
10074
- noted(watch, { type, filename: baseOf2(root) });
10123
+ noted(watch, { type, filename: baseOf3(root) });
10075
10124
  if (type === "rename") noted(watch, { ended: true });
10076
10125
  return true;
10077
10126
  }
10078
- if (parentOf6(path) === root) noted(watch, { type, filename: baseOf2(path) });
10127
+ if (parentOf7(path) === root) noted(watch, { type, filename: baseOf3(path) });
10079
10128
  else if (watch.recursive && under3(path, root)) {
10080
10129
  noted(watch, { type, filename: path.slice(root === "/" ? 1 : root.length + 1) });
10081
10130
  } else return false;
@@ -10151,7 +10200,7 @@ var FsWatchers = class {
10151
10200
  covers(path) {
10152
10201
  for (const watch of this.typed) {
10153
10202
  for (const root of watch.roots) {
10154
- if (path === root || parentOf6(path) === root || watch.recursive && under3(path, root))
10203
+ if (path === root || parentOf7(path) === root || watch.recursive && under3(path, root))
10155
10204
  return true;
10156
10205
  }
10157
10206
  }
@@ -11320,7 +11369,7 @@ var HeldPaths = class extends Map {
11320
11369
  return this.revoked.has(path);
11321
11370
  }
11322
11371
  };
11323
- var parentOf7 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
11372
+ var parentOf8 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
11324
11373
  var readonly = (mount) => mount.caps.readonly === true || mount.options.ro !== void 0;
11325
11374
  function inodeless(mount, attr2) {
11326
11375
  if (attr2.ino !== void 0) return false;
@@ -11620,7 +11669,7 @@ var MountTable = class {
11620
11669
  for (const [key, cached] of [...mount.attrs])
11621
11670
  if (within6(key, path) || linked2(cached.value)) mount.attrs.delete(key);
11622
11671
  mount.lists.delete(path);
11623
- mount.lists.delete(parentOf7(path));
11672
+ mount.lists.delete(parentOf8(path));
11624
11673
  for (const [key, cached] of [...mount.lists])
11625
11674
  if (within6(key, path) || cached.value.some((e) => linked2(e.attr))) mount.lists.delete(key);
11626
11675
  }
@@ -11740,7 +11789,7 @@ var MountFs = class _MountFs {
11740
11789
  const real = normalizePath(path);
11741
11790
  if (this.at(real)) return real;
11742
11791
  try {
11743
- await this.base.stat(parentOf7(real));
11792
+ await this.base.stat(parentOf8(real));
11744
11793
  return real;
11745
11794
  } catch (err) {
11746
11795
  const moved2 = redirectOf(err);
@@ -11782,7 +11831,7 @@ var MountFs = class _MountFs {
11782
11831
  if (this.rejectLinks) throw errnoError("ELOOP", path);
11783
11832
  if (hops >= MAX_LINKS7) throw errnoError("ELOOP", path);
11784
11833
  const target = await found.mount.conn.call({ op: "readlink", path: found.rel });
11785
- 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);
11786
11835
  }
11787
11836
  async open(mount, rel2, write, owner) {
11788
11837
  return await mount.conn.call({
@@ -11798,8 +11847,8 @@ var MountFs = class _MountFs {
11798
11847
  async driverOwner(found, real, owner, directory) {
11799
11848
  const { mount, rel: rel2 } = found;
11800
11849
  if (!owner || !mount.owners || !mount.caps.owners) return void 0;
11801
- const dir = parentOf7(real);
11802
- 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);
11803
11852
  const setgid = (above.mode & S_ISGID4) !== 0;
11804
11853
  let mode = owner.mode;
11805
11854
  if (directory && setgid) mode = (mode ?? 493) | S_ISGID4;
@@ -12305,7 +12354,7 @@ var MountFs = class _MountFs {
12305
12354
  }
12306
12355
  async named(path) {
12307
12356
  const at = normalizePath(path);
12308
- 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));
12309
12358
  }
12310
12359
  async link(source, target, follow2 = false) {
12311
12360
  const from = follow2 ? await realTarget(this, normalizePath(source)) : await this.named(source);
@@ -14191,6 +14240,12 @@ var ProcTree = class {
14191
14240
  if (node.kind !== "dir") throw syncError("ENOTDIR", path);
14192
14241
  return node.names();
14193
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
+ }
14194
14249
  readlink(path) {
14195
14250
  const node = this.find(path, false);
14196
14251
  if (node.kind !== "link") throw syncError("EINVAL", path);
@@ -14206,7 +14261,7 @@ var ProcTree = class {
14206
14261
  }
14207
14262
  }
14208
14263
  };
14209
- var MAX_HOPS3 = 40;
14264
+ var MAX_HOPS4 = 40;
14210
14265
  function* procWalk(path, follow2) {
14211
14266
  let done5 = [];
14212
14267
  let todo = path.split("/").filter((part) => part !== "");
@@ -14225,7 +14280,7 @@ function* procWalk(path, follow2) {
14225
14280
  if (!follow2 && todo.length === 0) return void 0;
14226
14281
  const target = yield { at, check: todo.find((next3) => next3 !== ".") === ".." };
14227
14282
  if (typeof target === "string") {
14228
- if (++hops > MAX_HOPS3) return void 0;
14283
+ if (++hops > MAX_HOPS4) return void 0;
14229
14284
  [done5, todo] = throughLink(target, done5, todo);
14230
14285
  } else {
14231
14286
  broken ??= refusalOf(target);
@@ -21885,7 +21940,7 @@ function isWatchSyscall(req) {
21885
21940
  return WATCHING.has(req.op);
21886
21941
  }
21887
21942
  var WATCHES_MAX = 8192;
21888
- var parentOf8 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
21943
+ var parentOf9 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
21889
21944
  function visibleTo2(watch, root, visible) {
21890
21945
  return {
21891
21946
  close: () => watch.close(),
@@ -21895,7 +21950,7 @@ function visibleTo2(watch, root, visible) {
21895
21950
  const shown = await Promise.all(
21896
21951
  notes.map(async (note) => {
21897
21952
  if (!("filename" in note) || !note.filename.includes("/")) return true;
21898
- const dir = parentOf8(`${root === "/" ? "" : root}/${note.filename}`);
21953
+ const dir = parentOf9(`${root === "/" ? "" : root}/${note.filename}`);
21899
21954
  if (!seen.has(dir)) seen.set(dir, visible(dir));
21900
21955
  return seen.get(dir);
21901
21956
  })