@ai-ecoverse/slicc-kernel 1.178.2 → 1.178.4

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
@@ -1067,7 +1067,7 @@ With `users: 'enforce'`, the default, or `'report'`, files have owners, and [Per
1067
1067
  - **Homes:** every `kernel.users.add` gives the user's home (only a real directory at `/home/<dir>`, not a symlink or a path through one) and everything in it without an owner of its own to the user, and sets the home's mode: `0700`, `0750` for a `scoop-` user, `2750` with `homeGroup`, and its `~/.local/share/pnpm`'s to `0700` (`0750` for a `scoop-` user). The embedder calls `add` for each user at boot, as it already does, so homes made before owners were on become the user's. That also means a user's own `chmod` or `chgrp` of its home lasts only until the next `add`, and `add` must get the same `homeGroup` every time.
1068
1068
  - **`chown`, `lchown`, `fchown`:** Emscripten programs, and WASI programs through `slicc_fs` (see [File modes and the umask](#file-modes-and-the-umask)), change owners through the kernel, by the Linux rules: root may set any owner and group; a file's owner may set its group to one it belongs to, or leave it as it is; anything else fails with `EPERM`. `-1` keeps an id. A `chown` that sets an owner or group of a file clears its setuid bit, and its setgid bit if the file is group-executable, whoever calls it, root included, as on Linux. With `users: 'off'`, `chown` succeeds and changes nothing, as before.
1069
1069
  - **What programs see:** `stat` shows the owners to Emscripten programs, whatever their libc reports for their own ids, so a program built before H1 sees real owners too, and its owner checks can fail. WASI programs read them with `slicc_fs.fd_owner` and `path_owner`. The commands in `/usr/bin` and `/bin` are root's, and a user's own commands in `~/.local/share/pnpm/bin` belong to the owner of the closest real directory above them, normally the user.
1070
- - **Mounts:** the options `uid=` and `gid=` (decimal ids) give a mount its owner; without them it belongs to whoever mounted it: root for `kernel.mount`, `/etc/fstab` and a client, the process's effective ids for `mount(2)`, which then shows them in its options (a process that isn't root gets its own ids whatever `uid=` and `gid=` it passes, so it can't make a mount look root's). A mount whose driver keeps owners (`tmpfs`) starts with its root owned so, and its files get owners as on the root file system, `chown` included. On `hostfs`, `fsa` and any driver without `owners`, every file shows as the mount's owner and `chown` fails with `EPERM`, root's too, as on a FAT drive. An embedder that mounts a folder for a user (bios's linked and picked folders) passes that user's `uid` and `gid`, or the user's programs see root's files there, and git calls the repository's ownership dubious. Those mounts also take the FAT-style options `umask=`, `fmask=` and `dmask=` (octal, as on Linux `vfat`): when any is set, the kernel synthesises the permission bits that `stat` and DAC see from `0666` for files and `0777` for directories (and symlinks), less the mask (`fmask` for files, `dmask` for directories, and `umask` as the default for both). Without them, files stay `0644` and directories `0755` as before, so only the mount's `uid` may write. `umask=000` (or `fmask=000,dmask=000`) makes every file and directory world-writable under DAC, which is how a personal cone (another uid) can write a folder mounted as the template `cone` user. When a mask is set on a mount without a `chmod` capability (`fsa`), `chmod` is a no-op and is not owner-only, so a create that then sets the mode from another uid still succeeds. Without a mask, and on a mount that keeps real modes through `chmod` (`hostfs`), `chmod` still requires the mount's `uid` (or root). Drivers with `owners` (`tmpfs`) ignore the mask options: their modes stay real, and chmod still requires the owner.
1070
+ - **Mounts:** the options `uid=` and `gid=` (decimal ids) give a mount its owner; without them it belongs to whoever mounted it: root for `kernel.mount`, `/etc/fstab` and a client, the process's effective ids for `mount(2)`, which then shows them in its options (a process that isn't root gets its own ids whatever `uid=` and `gid=` it passes, so it can't make a mount look root's). A mount whose driver keeps owners (`tmpfs`) starts with its root owned so, and its files get owners as on the root file system, `chown` included. On `hostfs`, `fsa` and any driver without `owners`, every file shows as the mount's owner and `chown` fails with `EPERM`, root's too, as on a FAT drive. An embedder that mounts a folder for a user (bios's linked and picked folders) passes that user's `uid` and `gid`, or the user's programs see root's files there, and git calls the repository's ownership dubious. Those mounts also take the FAT-style options `umask=`, `fmask=` and `dmask=` (octal, as on Linux `vfat`): when any is set, the kernel synthesises the permission bits that `stat` and DAC see from `0666` for files and `0777` for directories (and symlinks), less the mask (`fmask` for files, `dmask` for directories, and `umask` as the default for both). Without them, files stay `0644` and directories `0755` as before, so only the mount's `uid` may write. `umask=000` (or `fmask=000,dmask=000`) makes every file and directory world-writable under DAC, which is how a personal cone (another uid) can write a folder mounted as the template `cone` user. When a mask is set on a mount without a `chmod` capability (`fsa`), `chmod` is a no-op and is not owner-only, so a create that then sets the mode from another uid still succeeds, and a non-owner who can write the file may set its times to an explicit value (`utimes` / `cp -p` / `tar x`), as on Linux `vfat` with a uid/umask mount; "now" times already needed only write access. Without a mask, and on a mount that keeps real modes through `chmod` (`hostfs`), `chmod` still requires the mount's `uid` (or root), and explicit times stay owner-only. Drivers with `owners` (`tmpfs`) ignore the mask options: their modes stay real, and chmod and explicit times still require the owner.
1071
1071
 
1072
1072
  ### Permissions
1073
1073
 
@@ -1339,8 +1339,8 @@ var RemoteWatch = class {
1339
1339
  const { held = [], pending = [] } = await this.locks.query();
1340
1340
  if (![...held, ...pending].some((lock) => lock.name === name2)) {
1341
1341
  this.mourned.delete(peer);
1342
- const live = this.peers.has(peer) || this.flushes.has(peer);
1343
- if (tries > 1 && live) {
1342
+ const live2 = this.peers.has(peer) || this.flushes.has(peer);
1343
+ if (tries > 1 && live2) {
1344
1344
  setTimeout(() => void this.mourn(peer, tries - 1), MOURN_RETRY_MS);
1345
1345
  } else this.flushedAt(peer);
1346
1346
  return;
@@ -1830,11 +1830,11 @@ async function positioned(handle, op) {
1830
1830
  }
1831
1831
  return;
1832
1832
  }
1833
- const writable2 = await handle.createWritable({ keepExistingData: true });
1833
+ const writable3 = await handle.createWritable({ keepExistingData: true });
1834
1834
  try {
1835
1835
  await op({
1836
1836
  write: async (at, bytes) => {
1837
- await writable2.write({
1837
+ await writable3.write({
1838
1838
  type: "write",
1839
1839
  position: at,
1840
1840
  data: bytes
@@ -1842,15 +1842,15 @@ async function positioned(handle, op) {
1842
1842
  extent.wrote(at + bytes.length);
1843
1843
  },
1844
1844
  truncate: async (n) => {
1845
- await writable2.truncate(n);
1845
+ await writable3.truncate(n);
1846
1846
  extent.cut(n);
1847
1847
  }
1848
1848
  });
1849
1849
  } catch (err) {
1850
- await writable2.abort();
1850
+ await writable3.abort();
1851
1851
  throw err;
1852
1852
  }
1853
- await writable2.close();
1853
+ await writable3.close();
1854
1854
  if (extent.big()) extent.check((await handle.getFile()).size);
1855
1855
  }
1856
1856
  function syncAccessHandles(scope = globalThis) {
@@ -2936,21 +2936,21 @@ var OpfsFs = class {
2936
2936
  }
2937
2937
  async writeSession(path, truncate) {
2938
2938
  const [handle, , target] = await this.fileFor(path);
2939
- const writable2 = await this.retrying(
2939
+ const writable3 = await this.retrying(
2940
2940
  path,
2941
2941
  () => this.io(handle.createWritable({ keepExistingData: !truncate })),
2942
2942
  true
2943
2943
  );
2944
2944
  return {
2945
- write: (at, bytes) => writable2.write({ type: "write", position: at, data: bytes }).catch((err) => {
2945
+ write: (at, bytes) => writable3.write({ type: "write", position: at, data: bytes }).catch((err) => {
2946
2946
  throw translate(err, path, "write", false);
2947
2947
  }),
2948
- truncate: (size) => writable2.truncate(size).catch((err) => {
2948
+ truncate: (size) => writable3.truncate(size).catch((err) => {
2949
2949
  throw translate(err, path, "write", false);
2950
2950
  }),
2951
2951
  close: async () => {
2952
2952
  this.wrote(handle);
2953
- await writable2.close().catch((err) => {
2953
+ await writable3.close().catch((err) => {
2954
2954
  this.wrote(handle);
2955
2955
  throw translate(err, path, "commit", false);
2956
2956
  });
@@ -3002,19 +3002,19 @@ var OpfsFs = class {
3002
3002
  const [file, existed, target] = await this.fileFor(path, owner?.existing !== true);
3003
3003
  if (!existed) await this.stampNew(target, owner, false);
3004
3004
  if (!existed && content.length === 0) return;
3005
- let writable2;
3005
+ let writable3;
3006
3006
  this.wrote(file);
3007
3007
  try {
3008
- writable2 = await this.io(file.createWritable());
3009
- await writable2.write(content);
3010
- await writable2.close();
3008
+ writable3 = await this.io(file.createWritable());
3009
+ await writable3.write(content);
3010
+ await writable3.close();
3011
3011
  if (typeof content !== "string" && content.length >= CHECKED_BYTES) {
3012
3012
  const extent = new Extent();
3013
3013
  extent.cut(content.length);
3014
3014
  extent.check((await file.getFile()).size);
3015
3015
  }
3016
3016
  } catch (err) {
3017
- await writable2?.abort().catch(() => void 0);
3017
+ await writable3?.abort().catch(() => void 0);
3018
3018
  throw translate(err, path, "commit", false);
3019
3019
  } finally {
3020
3020
  this.wrote(file);
@@ -3739,7 +3739,7 @@ var OpfsFs = class {
3739
3739
  created4 = true;
3740
3740
  }
3741
3741
  let target = destination;
3742
- let writable2;
3742
+ let writable3;
3743
3743
  let handle;
3744
3744
  try {
3745
3745
  const kept = created4 ? this.files.get(destination) : void 0;
@@ -3758,11 +3758,11 @@ var OpfsFs = class {
3758
3758
  if (entry?.fifo || entry?.socket) throw fsError("ENXIO", to);
3759
3759
  }
3760
3760
  this.wrote(handle);
3761
- writable2 = await handle.createWritable();
3762
- await writable2.write(file);
3763
- await writable2.close();
3761
+ writable3 = await handle.createWritable();
3762
+ await writable3.write(file);
3763
+ await writable3.close();
3764
3764
  } catch (err) {
3765
- await writable2?.abort().catch(() => void 0);
3765
+ await writable3?.abort().catch(() => void 0);
3766
3766
  if (created4) await this.rm(target, { file: true }).catch(() => void 0);
3767
3767
  throw translate(err, to, "commit", false);
3768
3768
  } finally {
@@ -3851,10 +3851,10 @@ var OpfsFs = class {
3851
3851
  }
3852
3852
  if (handle.kind === "file") {
3853
3853
  const made2 = await this.io(dir.getFileHandle(name2, { create: true }));
3854
- const writable2 = await this.io(made2.createWritable());
3854
+ const writable3 = await this.io(made2.createWritable());
3855
3855
  const file = await this.io(handle.getFile());
3856
- await writable2.write(file);
3857
- await writable2.close();
3856
+ await writable3.write(file);
3857
+ await writable3.close();
3858
3858
  copies?.set(rest2, { before: copies.get(rest2)?.before ?? file.lastModified, made: made2 });
3859
3859
  return;
3860
3860
  }
@@ -5572,13 +5572,6 @@ async function linkable(g, from, to, follow2) {
5572
5572
  }
5573
5573
  await inParent(g, "link", to);
5574
5574
  }
5575
- async function ownerOnly(g, op, path, follow2) {
5576
- const walked = await need(g, op, path, 0, { follow: follow2 });
5577
- const perm = walked.perm;
5578
- if (!perm || g.cred.euid === 0 || perm.uid === g.cred.euid) return walked;
5579
- if (await present(g.fs, walked.path, false)) deny(g, op, path, "EPERM");
5580
- return walked;
5581
- }
5582
5575
  async function chmodMode(g, path, mode) {
5583
5576
  const walked = await need(g, "chmod", path, 0, { follow: true });
5584
5577
  const perm = walked.perm;
@@ -5600,17 +5593,26 @@ function heldChmodMode(g, path, st, mode) {
5600
5593
  function heldRetimable(g, path, st, now) {
5601
5594
  const perm = permOf(st);
5602
5595
  if (g.cred.euid === 0 || perm.uid === g.cred.euid) return;
5603
- if (!now) deny(g, "futimens", path, "EPERM");
5604
- else if (!permits(g.cred, perm, W_OK, false)) deny(g, "futimens", path);
5596
+ if (now || st.fat) {
5597
+ if (!permits(g.cred, perm, W_OK, false)) deny(g, "futimens", path);
5598
+ return;
5599
+ }
5600
+ deny(g, "futimens", path, "EPERM");
5605
5601
  }
5606
5602
  async function retimable(g, op, path, now = false) {
5607
5603
  const follow2 = op !== "lutimes";
5608
- if (!now) return void await ownerOnly(g, op, path, follow2);
5609
5604
  const walked = await need(g, op, path, 0, { follow: follow2 });
5610
5605
  const perm = walked.perm;
5611
5606
  if (!perm || g.cred.euid === 0 || perm.uid === g.cred.euid) return;
5612
- if (permits(g.cred, perm, W_OK, false)) return;
5613
- if (await present(g.fs, walked.path, false)) deny(g, op, path);
5607
+ const look = follow2 ? g.fs.stat : g.fs.lstat;
5608
+ const st = await look.call(g.fs, walked.path).catch(() => void 0);
5609
+ if (now || st?.fat) {
5610
+ if (!permits(g.cred, perm, W_OK, false) && await present(g.fs, walked.path, false)) {
5611
+ deny(g, op, path);
5612
+ }
5613
+ return;
5614
+ }
5615
+ if (await present(g.fs, walked.path, false)) deny(g, op, path, "EPERM");
5614
5616
  }
5615
5617
  async function fileWritable(g, op, path) {
5616
5618
  await need(g, op, path, W_OK, { directory: false });
@@ -8777,7 +8779,7 @@ function satisfies(version, range2) {
8777
8779
  }
8778
8780
 
8779
8781
  // src/version.ts
8780
- var KERNEL_VERSION = true ? "1.178.2" : "0.0.0-dev";
8782
+ var KERNEL_VERSION = true ? "1.178.4" : "0.0.0-dev";
8781
8783
  var OS_RELEASE = "6.0.0-slicc";
8782
8784
  function procVersion() {
8783
8785
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -17089,11 +17091,11 @@ function systemFile(name2, listing, now, memory) {
17089
17091
  listing.processes.reduce((sum2, p) => sum2 + p.memory, 0)
17090
17092
  );
17091
17093
  }
17092
- const live = listing.processes.filter((p) => p.state !== "Z");
17094
+ const live2 = listing.processes.filter((p) => p.state !== "Z");
17093
17095
  if (name2 === "loadavg") {
17094
17096
  const last = Math.max(0, ...listing.processes.map((p) => p.pid));
17095
17097
  const [one, five, fifteen] = (listing.load ?? [0, 0, 0]).map((n) => n.toFixed(2));
17096
- return `${one} ${five} ${fifteen} 1/${live.length} ${last}
17098
+ return `${one} ${five} ${fifteen} 1/${live2.length} ${last}
17097
17099
  `;
17098
17100
  }
17099
17101
  const sum = (key) => listing.cpu?.[key] ?? listing.processes.reduce((n, p) => n + (p[key] ?? 0), 0);
@@ -19582,30 +19584,49 @@ function timestampKey(owner, type) {
19582
19584
  }
19583
19585
  return parent ? JSON.stringify(["ppid", parent.pid, parent.started]) : void 0;
19584
19586
  }
19587
+ function boundKey(bound3) {
19588
+ return JSON.stringify(
19589
+ Object.keys(bound3).sort().map((path) => [path, bound3[path]])
19590
+ );
19591
+ }
19592
+ function writable2(bound3) {
19593
+ return bound3 !== void 0 && Object.keys(bound3).length > 0;
19594
+ }
19595
+ function live(stamp, policy, now) {
19596
+ return stamp.at <= now && (policy.minutes < 0 || now - stamp.at < policy.minutes * MINUTE_MS2);
19597
+ }
19585
19598
  var Timestamps = class {
19586
19599
  users = /* @__PURE__ */ new Map();
19587
19600
  now;
19588
19601
  constructor(now) {
19589
19602
  this.now = now;
19590
19603
  }
19591
- current(owner, policy) {
19604
+ current(owner, policy, bound3) {
19592
19605
  if (policy.minutes === 0) return false;
19593
19606
  const key = timestampKey(owner, policy.type);
19594
- const at = key === void 0 ? void 0 : this.users.get(owner.uid)?.get(key);
19595
- if (at === void 0) return false;
19607
+ const stamp = key === void 0 ? void 0 : this.users.get(owner.uid)?.get(key);
19608
+ if (stamp === void 0) return false;
19596
19609
  const now = this.now();
19597
- return at <= now && (policy.minutes < 0 || now - at < policy.minutes * MINUTE_MS2);
19610
+ if (!live(stamp, policy, now)) {
19611
+ this.drop(owner.uid, key);
19612
+ return false;
19613
+ }
19614
+ if (!writable2(bound3)) return true;
19615
+ return stamp.covered.has(boundKey(bound3));
19598
19616
  }
19599
- stamp(owner, policy) {
19617
+ stamp(owner, policy, bound3) {
19600
19618
  const key = policy.minutes === 0 ? void 0 : timestampKey(owner, policy.type);
19601
19619
  if (key === void 0) return false;
19602
19620
  const now = this.now();
19603
19621
  const records = this.users.get(owner.uid) ?? /* @__PURE__ */ new Map();
19604
19622
  this.users.set(owner.uid, records);
19623
+ const prior = records.get(key);
19624
+ const covered = prior !== void 0 && live(prior, policy, now) ? prior.covered : /* @__PURE__ */ new Set();
19625
+ if (writable2(bound3)) covered.add(boundKey(bound3));
19605
19626
  records.delete(key);
19606
- records.set(key, now);
19607
- for (const [old, at] of records) {
19608
- const stale = policy.minutes > 0 && (at > now || now - at >= policy.minutes * MINUTE_MS2);
19627
+ records.set(key, { at: now, covered });
19628
+ for (const [old, stamp] of records) {
19629
+ const stale = policy.minutes > 0 && !live(stamp, policy, now);
19609
19630
  if (stale || records.size > TIMESTAMPS_MAX) records.delete(old);
19610
19631
  }
19611
19632
  return true;
@@ -19619,6 +19640,11 @@ var Timestamps = class {
19619
19640
  remove(uid) {
19620
19641
  this.users.delete(uid);
19621
19642
  }
19643
+ drop(uid, key) {
19644
+ const records = this.users.get(uid);
19645
+ records?.delete(key);
19646
+ if (records?.size === 0) this.users.delete(uid);
19647
+ }
19622
19648
  };
19623
19649
 
19624
19650
  // src/sudoers.ts
@@ -20248,12 +20274,12 @@ function usable(parsed2) {
20248
20274
  function policyOf(settings) {
20249
20275
  return { type: settings.timestampType, minutes: settings.timestampTimeout };
20250
20276
  }
20251
- function described(rule, writable2) {
20277
+ function described(rule, writable3) {
20252
20278
  const cmnd = rule.cmnd === "*" ? "ALL" : commandText(rule.cmnd);
20253
20279
  const cwd = rule.cwd === void 0 ? "" : `CWD=${quoteWord(rule.cwd)} `;
20254
20280
  const stdin = rule.stdin === void 0 ? "" : "STDIN=none ";
20255
20281
  const sums = Object.entries(rule.sha256 ?? {}).map(([path, hex4]) => `SHA256=${quoteWord(path)}:${hex4} `).join("");
20256
- const flag = writable2 ? " # user-writable command" : "";
20282
+ const flag = writable3 ? " # user-writable command" : "";
20257
20283
  return ` (${rule.target}) ${cwd}${stdin}${sums}${rule.nopasswd ? "NOPASSWD: " : ""}${cmnd}${flag}`;
20258
20284
  }
20259
20285
  var Sudo = class {
@@ -20546,8 +20572,8 @@ var Sudo = class {
20546
20572
  `sudo: ${self.name} is not allowed to run ${quoted} as ${target.name}`
20547
20573
  );
20548
20574
  }
20549
- if (!parsed2.reset && this.timestamps.current(s.owner, s.policy)) {
20550
- this.timestamps.stamp(s.owner, s.policy);
20575
+ if (!parsed2.reset && this.timestamps.current(s.owner, s.policy, bound3)) {
20576
+ this.timestamps.stamp(s.owner, s.policy, bound3);
20551
20577
  this.logged(s, cwd, argv);
20552
20578
  return grant();
20553
20579
  }
@@ -20562,7 +20588,7 @@ var Sudo = class {
20562
20588
  if (!now) return failed2("ENOENT", `sudo: ${word}: command not found`);
20563
20589
  const shown2 = recordable(cwd, [...parsed2.command, ...Object.keys(bound3)]);
20564
20590
  const result = answer2.answer === "once" ? grant() : await this.always(self, target, { cwd, argv, bound: bound3 }, now, shown2, grant);
20565
- if (!("error" in result)) this.stamped(s, answer2);
20591
+ if (!("error" in result)) this.stamped(s, answer2, bound3);
20566
20592
  return result;
20567
20593
  });
20568
20594
  }
@@ -20596,8 +20622,8 @@ var Sudo = class {
20596
20622
  this.pending.delete(uid);
20597
20623
  }
20598
20624
  }
20599
- stamped(s, answer2) {
20600
- if (answer2.person && !s.parsed.reset) this.timestamps.stamp(s.owner, s.policy);
20625
+ stamped(s, answer2, bound3 = {}) {
20626
+ if (answer2.person && !s.parsed.reset) this.timestamps.stamp(s.owner, s.policy, bound3);
20601
20627
  }
20602
20628
  logged(s, cwd, argv) {
20603
20629
  const { caller, self, target, owner, policy } = s;
@@ -26954,18 +26980,18 @@ var VfsNodes = class {
26954
26980
  if (id2 !== void 0) this.know(node, id2);
26955
26981
  return node;
26956
26982
  }
26957
- open(path, pin, writable2 = false, id2, ino) {
26983
+ open(path, pin, writable3 = false, id2, ino) {
26958
26984
  const found = this.named(path, id2);
26959
26985
  const same3 = !found || ino === void 0 || found.ino === void 0 || found.ino === ino;
26960
26986
  if (found && !same3) this.orphan(found);
26961
26987
  const node = (same3 ? found : void 0) ?? this.create(path, id2, new VfsNode(this.fs, path, void 0, false, false, pin));
26962
26988
  node.ino ??= ino;
26963
26989
  node.opens++;
26964
- if (writable2) node.writers++;
26990
+ if (writable3) node.writers++;
26965
26991
  if (node.opens === 1) this.opened?.(node);
26966
26992
  return node;
26967
26993
  }
26968
- adopt(path, contents2, dirty, writable2 = false, id2) {
26994
+ adopt(path, contents2, dirty, writable3 = false, id2) {
26969
26995
  let node = this.named(path, id2);
26970
26996
  if (!node) {
26971
26997
  node = this.create(path, id2, new VfsNode(this.fs, path, contents2, false, dirty));
@@ -26973,7 +26999,7 @@ var VfsNodes = class {
26973
26999
  node.replace(contents2);
26974
27000
  }
26975
27001
  node.opens++;
26976
- if (writable2) node.writers++;
27002
+ if (writable3) node.writers++;
26977
27003
  if (node.opens === 1) this.opened?.(node);
26978
27004
  return node;
26979
27005
  }
@@ -26986,11 +27012,11 @@ var VfsNodes = class {
26986
27012
  if (node.writers > 0 && within9(path, prefix)) return true;
26987
27013
  return false;
26988
27014
  }
26989
- closed(node, writable2 = false) {
27015
+ closed(node, writable3 = false) {
26990
27016
  node.opens--;
26991
- if (writable2) node.writers--;
27017
+ if (writable3) node.writers--;
26992
27018
  if (node.opens > 0) {
26993
- if (writable2 && node.writers === 0) this.onClosed?.(node.path);
27019
+ if (writable3 && node.writers === 0) this.onClosed?.(node.path);
26994
27020
  return;
26995
27021
  }
26996
27022
  this.forget(node);
@@ -27309,8 +27335,8 @@ function keepingOpen(fs, nodes, changed = () => {
27309
27335
  function vfsFile(fs, opts, nodes) {
27310
27336
  const access2 = opts.flags & O_ACCMODE;
27311
27337
  const readable2 = access2 !== O_WRONLY;
27312
- const writable2 = access2 === O_WRONLY || access2 === O_RDWR;
27313
- const node = !nodes || opts.orphan ? new VfsNode(fs, opts.path, opts.contents, opts.orphan === true, opts.dirty === true) : opts.contents !== void 0 ? nodes.adopt(opts.path, opts.contents, opts.dirty === true, writable2, opts.id) : nodes.open(opts.path, opts.pin, writable2, opts.id, opts.ino);
27338
+ const writable3 = access2 === O_WRONLY || access2 === O_RDWR;
27339
+ const node = !nodes || opts.orphan ? new VfsNode(fs, opts.path, opts.contents, opts.orphan === true, opts.dirty === true) : opts.contents !== void 0 ? nodes.adopt(opts.path, opts.contents, opts.dirty === true, writable3, opts.id) : nodes.open(opts.path, opts.pin, writable3, opts.id, opts.ino);
27314
27340
  let openError;
27315
27341
  const atOpen = (op) => {
27316
27342
  void node.serial(op).catch((err) => {
@@ -27337,7 +27363,7 @@ function vfsFile(fs, opts, nodes) {
27337
27363
  offset += out.length;
27338
27364
  return out;
27339
27365
  }) : void 0,
27340
- write: writable2 ? (bytes) => serial(async () => {
27366
+ write: writable3 ? (bytes) => serial(async () => {
27341
27367
  if (opts.flags & O_APPEND) offset = await node.append(bytes);
27342
27368
  else offset += await node.pwrite(bytes, offset);
27343
27369
  return bytes.length;
@@ -27356,11 +27382,11 @@ function vfsFile(fs, opts, nodes) {
27356
27382
  return node.pread(max, at);
27357
27383
  }),
27358
27384
  pwrite: (bytes, at) => serial(() => {
27359
- if (!writable2) throw new KernelError("EBADF");
27385
+ if (!writable3) throw new KernelError("EBADF");
27360
27386
  return node.pwrite(bytes, at);
27361
27387
  }),
27362
27388
  resize: (size) => serial(() => {
27363
- if (!writable2) throw new KernelError("EBADF");
27389
+ if (!writable3) throw new KernelError("EBADF");
27364
27390
  return node.truncate(size);
27365
27391
  }),
27366
27392
  stat: () => serial(async () => ({
@@ -27376,7 +27402,7 @@ function vfsFile(fs, opts, nodes) {
27376
27402
  takeOpenError();
27377
27403
  await node.flush();
27378
27404
  } finally {
27379
- nodes?.closed(node, writable2);
27405
+ nodes?.closed(node, writable3);
27380
27406
  }
27381
27407
  })
27382
27408
  });
@@ -29105,8 +29131,8 @@ var WasmProcess = class {
29105
29131
  };
29106
29132
  withDl(state) {
29107
29133
  if (this.dlLog.length === 0) return state;
29108
- const { next: next3, handle, free, live } = this.closures;
29109
- const dl = { log: [...this.dlLog], next: next3, handle, free: [...free], live: [...live] };
29134
+ const { next: next3, handle, free, live: live2 } = this.closures;
29135
+ const dl = { log: [...this.dlLog], next: next3, handle, free: [...free], live: [...live2] };
29110
29136
  return { ...state, dl };
29111
29137
  }
29112
29138
  seedDl(dl) {
@@ -32330,7 +32356,7 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
32330
32356
  if (remote.client.token === remote.token && !remote.client.gone) return remote;
32331
32357
  throw fsError2("ESTALE", "the folder was granted again, so this open file is gone");
32332
32358
  };
32333
- const live = (fh) => {
32359
+ const live2 = (fh) => {
32334
32360
  const remote = handles.get(fh);
32335
32361
  if (!remote) throw fsError2("EBADF", `handle ${fh}`);
32336
32362
  return fresh3(remote);
@@ -32349,7 +32375,7 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
32349
32375
  }, againMs);
32350
32376
  timer.unref?.();
32351
32377
  });
32352
- async function writable2(client, path, flags2) {
32378
+ async function writable3(client, path, flags2) {
32353
32379
  const { ifMatch: _, ...asked } = flags2;
32354
32380
  const opening2 = () => client.call("open", { path: rel(path), ...asked });
32355
32381
  try {
@@ -32380,7 +32406,7 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
32380
32406
  async open(path, flags2) {
32381
32407
  const client = current();
32382
32408
  const token = client.token;
32383
- const { opened: opened2, restore } = await writable2(client, path, flags2);
32409
+ const { opened: opened2, restore } = await writable3(client, path, flags2);
32384
32410
  if (flags2.ifMatch !== void 0 && opened2.attr?.etag !== flags2.ifMatch) {
32385
32411
  await client.call("release", { fh: opened2.fh }, true);
32386
32412
  throw fsError2("ESTALE", `${path} changed on the host while it was being read`);
@@ -32395,7 +32421,7 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
32395
32421
  return nextFh;
32396
32422
  },
32397
32423
  async read(fh, offset, size) {
32398
- const remote = live(fh);
32424
+ const remote = live2(fh);
32399
32425
  return remote.client.read(
32400
32426
  remote.fh,
32401
32427
  offset,
@@ -32404,7 +32430,7 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
32404
32430
  );
32405
32431
  },
32406
32432
  async write(fh, offset, bytes) {
32407
- const remote = live(fh);
32433
+ const remote = live2(fh);
32408
32434
  const most = remote.client.maxIo;
32409
32435
  for (let at = 0; at < bytes.length; at += most) {
32410
32436
  await remote.client.write(remote.fh, offset + at, bytes.subarray(at, at + most));