@ai-ecoverse/slicc-kernel 1.92.0 → 1.93.0

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.
@@ -450,12 +450,36 @@ async function chownAs(fs, cred, path, change) {
450
450
  }
451
451
 
452
452
  // src/fs/integrity.ts
453
+ var SOURCE_SUFFIXES = [
454
+ ".js",
455
+ ".mjs",
456
+ ".cjs",
457
+ ".json",
458
+ ".py",
459
+ ".pyc",
460
+ ".pth",
461
+ ".zip",
462
+ ".so",
463
+ ".wasm",
464
+ ".sh"
465
+ ];
466
+ var EXEC_OPS = { failed: "exec-integrity", unknown: "exec-unverified" };
467
+ var READ_OPS = { failed: "read-integrity", unknown: "read-unverified" };
453
468
  var KERNEL_CALLER = { pid: 0, uid: 0 };
454
469
  function integrityOptions(options) {
455
470
  if (options && typeof options.lookup !== "function") {
456
471
  throw new TypeError("protect.integrity.lookup must be a function");
457
472
  }
458
473
  integrityUnknown(options);
474
+ integritySources(options);
475
+ }
476
+ function integritySources(options) {
477
+ const sources = options?.sources ?? SOURCE_SUFFIXES;
478
+ const suffix = (s) => typeof s === "string" && /^\.[^/]+$/.test(s);
479
+ if (!Array.isArray(sources) || !sources.every(suffix)) {
480
+ throw new TypeError("protect.integrity.sources must be a list of suffixes such as .js");
481
+ }
482
+ return [...sources];
459
483
  }
460
484
  function integrityUnknown(options) {
461
485
  const unknown = options?.unknown ?? "allow";
@@ -477,16 +501,21 @@ var Integrity = class {
477
501
  unknown;
478
502
  report;
479
503
  unverified = /* @__PURE__ */ new Set();
504
+ unread = /* @__PURE__ */ new Set();
505
+ sources;
506
+ passed = /* @__PURE__ */ new Set();
507
+ generation = 0;
480
508
  constructor(prefixes2, root, options, report) {
481
509
  this.prefixes = prefixes2;
482
510
  this.root = root;
483
511
  this.lookup = options.lookup;
484
512
  this.unknown = integrityUnknown(options);
513
+ this.sources = integritySources(options);
485
514
  this.report = report;
486
515
  }
487
- async real(path, asked) {
516
+ async real(path, asked, known) {
488
517
  const at = normalizePath(path);
489
- const real = await this.root.realpath?.(at, true).catch(() => void 0) ?? at;
518
+ const real = known ?? await this.root.realpath?.(at, true).catch(() => void 0) ?? at;
490
519
  if (isProtected(this.prefixes, real)) return { real, name: real };
491
520
  const names = asked === void 0 ? [] : await namesOf(this.root, asked);
492
521
  if (real !== at) names.push(...await namesOf(this.root, at));
@@ -511,6 +540,51 @@ var Integrity = class {
511
540
  const read = name2 !== real ? { bytes: await plain(), version: void 0 } : this.root.readVersioned ? await this.root.readVersioned(real) : { bytes: await this.root.readFileBuffer(real), version: void 0 };
512
541
  const { bytes } = read;
513
542
  const keyed = read.version === void 0 ? { bytes } : { bytes, key: `${real}\0${read.version}` };
543
+ await this.verify(name2, bytes, caller, EXEC_OPS);
544
+ return keyed;
545
+ }
546
+ get readsChecked() {
547
+ return this.sources.length > 0;
548
+ }
549
+ async readSource(path, caller, plain) {
550
+ const found = await this.source(path);
551
+ return found === void 0 ? plain() : this.checked(found, caller, plain);
552
+ }
553
+ async openSource(path, caller, real) {
554
+ const found = await this.source(path, real);
555
+ if (found === void 0) return;
556
+ if (found.name === found.real && this.root.version) {
557
+ if (this.passed.has(`${found.real}\0${await this.root.version(found.real, true)}`)) return;
558
+ }
559
+ await this.checked(found, caller, () => this.root.readFileBuffer(found.real));
560
+ }
561
+ async checked(found, caller, plain) {
562
+ const { real, name: name2 } = found;
563
+ if (name2 !== real || !this.root.readVersioned) {
564
+ const bytes2 = await plain();
565
+ await this.verify(name2, bytes2, caller, READ_OPS);
566
+ return bytes2;
567
+ }
568
+ const { bytes, version } = await this.root.readVersioned(real, true);
569
+ await this.pass(`${real}\0${version}`, () => this.verify(name2, bytes, caller, READ_OPS));
570
+ return bytes;
571
+ }
572
+ async pass(key, check) {
573
+ if (this.passed.has(key)) return;
574
+ const generation = this.generation;
575
+ await check();
576
+ if (generation === this.generation) this.passed.add(key);
577
+ }
578
+ async source(path, known) {
579
+ if (this.sources.length === 0) return void 0;
580
+ const found = await this.real(path, void 0, known);
581
+ if (found === void 0) return void 0;
582
+ const suffixed = (p) => this.sources.some((s) => p.endsWith(s));
583
+ if (suffixed(found.real) || suffixed(found.name) || suffixed(normalizePath(path))) return found;
584
+ const st = await this.root.stat(found.real).catch(() => void 0);
585
+ return st?.isFile && ((st.mode ?? 0) & 73) !== 0 ? found : void 0;
586
+ }
587
+ async verify(name2, bytes, caller, ops) {
514
588
  const actual = await sha256(bytes);
515
589
  const denial = { pid: caller.pid, uid: caller.uid, path: name2, actual };
516
590
  const tagged = caller.tag !== void 0 ? { tag: caller.tag } : {};
@@ -518,27 +592,33 @@ var Integrity = class {
518
592
  try {
519
593
  expected = await this.expected(name2);
520
594
  } catch {
521
- this.say({ ...denial, ...tagged, op: "exec-integrity", errno: "EACCES" });
595
+ this.say({ ...denial, ...tagged, op: ops.failed, errno: "EACCES" });
522
596
  throw fsError("EACCES", `integrity lookup failed: ${name2}`);
523
597
  }
524
- if (expected === void 0) {
525
- if (this.unknown === "allow") {
526
- if (!this.unverified.has(name2)) {
527
- this.unverified.add(name2);
528
- this.say({ ...denial, ...tagged, op: "exec-unverified", errno: "ENODATA" });
529
- }
530
- return keyed;
598
+ if (expected === actual) return;
599
+ if (expected === void 0 && this.unknown === "allow") {
600
+ const seen = ops === EXEC_OPS ? this.unverified : this.unread;
601
+ if (!seen.has(name2)) {
602
+ seen.add(name2);
603
+ this.say({ ...denial, ...tagged, op: ops.unknown, errno: "ENODATA" });
531
604
  }
532
- } else if (expected === actual) {
533
- return keyed;
605
+ return;
534
606
  }
535
607
  const known = expected !== void 0 ? { expected } : {};
536
- this.say({ ...denial, ...tagged, ...known, op: "exec-integrity", errno: "EACCES" });
608
+ this.say({ ...denial, ...tagged, ...known, op: ops.failed, errno: "EACCES" });
537
609
  throw fsError("EACCES", `integrity check failed: ${name2}`);
538
610
  }
539
611
  forget(path) {
540
- for (const name2 of [...this.unverified]) {
541
- if (path === void 0 || isProtected([path], name2)) this.unverified.delete(name2);
612
+ this.generation++;
613
+ for (const seen of [this.unverified, this.unread]) {
614
+ for (const name2 of [...seen]) {
615
+ if (path === void 0 || isProtected([path], name2)) seen.delete(name2);
616
+ }
617
+ }
618
+ for (const key of [...this.passed]) {
619
+ if (path === void 0 || isProtected([path], key.slice(0, key.indexOf("\0")))) {
620
+ this.passed.delete(key);
621
+ }
542
622
  }
543
623
  }
544
624
  async expected(path) {
@@ -560,9 +640,13 @@ function protectInit(protection) {
560
640
  return {
561
641
  paths: [...protection.paths],
562
642
  ...writers.length > 0 ? { writers } : {},
563
- ...integrity ? { integrity: { unknown: integrityUnknown(integrity) } } : {}
643
+ ...integrity ? { integrity: integrityInit(integrity) } : {}
564
644
  };
565
645
  }
646
+ function integrityInit(integrity) {
647
+ const unknown = integrityUnknown(integrity);
648
+ return integrity.sources === void 0 ? { unknown } : { unknown, sources: integritySources(integrity) };
649
+ }
566
650
  function writerTags(protection) {
567
651
  const writers = protection?.writers ?? [];
568
652
  if (!Array.isArray(writers) || writers.some((tag) => typeof tag !== "string" || tag === "")) {
@@ -746,15 +830,15 @@ var ProtectedFs = class {
746
830
  }
747
831
  } : {}
748
832
  };
749
- const view = {};
833
+ const view2 = {};
750
834
  for (let at = fs; at && at !== Object.prototype; at = Object.getPrototypeOf(at)) {
751
835
  for (const name2 of Object.getOwnPropertyNames(at)) {
752
- if (name2 === "constructor" || Object.hasOwn(view, name2)) continue;
836
+ if (name2 === "constructor" || Object.hasOwn(view2, name2)) continue;
753
837
  const value = fs[name2];
754
- view[name2] = typeof value === "function" ? value.bind(fs) : value;
838
+ view2[name2] = typeof value === "function" ? value.bind(fs) : value;
755
839
  }
756
840
  }
757
- return Object.assign(view, own);
841
+ return Object.assign(view2, own);
758
842
  }
759
843
  };
760
844
 
@@ -873,6 +957,23 @@ var RemoteWatch = class {
873
957
  }
874
958
  };
875
959
 
960
+ // src/fs/view.ts
961
+ function view(target, overrides) {
962
+ const keys = new Set(Object.keys(overrides));
963
+ return new Proxy(target, {
964
+ get(t, prop, receiver) {
965
+ if (keys.has(prop)) return overrides[prop];
966
+ return Reflect.get(t, prop, receiver);
967
+ },
968
+ set(t, prop, value) {
969
+ return Reflect.set(t, prop, value);
970
+ },
971
+ getPrototypeOf(t) {
972
+ return Reflect.getPrototypeOf(t);
973
+ }
974
+ });
975
+ }
976
+
876
977
  // src/fs/opfs.ts
877
978
  var S_IFDIR = 16384;
878
979
  var S_IFREG = 32768;
@@ -1012,11 +1113,16 @@ var OpfsFs = class {
1012
1113
  remote;
1013
1114
  ranged;
1014
1115
  owners = false;
1116
+ rejectLinks = false;
1015
1117
  mounts = {};
1118
+ locks;
1119
+ lockPrefix;
1016
1120
  constructor(root, meta = new MemoryMeta(), channel, options = {}) {
1017
1121
  this.root = root;
1018
1122
  this.meta = sidecar(meta);
1019
1123
  this.ranged = options.ranged === true;
1124
+ this.locks = options.locks === void 0 ? lockManager() : options.locks ?? void 0;
1125
+ this.lockPrefix = `slicc-excl:${channel ?? ""}:`;
1020
1126
  this.channel = channel === void 0 ? void 0 : new BroadcastChannel(channel);
1021
1127
  this.remote = channel === void 0 ? void 0 : new RemoteWatch(`${channel}:watch`);
1022
1128
  if (this.channel) {
@@ -1043,12 +1149,13 @@ var OpfsFs = class {
1043
1149
  return this.remote?.announce(paths) ?? Promise.resolve();
1044
1150
  }
1045
1151
  committed(target) {
1152
+ this.fileTimes.delete(target);
1046
1153
  if (this.remote?.watched()) this.remote.wrote(target);
1047
1154
  }
1048
1155
  counted = [];
1049
- async stamp(path) {
1156
+ async stamp(path, cached = false) {
1050
1157
  const paths = [path, "/"];
1051
- const [entry, root] = this.meta.fresh ? await this.meta.fresh(paths) : await this.meta.get(paths);
1158
+ const [entry, root] = this.meta.fresh && !cached ? await this.meta.fresh(paths) : await this.meta.get(paths);
1052
1159
  return `${entry?.writes ?? 0}:${root?.epoch ?? 0}`;
1053
1160
  }
1054
1161
  async epoch() {
@@ -1147,13 +1254,25 @@ var OpfsFs = class {
1147
1254
  this.counted = versions ? prefixes2 : [];
1148
1255
  }
1149
1256
  unguarded() {
1150
- return Object.create(this, { guarded: { value: [] } });
1257
+ return view(this, { guarded: [] });
1258
+ }
1259
+ noLinks() {
1260
+ return view(this, { rejectLinks: true });
1151
1261
  }
1152
1262
  refuse(path, holding = false) {
1153
1263
  if (isProtected(this.guarded, path) || holding && holdsProtected(this.guarded, path)) {
1154
1264
  throw fsError("EROFS", path);
1155
1265
  }
1156
1266
  }
1267
+ async locked(physicalPath, op) {
1268
+ if (!this.locks) return op();
1269
+ const key = `${this.lockPrefix}${physicalPath}`;
1270
+ let result;
1271
+ await this.locks.request(key, async () => {
1272
+ result = await op();
1273
+ });
1274
+ return result;
1275
+ }
1157
1276
  async chown(path, change) {
1158
1277
  if (!this.owners) return;
1159
1278
  if (change.kind === "new") {
@@ -1285,8 +1404,15 @@ var OpfsFs = class {
1285
1404
  const link2 = await this.linkAlong(current);
1286
1405
  if (!link2) throw err;
1287
1406
  if (link2.path === current && !follow2) return { path: current, link: link2 };
1407
+ if (this.rejectLinks) throw fsError("ELOOP", path);
1288
1408
  const target = `${link2.link}${current.slice(link2.path.length)}`;
1289
- current = target.split("/").includes("..") ? await this.physical(parentOf3(link2.path), target, hops + 1, path) : resolvePath(parentOf3(link2.path), target);
1409
+ if (target.split("/").includes("..")) {
1410
+ const resolved2 = await this.physical(parentOf3(link2.path), target, hops + 1, path);
1411
+ current = resolved2.path;
1412
+ hops = resolved2.hops;
1413
+ } else {
1414
+ current = resolvePath(parentOf3(link2.path), target);
1415
+ }
1290
1416
  this.crossing(path, current);
1291
1417
  }
1292
1418
  }
@@ -1295,7 +1421,8 @@ var OpfsFs = class {
1295
1421
  async physical(base, path, used, of) {
1296
1422
  let done4 = [];
1297
1423
  let todo = (path.startsWith("/") ? path : `${base}/${path}`).split("/").filter((p) => p !== "" && p !== ".");
1298
- for (let hops = used; todo.length > 0; ) {
1424
+ let hops = used;
1425
+ while (todo.length > 0) {
1299
1426
  const part = todo.shift();
1300
1427
  if (part === "..") done4.pop();
1301
1428
  else if (!todo.includes("..")) done4.push(part);
@@ -1305,6 +1432,7 @@ var OpfsFs = class {
1305
1432
  const st = await this.lstat(at);
1306
1433
  if (!st.isSymbolicLink && !st.isDirectory) throw fsError("ENOTDIR", at);
1307
1434
  if (!st.isSymbolicLink) done4.push(part);
1435
+ else if (this.rejectLinks) throw fsError("ELOOP", of);
1308
1436
  else if (++hops > MAX_LINKS2) throw fsError("ELOOP", path);
1309
1437
  else {
1310
1438
  const link2 = await this.readlink(at);
@@ -1313,7 +1441,7 @@ var OpfsFs = class {
1313
1441
  }
1314
1442
  }
1315
1443
  }
1316
- return `/${done4.join("/")}`;
1444
+ return { path: `/${done4.join("/")}`, hops };
1317
1445
  }
1318
1446
  mode(path, directory, meta) {
1319
1447
  const fallback = directory || EXECUTABLE.test(path) ? 493 : 420;
@@ -1357,6 +1485,11 @@ var OpfsFs = class {
1357
1485
  return entry.timesAt >= ms;
1358
1486
  }
1359
1487
  movePending(from, to) {
1488
+ if (to !== void 0) {
1489
+ for (const path of [...this.fileTimes.keys()]) {
1490
+ if (within2(path, to)) this.fileTimes.delete(path);
1491
+ }
1492
+ }
1360
1493
  for (const [path, ms] of [...this.fileTimes]) {
1361
1494
  if (!within2(path, from)) continue;
1362
1495
  this.fileTimes.delete(path);
@@ -1364,8 +1497,7 @@ var OpfsFs = class {
1364
1497
  }
1365
1498
  }
1366
1499
  flushing = {};
1367
- touch(...dirs) {
1368
- const now = Date.now();
1500
+ touch(now, ...dirs) {
1369
1501
  for (const dir of dirs) {
1370
1502
  this.dirTimes.set(dir, now);
1371
1503
  this.dirDirty.add(dir);
@@ -1478,12 +1610,12 @@ var OpfsFs = class {
1478
1610
  false
1479
1611
  );
1480
1612
  }
1481
- async readVersioned(path) {
1613
+ async readVersioned(path, cached = false) {
1482
1614
  const handle = await this.existing(path);
1483
1615
  return this.retrying(
1484
1616
  path,
1485
1617
  async () => {
1486
- const stamp = await this.stamp(path);
1618
+ const stamp = await this.stamp(path, cached);
1487
1619
  const file = await handle.getFile();
1488
1620
  const bytes = new Uint8Array(await file.arrayBuffer());
1489
1621
  return { bytes, version: `${file.lastModified}:${file.size}:${stamp}` };
@@ -1491,12 +1623,12 @@ var OpfsFs = class {
1491
1623
  false
1492
1624
  );
1493
1625
  }
1494
- async version(path) {
1626
+ async version(path, cached = false) {
1495
1627
  const handle = await this.existing(path);
1496
1628
  return this.retrying(
1497
1629
  path,
1498
1630
  async () => {
1499
- const stamp = await this.stamp(path);
1631
+ const stamp = await this.stamp(path, cached);
1500
1632
  const file = await handle.getFile();
1501
1633
  return `${file.lastModified}:${file.size}:${stamp}`;
1502
1634
  },
@@ -1581,7 +1713,7 @@ var OpfsFs = class {
1581
1713
  dir,
1582
1714
  (parent) => parent.getFileHandle(name2, { create: true })
1583
1715
  );
1584
- if (!existed) this.touch(parentOf3(target));
1716
+ if (!existed) this.touch(Date.now(), parentOf3(target));
1585
1717
  return [handle, existed, target];
1586
1718
  } catch (err) {
1587
1719
  const code = err?.name === "TypeMismatchError" ? "EISDIR" : null;
@@ -1636,6 +1768,7 @@ var OpfsFs = class {
1636
1768
  );
1637
1769
  }
1638
1770
  async mkdir(path, options = {}) {
1771
+ if (!options.recursive) return this.mkdirExcl(path, options.owner);
1639
1772
  let found;
1640
1773
  try {
1641
1774
  found = await this.locate(path, true);
@@ -1644,28 +1777,75 @@ var OpfsFs = class {
1644
1777
  }
1645
1778
  if (found) {
1646
1779
  const directory = found.handle?.kind === "directory";
1647
- if (options.recursive && directory) return;
1648
- throw fsError(options.recursive ? "ENOTDIR" : "EEXIST", path);
1780
+ if (directory) return;
1781
+ throw fsError("ENOTDIR", path);
1649
1782
  }
1650
1783
  const [entry] = await this.meta.get([path]);
1651
1784
  if (entry?.link !== void 0) throw fsError("EEXIST", path);
1652
1785
  const { owner } = options;
1653
- if (options.recursive) {
1654
- const mode = owner?.parents;
1655
- const above = owner && {
1656
- uid: owner.uid,
1657
- gid: owner.gid,
1658
- ...mode !== void 0 ? { mode, parents: mode } : {}
1659
- };
1660
- await this.mkdir(parentOf3(path), { recursive: true, ...above ? { owner: above } : {} });
1661
- }
1786
+ const mode = owner?.parents;
1787
+ const above = owner && {
1788
+ uid: owner.uid,
1789
+ gid: owner.gid,
1790
+ ...mode !== void 0 ? { mode, parents: mode } : {}
1791
+ };
1792
+ await this.mkdir(parentOf3(path), { recursive: true, ...above ? { owner: above } : {} });
1662
1793
  const parent = await this.locate(parentOf3(path), true);
1663
1794
  if (parent.handle?.kind !== "directory") throw fsError("ENOTDIR", path);
1664
1795
  this.refuse(resolvePath(parent.path, baseOf(path)));
1665
1796
  await parent.handle.getDirectoryHandle(baseOf(path), { create: true });
1797
+ const now = Date.now();
1666
1798
  const made2 = `${parent.path === "/" ? "" : parent.path}/${baseOf(path)}`;
1667
1799
  await this.stampNew(made2, owner, true);
1668
- this.touch(parent.path, made2);
1800
+ this.touch(now, parent.path, made2);
1801
+ }
1802
+ async mkdirExcl(path, owner) {
1803
+ const parent = await this.locate(parentOf3(path), true);
1804
+ if (parent.handle?.kind !== "directory") throw fsError("ENOTDIR", path);
1805
+ const dir = parent.handle;
1806
+ const name2 = baseOf(path);
1807
+ const physical = resolvePath(parent.path, name2);
1808
+ await this.locked(physical, async () => {
1809
+ if (await this.handleExists(dir, name2)) throw fsError("EEXIST", path);
1810
+ const freshMeta = this.meta.fresh ? await this.meta.fresh([path]) : await this.meta.get([path]);
1811
+ if (freshMeta[0]?.link !== void 0) throw fsError("EEXIST", path);
1812
+ this.forget(physical);
1813
+ this.refuse(physical);
1814
+ await dir.getDirectoryHandle(name2, { create: true });
1815
+ const now = Date.now();
1816
+ const made2 = `${parent.path === "/" ? "" : parent.path}/${name2}`;
1817
+ await this.stampNew(made2, owner, true);
1818
+ this.touch(now, parent.path, made2);
1819
+ });
1820
+ }
1821
+ async createExclusive(path, owner) {
1822
+ const parent = await this.locate(parentOf3(path), true);
1823
+ if (parent.handle?.kind !== "directory") throw fsError("ENOTDIR", path);
1824
+ const dir = parent.handle;
1825
+ const name2 = baseOf(path);
1826
+ const physical = resolvePath(parent.path, name2);
1827
+ await this.locked(physical, async () => {
1828
+ if (await this.handleExists(dir, name2)) throw fsError("EEXIST", path);
1829
+ const freshMeta = this.meta.fresh ? await this.meta.fresh([path]) : await this.meta.get([path]);
1830
+ if (freshMeta[0] !== void 0) throw fsError("EEXIST", path);
1831
+ this.forget(physical);
1832
+ this.refuse(physical);
1833
+ await dir.getFileHandle(name2, { create: true });
1834
+ const now = Date.now();
1835
+ const made2 = `${parent.path === "/" ? "" : parent.path}/${name2}`;
1836
+ await this.stampNew(made2, owner, false);
1837
+ this.touch(now, parent.path, made2);
1838
+ });
1839
+ }
1840
+ async handleExists(dir, name2) {
1841
+ try {
1842
+ await dir.getDirectoryHandle(name2);
1843
+ return true;
1844
+ } catch (err) {
1845
+ if (err?.name === "TypeMismatchError") return true;
1846
+ if (err?.name === "NotFoundError") return false;
1847
+ throw err;
1848
+ }
1669
1849
  }
1670
1850
  async rm(path, options = {}) {
1671
1851
  let found;
@@ -1676,8 +1856,8 @@ var OpfsFs = class {
1676
1856
  if (options.force && error.code === "ENOENT") return;
1677
1857
  throw error;
1678
1858
  }
1679
- this.refuse(found.path, true);
1680
1859
  if (options.file && found.handle?.kind === "directory") throw fsError("EISDIR", path);
1860
+ this.refuse(found.path, true);
1681
1861
  if (found.handle) {
1682
1862
  const [dir, name2] = await this.parent(found.path);
1683
1863
  try {
@@ -1688,22 +1868,23 @@ var OpfsFs = class {
1688
1868
  } catch (err) {
1689
1869
  throw translate(err, path);
1690
1870
  }
1871
+ const now = Date.now();
1691
1872
  const file = found.handle.kind === "file";
1692
1873
  if (!file || this.remote?.watched()) this.changed(found.path);
1693
1874
  else this.forget(found.path);
1694
1875
  this.movePending(found.path);
1695
1876
  if (file && this.meta.absent?.(found.path)) {
1696
- this.touch(parentOf3(found.path));
1877
+ this.touch(now, parentOf3(found.path));
1697
1878
  return;
1698
1879
  }
1699
1880
  if (file && this.meta.removeFile) {
1700
1881
  await this.meta.removeFile(found.path);
1701
- this.touch(parentOf3(found.path));
1882
+ this.touch(now, parentOf3(found.path));
1702
1883
  return;
1703
1884
  }
1704
1885
  }
1705
1886
  await this.meta.remove([found.path]);
1706
- this.touch(parentOf3(found.path));
1887
+ this.touch(Date.now(), parentOf3(found.path));
1707
1888
  }
1708
1889
  links() {
1709
1890
  return false;
@@ -1711,11 +1892,14 @@ var OpfsFs = class {
1711
1892
  async link(from, to, follow2) {
1712
1893
  await (follow2 ? this.stat(from) : this.lstat(from));
1713
1894
  if (!(await this.stat(parentOf3(to))).isDirectory) throw fsError("ENOTDIR", to);
1714
- if (await this.lstat(to).then(
1895
+ const taken = await this.lstat(to).then(
1715
1896
  () => true,
1716
- () => false
1717
- ))
1718
- throw fsError("EEXIST", to);
1897
+ (err) => {
1898
+ if (missing2(err)) return false;
1899
+ throw err;
1900
+ }
1901
+ );
1902
+ if (taken) throw fsError("EEXIST", to);
1719
1903
  throw fsError("EPERM", `${from}: this file system has no hard links`);
1720
1904
  }
1721
1905
  async rename(from, to) {
@@ -1755,7 +1939,7 @@ var OpfsFs = class {
1755
1939
  if (source.link) {
1756
1940
  if (target?.handle) await this.rm(destination);
1757
1941
  await this.meta.move(source.path, destination, carry);
1758
- this.touch(parentOf3(source.path), parent.path);
1942
+ this.touch(Date.now(), parentOf3(source.path), parent.path);
1759
1943
  return;
1760
1944
  }
1761
1945
  const dir = parent.handle;
@@ -1778,7 +1962,7 @@ var OpfsFs = class {
1778
1962
  }
1779
1963
  if (copied) await this.restamp(destination, timed2);
1780
1964
  this.changed(source.path, destination);
1781
- this.touch(parentOf3(source.path), parent.path);
1965
+ this.touch(Date.now(), parentOf3(source.path), parent.path);
1782
1966
  }
1783
1967
  async modified(path) {
1784
1968
  const handle = await this.handle(path);
@@ -1848,7 +2032,7 @@ var OpfsFs = class {
1848
2032
  ...fields
1849
2033
  }))
1850
2034
  );
1851
- this.touch(parent.path);
2035
+ this.touch(Date.now(), parent.path);
1852
2036
  }
1853
2037
  async realpath(path, follow2) {
1854
2038
  return (await this.locate(path, follow2)).path;
@@ -2280,6 +2464,9 @@ async function present(fs, path, directory) {
2280
2464
  }
2281
2465
  async function finalOf(fs, walked, want, options) {
2282
2466
  const perm = walked.perm;
2467
+ if (options.follow === false && perm.link !== void 0) {
2468
+ return [{ ...perm, mode: 511 }, false];
2469
+ }
2283
2470
  if ((want & X_OK) === 0 || options.directory !== void 0) {
2284
2471
  return [perm, options.directory !== false];
2285
2472
  }
@@ -2451,6 +2638,18 @@ async function chmodMode(g, path, mode) {
2451
2638
  if (!g.dac.enforcing || !perm || g.cred.euid === 0 || inGroup(g.cred, perm.gid)) return mode;
2452
2639
  return mode & ~S_ISGID3;
2453
2640
  }
2641
+ function heldChmodMode(g, path, st, mode) {
2642
+ const perm = permOf(st);
2643
+ if (g.cred.euid !== 0 && perm.uid !== g.cred.euid) deny(g, "fchmod", path, "EPERM");
2644
+ if (!g.dac.enforcing || g.cred.euid === 0 || inGroup(g.cred, perm.gid)) return mode;
2645
+ return mode & ~S_ISGID3;
2646
+ }
2647
+ function heldRetimable(g, path, st, now) {
2648
+ const perm = permOf(st);
2649
+ if (g.cred.euid === 0 || perm.uid === g.cred.euid) return;
2650
+ if (!now) deny(g, "futimens", path, "EPERM");
2651
+ else if (!permits(g.cred, perm, W_OK, false)) deny(g, "futimens", path);
2652
+ }
2454
2653
  async function retimable(g, op, path, now = false) {
2455
2654
  const follow2 = op !== "lutimes";
2456
2655
  if (!now) return void await ownerOnly(g, op, path, follow2);
@@ -2902,6 +3101,17 @@ var FdTable = class _FdTable {
2902
3101
  this.status.delete(fd);
2903
3102
  void Promise.resolve(previous?.release()).catch(() => void 0);
2904
3103
  }
3104
+ async replaceAt(fd, file) {
3105
+ if (fd < 0 || fd >= _FdTable.MAX_FDS) {
3106
+ void Promise.resolve(file.release()).catch(() => void 0);
3107
+ throw new KernelError("EBADF");
3108
+ }
3109
+ const previous = this.fds.get(fd);
3110
+ this.fds.set(fd, file);
3111
+ this.cloexec.delete(fd);
3112
+ this.status.delete(fd);
3113
+ await Promise.resolve(previous?.release()).catch(() => void 0);
3114
+ }
2905
3115
  dup(fd, min = 0) {
2906
3116
  return this.install(this.get(fd).retain(), min);
2907
3117
  }
@@ -2910,6 +3120,11 @@ var FdTable = class _FdTable {
2910
3120
  if (oldFd !== newFd) this.installAt(newFd, file.retain());
2911
3121
  return newFd;
2912
3122
  }
3123
+ async dup2Clipboard(oldFd, newFd) {
3124
+ const file = this.get(oldFd);
3125
+ if (oldFd !== newFd) await this.replaceAt(newFd, file.retain());
3126
+ return newFd;
3127
+ }
2913
3128
  close(fd) {
2914
3129
  const file = this.get(fd);
2915
3130
  this.fds.delete(fd);
@@ -3969,7 +4184,7 @@ function homeOf(home) {
3969
4184
  if (typeof home !== "string" || !home.startsWith("/") || /[:\r\n]/.test(home)) {
3970
4185
  throw fsError("EINVAL", `not a home directory: ${String(home)}`);
3971
4186
  }
3972
- return home;
4187
+ return normalizePath(home);
3973
4188
  }
3974
4189
  function freeId(users, groups) {
3975
4190
  const taken = /* @__PURE__ */ new Set([...users.map((r) => r.uid), ...groups.map((r) => r.gid)]);
@@ -4750,6 +4965,7 @@ var ClipboardChannel = class {
4750
4965
  size = 0;
4751
4966
  wrote = false;
4752
4967
  failed = false;
4968
+ aborted = false;
4753
4969
  constructor(broker, access2, stamp) {
4754
4970
  this.broker = broker;
4755
4971
  this.access = access2;
@@ -4804,11 +5020,14 @@ var ClipboardChannel = class {
4804
5020
  poll() {
4805
5021
  return READY;
4806
5022
  }
5023
+ abort() {
5024
+ this.aborted = true;
5025
+ }
4807
5026
  async close() {
4808
5027
  this.snapshot = void 0;
4809
5028
  this.taken = void 0;
4810
5029
  const commits = this.access === "write" || this.access === "both" && this.wrote;
4811
- if (!commits || this.failed) return;
5030
+ if (!commits || this.failed || this.aborted) return;
4812
5031
  const bytes = joined2(this.chunks, this.size);
4813
5032
  this.chunks = [];
4814
5033
  this.size = 0;
@@ -5273,15 +5492,15 @@ function frame(opcode, payload) {
5273
5492
  const n = payload.length;
5274
5493
  const size = n < 126 ? 2 : n < 65536 ? 4 : 10;
5275
5494
  const out = new Uint8Array(size + n);
5276
- const view = new DataView(out.buffer);
5495
+ const view2 = new DataView(out.buffer);
5277
5496
  out[0] = 128 | opcode;
5278
5497
  if (n < 126) out[1] = n;
5279
5498
  else if (n < 65536) {
5280
5499
  out[1] = 126;
5281
- view.setUint16(2, n);
5500
+ view2.setUint16(2, n);
5282
5501
  } else {
5283
5502
  out[1] = 127;
5284
- view.setBigUint64(2, BigInt(n));
5503
+ view2.setBigUint64(2, BigInt(n));
5285
5504
  }
5286
5505
  out.set(payload, size);
5287
5506
  return out;
@@ -5403,9 +5622,9 @@ var WsReader = class {
5403
5622
  if (short < 126) return short;
5404
5623
  const ext = new Uint8Array(short === 126 ? 2 : 8);
5405
5624
  await fill(this.incoming, ext, signal);
5406
- const view = new DataView(ext.buffer);
5407
- if (short === 126) return view.getUint16(0);
5408
- const long = view.getBigUint64(0);
5625
+ const view2 = new DataView(ext.buffer);
5626
+ if (short === 126) return view2.getUint16(0);
5627
+ const long = view2.getBigUint64(0);
5409
5628
  return long > BigInt(this.max) ? this.max + 1 : Number(long);
5410
5629
  }
5411
5630
  take(opcode, fin, payload) {
@@ -8944,6 +9163,15 @@ function parseSize(text) {
8944
9163
  if (!match) throw errnoError("EINVAL", `not a size: ${text}`);
8945
9164
  return Math.floor(Number(match[1]) * UNITS[match[2].toLowerCase()]);
8946
9165
  }
9166
+ function linkedTo(mount, path) {
9167
+ const own = mount.attrs.get(path)?.value;
9168
+ const ino = own?.kind === "directory" ? void 0 : own?.ino;
9169
+ return (attr2) => {
9170
+ if (!attr2 || attr2.kind === "directory") return false;
9171
+ if (ino !== void 0) return attr2.ino === ino;
9172
+ return own?.kind !== "directory" && (attr2.nlink ?? 1) > 1;
9173
+ };
9174
+ }
8947
9175
  var within6 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
8948
9176
  var HeldSet = class extends Set {
8949
9177
  idle;
@@ -9385,10 +9613,13 @@ var MountTable = class {
9385
9613
  }
9386
9614
  forget(mount, path) {
9387
9615
  mount.epoch++;
9388
- for (const key of [...mount.attrs.keys()]) if (within6(key, path)) mount.attrs.delete(key);
9616
+ const linked2 = mount.caps.links ? linkedTo(mount, path) : () => false;
9617
+ for (const [key, cached] of [...mount.attrs])
9618
+ if (within6(key, path) || linked2(cached.value)) mount.attrs.delete(key);
9389
9619
  mount.lists.delete(path);
9390
9620
  mount.lists.delete(parentOf7(path));
9391
- for (const key of [...mount.lists.keys()]) if (within6(key, path)) mount.lists.delete(key);
9621
+ for (const [key, cached] of [...mount.lists])
9622
+ if (within6(key, path) || cached.value.some((e) => linked2(e.attr))) mount.lists.delete(key);
9392
9623
  }
9393
9624
  async getattr(mount, path, rel2, settled = false) {
9394
9625
  for (let round = 1; ; round++) {
@@ -9437,12 +9668,64 @@ function noteExtent(extents, from, to) {
9437
9668
  last[1] = Math.max(last[1], to);
9438
9669
  } else extents.push([from, to]);
9439
9670
  }
9440
- var MountFs = class {
9671
+ var RELINKED = [
9672
+ ["readFileBuffer", [0]],
9673
+ ["writeFile", [0]],
9674
+ ["pread", [0]],
9675
+ ["pwrite", [0]],
9676
+ ["truncate", [0]],
9677
+ ["lstat", [0]],
9678
+ ["stat", [0]],
9679
+ ["readdir", [0]],
9680
+ ["readdirStat", [0]],
9681
+ ["mkdir", [0]],
9682
+ ["rm", [0]],
9683
+ ["rename", [0, 1]],
9684
+ ["symlink", [1]],
9685
+ ["readlink", [0]],
9686
+ ["chown", [0]],
9687
+ ["chmod", [0]],
9688
+ ["utimes", [0]],
9689
+ ["lutimes", [0]]
9690
+ ];
9691
+ var MountFs = class _MountFs {
9441
9692
  table;
9442
9693
  base;
9443
- constructor(table, base) {
9694
+ rejectLinks;
9695
+ constructor(table, base, rejectLinks = false) {
9444
9696
  this.table = table;
9445
9697
  this.base = base;
9698
+ this.rejectLinks = rejectLinks;
9699
+ }
9700
+ static {
9701
+ const proto = _MountFs.prototype;
9702
+ for (const [name2, at] of RELINKED) {
9703
+ const run2 = proto[name2];
9704
+ proto[name2] = async function(...args) {
9705
+ try {
9706
+ return await run2.apply(this, args);
9707
+ } catch (err) {
9708
+ const moved2 = await this.relinked(err, args, at);
9709
+ if (!moved2) throw err;
9710
+ return this[name2](...moved2);
9711
+ }
9712
+ };
9713
+ }
9714
+ }
9715
+ async relinked(err, args, at) {
9716
+ if (err?.code !== "ENOTDIR") return void 0;
9717
+ const moved2 = [...args];
9718
+ let changed = false;
9719
+ for (const i of at) {
9720
+ const path = args[i];
9721
+ if (typeof path !== "string" || !this.at(path)) continue;
9722
+ const named = await this.named(path).catch(() => void 0);
9723
+ if (named === void 0 || named === normalizePath(path)) continue;
9724
+ if (this.rejectLinks) throw errnoError("ELOOP", path);
9725
+ moved2[i] = named;
9726
+ changed = true;
9727
+ }
9728
+ return changed ? moved2 : void 0;
9446
9729
  }
9447
9730
  at(path) {
9448
9731
  return this.table.locate(normalizePath(path));
@@ -9493,6 +9776,7 @@ var MountFs = class {
9493
9776
  throw err;
9494
9777
  }
9495
9778
  if (attr2.kind !== "symlink") return real;
9779
+ if (this.rejectLinks) throw errnoError("ELOOP", path);
9496
9780
  if (hops >= MAX_LINKS7) throw errnoError("ELOOP", path);
9497
9781
  const target = await found.mount.conn.call({ op: "readlink", path: found.rel });
9498
9782
  return this.follow(this.base.resolvePath(parentOf7(real), target), hops + 1, create);
@@ -9897,6 +10181,7 @@ var MountFs = class {
9897
10181
  }
9898
10182
  const { mount, rel: rel2 } = found;
9899
10183
  const real = normalizePath(path);
10184
+ if (options.file && rel2 === "/") throw errnoError("EISDIR", real);
9900
10185
  if (rel2 === "/" && options.recursive) await this.rmEach(real, mount, rel2, options);
9901
10186
  if (rel2 === "/") throw errnoError("EBUSY", `${real} is a mount point`);
9902
10187
  this.writable(mount, real);
@@ -10132,8 +10417,22 @@ var MountFs = class {
10132
10417
  this.table.forget(found.mount, real);
10133
10418
  await found.mount.conn.call({ op: "setattr", path: found.rel, mtime: mtime.getTime() });
10134
10419
  }
10420
+ noLinks() {
10421
+ const noLinkBase = this.base.noLinks.call(this.base);
10422
+ return view(this, { rejectLinks: true, base: noLinkBase });
10423
+ }
10135
10424
  };
10136
10425
 
10426
+ // src/fs/dev-removal.ts
10427
+ function inDevTree(path) {
10428
+ return path === "/dev" || path.startsWith("/dev/");
10429
+ }
10430
+ function devRemoval(path, cred) {
10431
+ if (!inDevTree(path)) return void 0;
10432
+ if (cred && cred.euid !== 0) return "EACCES";
10433
+ return path === "/dev" ? "EBUSY" : "EPERM";
10434
+ }
10435
+
10137
10436
  // src/realm/sync-fs-token-registry.ts
10138
10437
  var registry = /* @__PURE__ */ new Map();
10139
10438
  function mintSyncFsToken(entry) {
@@ -10246,6 +10545,22 @@ function plainDirMode(entry, mode) {
10246
10545
  const wanted = mode & 4095 & ~(entry.umask?.() ?? 18);
10247
10546
  return wanted === PLAIN_DIR_MODE ? void 0 : wanted;
10248
10547
  }
10548
+ function plainParentsMode(entry) {
10549
+ if (entry.cred) return void 0;
10550
+ const wanted = 511 & ~(entry.umask?.() ?? 18) | 192;
10551
+ return wanted === PLAIN_DIR_MODE ? void 0 : wanted;
10552
+ }
10553
+ async function missingParents(fs, path, mode) {
10554
+ const parts2 = path.split("/").filter(Boolean);
10555
+ const dirs = [];
10556
+ for (let i = 1; i < parts2.length; i++) {
10557
+ const p = `/${parts2.slice(0, i).join("/")}`;
10558
+ if (!await fs.exists(p)) dirs.push(p);
10559
+ }
10560
+ return async () => {
10561
+ for (const p of dirs) await fs.chmod(p, mode);
10562
+ };
10563
+ }
10249
10564
  function mkdirOptions(entry, mode = 511) {
10250
10565
  const owner = ownerFor2(entry, mode);
10251
10566
  if (!owner) return { recursive: true };
@@ -10257,23 +10572,39 @@ async function mkdirExclusive(entry, path, mode) {
10257
10572
  await entry.fs.mkdir(path, owner ? { owner } : {});
10258
10573
  }
10259
10574
  async function chown(entry, path, req) {
10575
+ const real = req.handle !== void 0 ? await keptOr(entry, path, req.handle) : path;
10260
10576
  const cred = entry.cred?.();
10261
10577
  if (!cred) {
10262
- await (req.op === "chown" ? entry.fs.stat(path) : entry.fs.lstat(path));
10578
+ await (req.op === "chown" ? entry.fs.stat(real) : entry.fs.lstat(real));
10263
10579
  return done;
10264
10580
  }
10265
- await chownAs(entry.fs, cred, path, {
10581
+ await chownAs(entry.fs, cred, real, {
10266
10582
  ...req.uid !== void 0 ? { uid: req.uid } : {},
10267
10583
  ...req.gid !== void 0 ? { gid: req.gid } : {},
10268
10584
  follow: req.op === "chown"
10269
10585
  });
10270
10586
  return done;
10271
10587
  }
10588
+ async function runMkdir(entry, path, req) {
10589
+ const { fs } = entry;
10590
+ const plain = plainDirMode(entry, req.mode);
10591
+ const parentsMode = !req.exclusive ? plainParentsMode(entry) : void 0;
10592
+ const fixParents = parentsMode !== void 0 ? await missingParents(fs, path, parentsMode) : void 0;
10593
+ const fresh2 = plain !== void 0 && !await fs.exists(path);
10594
+ await (req.exclusive ? mkdirExclusive(entry, path, req.mode) : fs.mkdir(path, mkdirOptions(entry, req.mode)));
10595
+ await fixParents?.();
10596
+ if (fresh2) await fs.chmod(path, plain);
10597
+ return done;
10598
+ }
10599
+ function readSource(entry, path) {
10600
+ const plain = () => entry.fs.readFileBuffer(path);
10601
+ return entry.source ? entry.source.read(path, plain) : plain();
10602
+ }
10272
10603
  async function run(entry, path, req) {
10273
10604
  const { fs, cwd } = entry;
10274
10605
  switch (req.op) {
10275
10606
  case "read":
10276
- return { ok: true, kind: "bytes", bytes: await fs.readFileBuffer(path) };
10607
+ return { ok: true, kind: "bytes", bytes: await readSource(entry, path) };
10277
10608
  case "write":
10278
10609
  await fs.writeFile(path, req.body ?? new Uint8Array(0), ownerFor2(entry, 438));
10279
10610
  return done;
@@ -10287,13 +10618,8 @@ async function run(entry, path, req) {
10287
10618
  return json2(await fs.readdir(path));
10288
10619
  case "readdir-stat":
10289
10620
  return json2(await readdirStat(fs, path));
10290
- case "mkdir": {
10291
- const plain = plainDirMode(entry, req.mode);
10292
- const fresh2 = plain !== void 0 && !await fs.exists(path);
10293
- await (req.exclusive ? mkdirExclusive(entry, path, req.mode) : fs.mkdir(path, mkdirOptions(entry, req.mode)));
10294
- if (fresh2) await fs.chmod(path, plain);
10295
- return done;
10296
- }
10621
+ case "mkdir":
10622
+ return runMkdir(entry, path, req);
10297
10623
  case "rm":
10298
10624
  await fs.rm(path, { recursive: true });
10299
10625
  return done;
@@ -10319,7 +10645,7 @@ async function run(entry, path, req) {
10319
10645
  return done;
10320
10646
  case "create":
10321
10647
  await createExclusive(fs, path, ownerFor2(entry, req.mode ?? 438));
10322
- entry.created?.add(path);
10648
+ if (req.opening) entry.created?.add(path);
10323
10649
  entry.fresh?.(path);
10324
10650
  return done;
10325
10651
  case "readlink":
@@ -10330,10 +10656,10 @@ async function run(entry, path, req) {
10330
10656
  return done;
10331
10657
  }
10332
10658
  case "utimes":
10333
- await fs.utimes(path, new Date(req.atimeMs ?? 0), new Date(req.mtimeMs ?? 0));
10659
+ await fs.utimes(path, ...await timesOf(entry, path, req));
10334
10660
  return done;
10335
10661
  case "lutimes":
10336
- await lutimesOf(fs, path, new Date(req.atimeMs ?? 0), new Date(req.mtimeMs ?? 0));
10662
+ await lutimesOf(fs, path, ...await timesOf(entry, path, req));
10337
10663
  return done;
10338
10664
  case "chown":
10339
10665
  case "lchown":
@@ -10346,6 +10672,17 @@ async function run(entry, path, req) {
10346
10672
  return { ok: false, errno: "EINVAL", message: `sync-fs: unknown op '${req.op}'` };
10347
10673
  }
10348
10674
  }
10675
+ async function timesOf(entry, path, req) {
10676
+ const given = [new Date(req.atimeMs ?? 0), new Date(req.mtimeMs ?? 0)];
10677
+ const g = req.now === true ? guardOf(entry) : void 0;
10678
+ if (!g || g.cred.euid === 0) return given;
10679
+ const look = req.op === "lutimes" ? entry.fs.lstat : entry.fs.stat;
10680
+ const st = await look.call(entry.fs, path).catch(() => void 0);
10681
+ if (!st || (st.uid ?? 0) === g.cred.euid) return given;
10682
+ const now = /* @__PURE__ */ new Date();
10683
+ const kept = (ms, stored) => ms === stored.getTime() ? stored : now;
10684
+ return [kept(req.atimeMs, st.atime), kept(req.mtimeMs, st.mtime)];
10685
+ }
10349
10686
  function guardOf(entry) {
10350
10687
  const cred = entry.dac && entry.cred?.();
10351
10688
  if (!cred || !entry.dac) return void 0;
@@ -10355,41 +10692,82 @@ function guardOf(entry) {
10355
10692
  var FOLLOWING = /* @__PURE__ */ new Set(["exists", "stat", "chown", "statfs"]);
10356
10693
  var LOOKS = /* @__PURE__ */ new Set(["exists", "stat", "lstat"]);
10357
10694
  var NOT_FOLLOWING = /* @__PURE__ */ new Set(["lstat", "readlink", "lchown"]);
10695
+ async function walkSafe(g, path, follow2) {
10696
+ try {
10697
+ const walked = await walk(g.fs, g.cred, path, follow2, g.links);
10698
+ return walked.path;
10699
+ } catch {
10700
+ return path;
10701
+ }
10702
+ }
10358
10703
  async function guard(g, path, req, cwd) {
10359
10704
  const { op } = req;
10360
- if (FOLLOWING.has(op) || NOT_FOLLOWING.has(op)) return searchable(g, op, path, FOLLOWING.has(op));
10705
+ if (FOLLOWING.has(op) || NOT_FOLLOWING.has(op)) {
10706
+ const walked = await need(g, op, path, 0, { follow: FOLLOWING.has(op) });
10707
+ return { path: walked.path };
10708
+ }
10361
10709
  const other = () => g.fs.resolvePath(cwd, req.arg2 ?? "");
10362
10710
  switch (op) {
10363
10711
  case "readdir":
10364
- case "readdir-stat":
10365
- return listable(g, op, path);
10366
- case "mkdir":
10367
- return makeIn(g, op, path);
10712
+ case "readdir-stat": {
10713
+ await listable(g, op, path);
10714
+ const w = await walkSafe(g, path, true);
10715
+ return { path: w };
10716
+ }
10717
+ case "mkdir": {
10718
+ await makeIn(g, op, path);
10719
+ return { path: await walkSafe(g, path, false) };
10720
+ }
10368
10721
  case "create":
10369
10722
  case "symlink":
10370
- return inParent(g, op, path);
10723
+ await inParent(g, op, path);
10724
+ return { path: await walkSafe(g, path, op === "create") };
10371
10725
  case "unlink":
10372
- case "rmdir":
10373
- return inParent(g, op, path, true);
10374
- case "rm":
10375
- return removable(g, op, path);
10376
- case "rename":
10377
- return renamable(g, path, other());
10378
- case "link":
10379
- return linkable(g, path, other(), req.mode === 1);
10726
+ case "rmdir": {
10727
+ await inParent(g, op, path, true);
10728
+ const w = await walkSafe(g, path, false);
10729
+ return { path: w };
10730
+ }
10731
+ case "rm": {
10732
+ await removable(g, op, path);
10733
+ const w = await walkSafe(g, path, false);
10734
+ return { path: w };
10735
+ }
10736
+ case "rename": {
10737
+ const to = other();
10738
+ await renamable(g, path, to);
10739
+ const fromWalked = await walkSafe(g, path, false);
10740
+ const toWalked = await walkSafe(g, to, false);
10741
+ return { path: fromWalked, arg2: toWalked };
10742
+ }
10743
+ case "link": {
10744
+ const to = other();
10745
+ const follow2 = req.mode === 1;
10746
+ await linkable(g, path, to, follow2);
10747
+ const fromWalked = await walkSafe(g, path, follow2);
10748
+ const toWalked = await walkSafe(g, to, false);
10749
+ return { path: fromWalked, arg2: toWalked };
10750
+ }
10380
10751
  case "utimes":
10381
- case "lutimes":
10382
- return retimable(g, op, path, req.now === true);
10752
+ case "lutimes": {
10753
+ await retimable(g, op, path, req.now === true);
10754
+ const follow2 = op === "utimes";
10755
+ const w = await walkSafe(g, path, follow2);
10756
+ return { path: w };
10757
+ }
10383
10758
  case "read":
10384
10759
  case "pread":
10385
- return req.handle === void 0 ? readable(g, op, path) : void 0;
10760
+ if (req.handle === void 0) await readable(g, op, path);
10761
+ return { path: await walkSafe(g, path, true) };
10386
10762
  case "pwrite":
10387
10763
  case "truncate":
10388
- return req.handle === void 0 ? fileWritable(g, op, path) : void 0;
10764
+ if (req.handle === void 0) await fileWritable(g, op, path);
10765
+ return { path: await walkSafe(g, path, true) };
10389
10766
  case "write":
10390
- return req.handle === void 0 && req.mode !== 1 ? writable(g, op, path) : void 0;
10767
+ if (req.handle === void 0 && req.mode !== 1) await writable(g, op, path);
10768
+ return { path: await walkSafe(g, path, true) };
10391
10769
  default:
10392
- return void 0;
10770
+ return { path: await walkSafe(g, path, true) };
10393
10771
  }
10394
10772
  }
10395
10773
  async function opening(entry, path, mode) {
@@ -10406,6 +10784,7 @@ async function holdOp(entry, path, req) {
10406
10784
  if (on && open) {
10407
10785
  await opening(entry, path, mode);
10408
10786
  const real = await entry.fs.realpath?.(path, true).catch(() => path);
10787
+ await entry.source?.open(path, real);
10409
10788
  return json2(entry.hold?.(path, on, open, real) ?? null);
10410
10789
  }
10411
10790
  entry.hold?.(path, on, open, void 0, req.handle);
@@ -10458,15 +10837,16 @@ async function mayChange(entry, path, req) {
10458
10837
  }
10459
10838
  async function guarded(entry, path, req) {
10460
10839
  const g = guardOf(entry);
10461
- if (!g) return void 0;
10462
- if (LOOKS.has(req.op) && await entry.holdsKept?.(path)) return void 0;
10840
+ if (!g) return {};
10841
+ if (LOOKS.has(req.op) && await entry.holdsKept?.(path)) return {};
10463
10842
  try {
10464
10843
  await mayChange(entry, path, req);
10465
- await guard(g, path, req, entry.cwd);
10466
- return void 0;
10844
+ const paths = await guard(g, path, req, entry.cwd);
10845
+ return { paths };
10467
10846
  } catch (err) {
10468
10847
  const refusal = err.code === "EACCES";
10469
- return req.op === "exists" && refusal ? json2(false) : toErrno(err);
10848
+ const refused2 = req.op === "exists" && refusal ? json2(false) : toErrno(err);
10849
+ return { refused: refused2 };
10470
10850
  }
10471
10851
  }
10472
10852
  async function createExclusive(fs, path, owner) {
@@ -10492,6 +10872,7 @@ async function ranged(fs, path, req) {
10492
10872
  else await ops.truncate(path, req.length ?? 0);
10493
10873
  return done;
10494
10874
  }
10875
+ var CHOWN_OPS = /* @__PURE__ */ new Set(["chown", "lchown"]);
10495
10876
  var UNLINKED_OPS = /* @__PURE__ */ new Set(["read", "pread", "pwrite", "truncate"]);
10496
10877
  var OWN_OPS = /* @__PURE__ */ new Set(["rm", "unlink", "rmdir", "rename", "link"]);
10497
10878
  var TWO_PATHS = /* @__PURE__ */ new Set(["rename", "link"]);
@@ -10556,7 +10937,7 @@ async function rmPermitted(entry, path, req, refused2) {
10556
10937
  const st = await entry.fs.lstat(path).catch(() => void 0);
10557
10938
  if (!st?.isDirectory) return refused2;
10558
10939
  const unlisted = await guarded(entry, path, { ...req, op: "readdir" });
10559
- if (unlisted) return unlisted;
10940
+ if ("refused" in unlisted) return unlisted.refused;
10560
10941
  const names = await entry.fs.readdir(path).catch(() => void 0);
10561
10942
  if (!names) return refused2;
10562
10943
  let first;
@@ -10568,31 +10949,64 @@ async function rmPermitted(entry, path, req, refused2) {
10568
10949
  if (!child.ok) first ??= child;
10569
10950
  }
10570
10951
  if (first) return first;
10571
- const again = await guarded(entry, path, req);
10572
- return again ?? await run(entry, path, req);
10952
+ const g = guardOf(entry);
10953
+ if (g) {
10954
+ try {
10955
+ await inParent(g, "rm", path, true);
10956
+ } catch (err) {
10957
+ return toErrno(err);
10958
+ }
10959
+ }
10960
+ return await run(entry, path, req);
10573
10961
  }
10962
+ async function byHandle(entry, path, req) {
10963
+ if (req.op === "kept") return json2(entry.keptFor?.(req.handle ?? -1) !== void 0);
10964
+ if (req.op === "stat" && req.handle !== void 0) return keptStat(entry, req);
10965
+ if (CHOWN_OPS.has(req.op) && entry.keptFor?.(req.handle ?? -1)) return chown(entry, path, req);
10966
+ return void 0;
10967
+ }
10968
+ async function guardAndResolve(entry, path, req) {
10969
+ const result = await guarded(entry, path, req);
10970
+ if ("refused" in result) return result.refused;
10971
+ const { noLinks } = entry;
10972
+ const { paths } = result;
10973
+ if (entry.links === void 0 || !noLinks || !paths) return { path, entry };
10974
+ return {
10975
+ path: paths.path,
10976
+ ...paths.arg2 ? { arg2: paths.arg2 } : {},
10977
+ entry: { ...entry, fs: noLinks }
10978
+ };
10979
+ }
10980
+ var REMOVALS = /* @__PURE__ */ new Set(["unlink", "rmdir", "rm"]);
10574
10981
  async function dispatchOne(entry, req) {
10575
10982
  const { fs, cwd } = entry;
10576
10983
  try {
10577
10984
  const path = fs.resolvePath(cwd, req.path);
10578
- if (req.op === "kept") return json2(entry.keptFor?.(req.handle ?? -1) !== void 0);
10579
- if (req.op === "stat" && req.handle !== void 0) return await keptStat(entry, req);
10985
+ const device = REMOVALS.has(req.op) ? devRemoval(path, entry.cred?.()) : void 0;
10986
+ if (device) return { ok: false, errno: device, message: `${path}: a device entry stays` };
10987
+ const held = await byHandle(entry, path, req);
10988
+ if (held) return held;
10580
10989
  if (req.op === "access") return await accessOp(entry, path, req);
10581
10990
  if (req.op === "hold") return await holdOp(entry, path, req);
10582
- const refused2 = await guarded(entry, path, req);
10583
- if (refused2 && req.op === "rm" && !refused2.ok && DENIED.has(refused2.errno)) {
10584
- return await rmPermitted(entry, path, req, refused2);
10585
- }
10586
- if (refused2) return refused2;
10587
- if (req.op === "statfs") return json2(await usageAt(entry, path) ?? await storageUsage());
10588
- if (entry.revoked?.(path)) return { ok: false, errno: "EIO", message: `${path} was unmounted` };
10991
+ const resolved2 = await guardAndResolve(entry, path, req);
10992
+ if (!("entry" in resolved2)) {
10993
+ if (req.op === "rm" && !resolved2.ok && DENIED.has(resolved2.errno))
10994
+ return await rmPermitted(entry, path, req, resolved2);
10995
+ return resolved2;
10996
+ }
10997
+ const { path: resolvedPath, arg2: resolvedArg2, entry: opEntry } = resolved2;
10998
+ const opReq = resolvedArg2 ? { ...req, arg2: resolvedArg2 } : req;
10999
+ if (req.op === "statfs")
11000
+ return json2(await usageAt(entry, resolvedPath) ?? await storageUsage());
11001
+ if (entry.revoked?.(resolvedPath))
11002
+ return { ok: false, errno: "EIO", message: `${resolvedPath} was unmounted` };
10589
11003
  if (req.op === "write" && req.mode === 1 || UNLINKED_OPS.has(req.op)) {
10590
- const serve = async () => run(entry, await keptOr(entry, path, req.handle), req);
10591
- return await (entry.gate ? entry.gate(path, serve) : serve());
11004
+ const serve = async () => run(opEntry, await keptOr(opEntry, resolvedPath, req.handle), opReq);
11005
+ return await (entry.gate ? entry.gate(resolvedPath, serve) : serve());
10592
11006
  }
10593
- if (!entry.own || !OWN_OPS.has(req.op)) return await run(entry, path, req);
10594
- const target = TWO_PATHS.has(req.op) ? fs.resolvePath(cwd, req.arg2 ?? "") : path;
10595
- return await owned2(entry, entry.own, req, path, target);
11007
+ if (!entry.own || !OWN_OPS.has(req.op)) return await run(opEntry, resolvedPath, opReq);
11008
+ const target = resolvedArg2 ?? (TWO_PATHS.has(req.op) ? fs.resolvePath(cwd, req.arg2 ?? "") : resolvedPath);
11009
+ return await owned2(opEntry, entry.own, opReq, resolvedPath, target);
10596
11010
  } catch (err) {
10597
11011
  return toErrno(err);
10598
11012
  }
@@ -10620,6 +11034,15 @@ var SAB_I_KILLED = 13;
10620
11034
  var SAB_I_ASYNC = 14;
10621
11035
  var SAB_I_STOP = 15;
10622
11036
  var SAB_I_BLOCKED = 16;
11037
+ var SAB_I_BLOCKED_HI = 17;
11038
+ function loadBlocked(header3) {
11039
+ return [Atomics.load(header3, SAB_I_BLOCKED), Atomics.load(header3, SAB_I_BLOCKED_HI)];
11040
+ }
11041
+ function storeBlocked(header3, [low, high]) {
11042
+ Atomics.store(header3, SAB_I_BLOCKED_HI, high);
11043
+ Atomics.store(header3, SAB_I_BLOCKED, low);
11044
+ }
11045
+ var anyBlocked = (mask) => mask !== void 0 && (mask[0] !== 0 || mask[1] !== 0);
10623
11046
  var U32 = 2 ** 32;
10624
11047
  function storeU53(header3, lo, hi, value) {
10625
11048
  Atomics.store(header3, hi, Math.floor(value / U32));
@@ -11146,7 +11569,7 @@ function mountTable(mounts = []) {
11146
11569
  }
11147
11570
 
11148
11571
  // src/version.ts
11149
- var KERNEL_VERSION = true ? "1.92.0" : "0.0.0-dev";
11572
+ var KERNEL_VERSION = true ? "1.93.0" : "0.0.0-dev";
11150
11573
  var OS_RELEASE = "6.0.0-slicc";
11151
11574
  function procVersion() {
11152
11575
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -11846,21 +12269,28 @@ function bodyOf(incoming, head, method, signal) {
11846
12269
  var KernelLoopback = class {
11847
12270
  net;
11848
12271
  cred;
11849
- constructor(net, cred) {
12272
+ stamp;
12273
+ constructor(net, cred, stamp) {
11850
12274
  this.net = net;
11851
12275
  this.cred = cred;
12276
+ this.stamp = stamp;
11852
12277
  }
11853
12278
  port(url) {
11854
12279
  return kernelLoopbackPort(url);
11855
12280
  }
11856
- connect(url, port, signal) {
12281
+ async connect(url, port, signal) {
11857
12282
  if (url.protocol === "https:" || url.protocol === "wss:") {
11858
12283
  throw errno("ENOTSUP", `TLS to the kernel's loopback is not supported: ${url.origin}`);
11859
12284
  }
11860
12285
  signal.throwIfAborted();
11861
12286
  const socket = this.net.socket("inet");
11862
12287
  try {
11863
- void socket.connect({ family: "inet", host: "127.0.0.1", port }, false, this.cred());
12288
+ void socket.connect(
12289
+ { family: "inet", host: "127.0.0.1", port },
12290
+ false,
12291
+ this.cred(),
12292
+ this.stamp ? await this.stamp() : void 0
12293
+ );
11864
12294
  } catch (err) {
11865
12295
  socket.close();
11866
12296
  const refused2 = err instanceof KernelError && err.code === "ECONNREFUSED";
@@ -11874,7 +12304,7 @@ var KernelLoopback = class {
11874
12304
  const body = req.body ?? (/^(POST|PUT|PATCH)$/i.test(req.method) ? new Uint8Array(0) : void 0);
11875
12305
  const length2 = body ? [["Content-Length", String(body.length)]] : [];
11876
12306
  const head = requestHead(req.method, url, req.headers, [...length2, ["Connection", "close"]]);
11877
- const socket = this.connect(url, port, req.signal);
12307
+ const socket = await this.connect(url, port, req.signal);
11878
12308
  try {
11879
12309
  await writeAll2(socket, body ? concat([head, body]) : head, req.signal);
11880
12310
  const incoming = new Incoming(socket);
@@ -11895,7 +12325,7 @@ var KernelLoopback = class {
11895
12325
  }
11896
12326
  async websocket(req, port) {
11897
12327
  const url = new URL(req.url);
11898
- const socket = this.connect(url, port, req.signal);
12328
+ const socket = await this.connect(url, port, req.signal);
11899
12329
  try {
11900
12330
  const key = btoa(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(16))));
11901
12331
  const extra = [
@@ -11920,6 +12350,9 @@ var KernelLoopback = class {
11920
12350
  if (!upgraded || accept2 !== await acceptFor(key)) {
11921
12351
  throw errno("EPROTO", "the server did not accept the WebSocket key");
11922
12352
  }
12353
+ if (fieldValues(head.headers, "sec-websocket-extensions").length > 0) {
12354
+ throw errno("EPROTO", "the server offered an unsolicited extension");
12355
+ }
11923
12356
  const protocol = fieldValues(head.headers, "sec-websocket-protocol")[0] ?? "";
11924
12357
  return new LoopbackWebSocket(socket, incoming, protocol);
11925
12358
  } catch (err) {
@@ -12019,7 +12452,7 @@ var LoopbackWebSocket = class {
12019
12452
  })();
12020
12453
  }
12021
12454
  async pump(reader, deliver) {
12022
- let close = { code: 1006, reason: "" };
12455
+ let close = { code: 1006, reason: "", wasClean: false };
12023
12456
  try {
12024
12457
  for (; ; ) {
12025
12458
  await this.space();
@@ -12029,14 +12462,14 @@ var LoopbackWebSocket = class {
12029
12462
  else if (event.kind === "binary") deliver(event.bytes);
12030
12463
  else if (event.kind === "ping") this.write(clientFrame(OP_PONG, event.payload));
12031
12464
  else {
12032
- close = { code: event.code, reason: event.reason };
12465
+ close = { code: event.code, reason: event.reason, wasClean: true };
12033
12466
  if (!this.sentClose) this.sendClose(event.code === 1005 ? 1e3 : event.code, "");
12034
12467
  break;
12035
12468
  }
12036
12469
  }
12037
12470
  } catch (err) {
12038
12471
  if (err instanceof WsError) this.sendClose(err.code, err.message);
12039
- close = { code: err instanceof WsError ? err.code : 1006, reason: "" };
12472
+ close = { code: 1006, reason: "", wasClean: false };
12040
12473
  } finally {
12041
12474
  const late = setTimeout(() => this.stop.abort(), CLOSE_WAIT_MS3);
12042
12475
  await this.writes;
@@ -12162,14 +12595,22 @@ var WsHandles = class {
12162
12595
  return typeof next2.value === "string" ? { ok: true, kind: "json", json: { text: next2.value } } : { ok: true, kind: "bytes", bytes: next2.value };
12163
12596
  }
12164
12597
  const closed = await socket.closed;
12165
- return { ok: true, kind: "json", json: { code: closed.code, reason: closed.reason } };
12598
+ return {
12599
+ ok: true,
12600
+ kind: "json",
12601
+ json: { code: closed.code, reason: closed.reason, wasClean: closed.wasClean === true }
12602
+ };
12166
12603
  } catch (e) {
12167
12604
  return fail("ECONNRESET", message(e));
12168
12605
  }
12169
12606
  }
12170
12607
  async closed(socket) {
12171
- const { code, reason } = await socket.closed;
12172
- return { ok: true, kind: "json", json: { code, reason } };
12608
+ const closed = await socket.closed;
12609
+ return {
12610
+ ok: true,
12611
+ kind: "json",
12612
+ json: { code: closed.code, reason: closed.reason, wasClean: closed.wasClean === true }
12613
+ };
12173
12614
  }
12174
12615
  send(socket, req, entry) {
12175
12616
  const data = req.text ?? req.body;
@@ -12209,7 +12650,7 @@ function message2(e) {
12209
12650
  }
12210
12651
  function codeOf2(e) {
12211
12652
  const code = e?.code;
12212
- return typeof code === "string" && /^E[A-Z]+$/.test(code) ? code : "EIO";
12653
+ return typeof code === "string" && /^E[A-Z]+$|^EAI_[A-Z]+$/.test(code) ? code : "EIO";
12213
12654
  }
12214
12655
  async function* once(bytes) {
12215
12656
  yield bytes;
@@ -12292,7 +12733,8 @@ var HttpHandles = class {
12292
12733
  if (port !== void 0) return { ok: false, errno: codeOf2(e), message: text };
12293
12734
  const status = e.status;
12294
12735
  if (text === NO_TRANSPORT || typeof status !== "number" || status === 502) {
12295
- const errno2 = text === NO_TRANSPORT ? "ENETUNREACH" : "ECONNREFUSED";
12736
+ const code = codeOf2(e);
12737
+ const errno2 = text === NO_TRANSPORT ? "ENETUNREACH" : code !== "EIO" ? code : "ECONNREFUSED";
12296
12738
  return { ok: false, errno: errno2, message: text };
12297
12739
  }
12298
12740
  entry.body = once(new TextEncoder().encode(text))[Symbol.asyncIterator]();
@@ -13785,7 +14227,7 @@ function isPtySyscall(req) {
13785
14227
  return req.op.startsWith("pty-");
13786
14228
  }
13787
14229
  function isVfsSyscall(req) {
13788
- return req.op.startsWith("fd-path-") || req.op === "fd-pread" || req.op === "fd-pwrite" || req.op === "fd-resize" || req.op === "fd-vfs-stat";
14230
+ return req.op.startsWith("fd-path-") || req.op === "fd-pread" || req.op === "fd-pwrite" || req.op === "fd-resize" || req.op === "fd-vfs-stat" || req.op === "fd-chmod" || req.op === "fd-chown" || req.op === "fd-utimes";
13789
14231
  }
13790
14232
  function isFdSyscall(req) {
13791
14233
  return req.op.startsWith("fd-");
@@ -13805,6 +14247,9 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
13805
14247
  "fd-pwrite",
13806
14248
  "fd-resize",
13807
14249
  "fd-vfs-stat",
14250
+ "fd-chmod",
14251
+ "fd-chown",
14252
+ "fd-utimes",
13808
14253
  "fd-path-flush",
13809
14254
  "fd-path-unlinking",
13810
14255
  "fd-path-unlinked",
@@ -13835,6 +14280,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
13835
14280
  "fd-tty-owner",
13836
14281
  "fd-open-sealed",
13837
14282
  "fd-open-clipboard",
14283
+ "fd-clipboard-abort",
13838
14284
  "tty-get",
13839
14285
  "tty-set",
13840
14286
  "tty-winsz",
@@ -14180,7 +14626,11 @@ var WasmProcess = class {
14180
14626
  if (!stderr?.write || stderr.clipboard) return;
14181
14627
  const line = `${name2}: /dev/clipboard: ${clipboardLine(err.detail)}
14182
14628
  `;
14183
- await stderr.write(new TextEncoder().encode(line), AbortSignal.timeout(1e3)).catch(() => 0);
14629
+ const timeout = new Promise((r) => setTimeout(() => r(0), 1e3));
14630
+ await Promise.race([
14631
+ stderr.write(new TextEncoder().encode(line), AbortSignal.timeout(1e3)).catch(() => 0),
14632
+ timeout
14633
+ ]);
14184
14634
  }
14185
14635
  async write(fd, body, nonblock = false, cancelled) {
14186
14636
  const file = this.fds.get(fd).file;
@@ -14207,10 +14657,13 @@ var WasmProcess = class {
14207
14657
  await refuseReadonly(this.options.fs, req.path, req.flags, req);
14208
14658
  if (this.writesAt(req)) await this.options.fs.mayChange?.(req.path, !req.exclusive);
14209
14659
  await this.mayOpen(req);
14660
+ const marked = await this.markedPath(req);
14661
+ await this.sourceOpen(marked === void 0 ? req : { ...req, path: marked });
14662
+ const fs = marked === void 0 ? this.options.fs : this.options.noLinks;
14210
14663
  const fd = this.fds.install(heldFile(), 3);
14211
14664
  try {
14212
14665
  const nodes = this.nodes;
14213
- const path = req.orphan ? req.path : req.exclusive ? await nodes.entryKey(req.path) : req.create ? await nodes.targetKey(req.path) : await this.keptOr(await nodes.targetKey(req.path), req.handle);
14666
+ const path = await this.openPath(req, marked);
14214
14667
  if (inProc(path)) {
14215
14668
  this.fds.installAt(fd, await this.procFile(req, path));
14216
14669
  return fd;
@@ -14220,23 +14673,23 @@ var WasmProcess = class {
14220
14673
  const plain = this.plainMode(req.mode);
14221
14674
  if (req.exclusive) {
14222
14675
  await nodes.createExclusive(path, this.newOwner(req.mode));
14223
- if (plain !== void 0) await this.options.fs.chmod?.(path, plain);
14224
- this.fds.installAt(fd, this.openVfsFile(open));
14676
+ if (plain !== void 0) await fs.chmod?.(path, plain);
14677
+ this.fds.installAt(fd, this.openVfsFile(open, void 0, fs));
14225
14678
  } else if (req.create) {
14226
14679
  await nodes.onEntry(path, async () => {
14227
14680
  const marks = this.options.owners || plain !== void 0;
14228
- const fresh2 = marks && !await present2(this.options.fs, path, true);
14229
- this.fds.installAt(fd, this.openVfsFile(open, await nodes.identify(path)));
14681
+ const fresh2 = marks && !await present2(fs, path, true);
14682
+ this.fds.installAt(fd, this.openVfsFile(open, await nodes.identify(path), fs));
14230
14683
  await this.fds.get(fd).file.stat?.();
14231
- if (fresh2 && plain !== void 0) await this.options.fs.chmod?.(path, plain);
14684
+ if (fresh2 && plain !== void 0) await fs.chmod?.(path, plain);
14232
14685
  else if (fresh2) {
14233
- await this.options.fs.chown?.(path, { ...this.newOwner(req.mode), kind: "new" });
14686
+ await fs.chown?.(path, { ...this.newOwner(req.mode), kind: "new" });
14234
14687
  }
14235
14688
  });
14236
14689
  } else {
14237
- this.fds.installAt(fd, this.openVfsFile(open, await nodes.identify(path)));
14690
+ this.fds.installAt(fd, this.openVfsFile(open, await nodes.identify(path), fs));
14238
14691
  }
14239
- if (req.existing && !await present2(this.options.fs, path, true)) {
14692
+ if (req.existing && !await present2(fs, path, true)) {
14240
14693
  throw new KernelError("ENOENT");
14241
14694
  }
14242
14695
  } catch (err) {
@@ -14270,6 +14723,27 @@ var WasmProcess = class {
14270
14723
  writesAt(req) {
14271
14724
  return (req.flags & WRITE_ACCESS) !== 0 || req.truncate === true || req.create === true || req.exclusive === true;
14272
14725
  }
14726
+ async sourceOpen(req) {
14727
+ const { source } = this.options;
14728
+ const inherited2 = req.orphan || req.contents !== void 0 || req.handle !== void 0;
14729
+ if (!source || inherited2 || req.pin !== void 0 || req.exclusive) return;
14730
+ await source.open(req.path);
14731
+ }
14732
+ async markedPath(req) {
14733
+ const { guard: guard2, noLinks } = this.options;
14734
+ const inherited2 = req.orphan || req.contents !== void 0 || req.handle !== void 0;
14735
+ if (guard2?.links === void 0 || !noLinks || inherited2 || req.pin !== void 0)
14736
+ return void 0;
14737
+ return (await walk(guard2.fs, this.cred, req.path, !req.exclusive, guard2.links)).path;
14738
+ }
14739
+ async openPath(req, marked) {
14740
+ const nodes = this.nodes;
14741
+ if (req.orphan) return req.path;
14742
+ if (req.exclusive) return nodes.entryKey(marked ?? req.path);
14743
+ if (marked !== void 0) return marked;
14744
+ if (req.create) return nodes.targetKey(req.path);
14745
+ return this.keptOr(await nodes.targetKey(req.path), req.handle);
14746
+ }
14273
14747
  async mayOpen(req) {
14274
14748
  const { guard: guard2 } = this.options;
14275
14749
  const inherited2 = req.orphan || req.contents !== void 0 || req.handle !== void 0;
@@ -14369,9 +14843,9 @@ var WasmProcess = class {
14369
14843
  );
14370
14844
  return memoryFile(tree2.read(path), path, req.position);
14371
14845
  }
14372
- openVfsFile(req, id2) {
14846
+ openVfsFile(req, id2, fs = this.options.fs) {
14373
14847
  return vfsFile(
14374
- this.options.fs,
14848
+ fs,
14375
14849
  {
14376
14850
  path: req.path,
14377
14851
  flags: req.flags,
@@ -14454,7 +14928,7 @@ var WasmProcess = class {
14454
14928
  return { ok: true, kind: "void" };
14455
14929
  case "fd-renumber":
14456
14930
  if (req.from !== req.to) {
14457
- this.fds.dup2(req.from, req.to);
14931
+ await this.fds.dup2Clipboard(req.from, req.to);
14458
14932
  if (!req.keep) await Promise.resolve(this.fds.close(req.from));
14459
14933
  }
14460
14934
  return { ok: true, kind: "void" };
@@ -14466,6 +14940,11 @@ var WasmProcess = class {
14466
14940
  return { ok: true, kind: "json", json: this.openSealed(req.flags) };
14467
14941
  case "fd-open-clipboard":
14468
14942
  return { ok: true, kind: "json", json: await this.openClipboard(req.flags) };
14943
+ case "fd-clipboard-abort": {
14944
+ const file = this.fds.get(req.fd);
14945
+ file.file.clipboard?.abort();
14946
+ return { ok: true, kind: "void" };
14947
+ }
14469
14948
  case "fd-tty-owner":
14470
14949
  return { ok: true, kind: "json", json: this.terminalByName(req.name).owner ?? null };
14471
14950
  case "fd-select": {
@@ -14518,12 +14997,23 @@ var WasmProcess = class {
14518
14997
  case "fd-resize":
14519
14998
  await file.resize(req.size);
14520
14999
  return { ok: true, kind: "void" };
14521
- case "fd-vfs-stat":
14522
- return {
14523
- ok: true,
14524
- kind: "json",
14525
- json: req.identity ? await this.identified(file.stat.bind(file), file.known?.bind(file)) : await file.stat()
14526
- };
15000
+ case "fd-vfs-stat": {
15001
+ if (req.identity) {
15002
+ return {
15003
+ ok: true,
15004
+ kind: "json",
15005
+ json: await this.identified(file.stat.bind(file), file.known?.bind(file))
15006
+ };
15007
+ }
15008
+ const vfs = await file.stat();
15009
+ if (!req.full) return { ok: true, kind: "json", json: vfs };
15010
+ const st = await this.heldStat(vfs).catch(() => void 0);
15011
+ return { ok: true, kind: "json", json: { ...vfs, ...st ? { stat: st } : {} } };
15012
+ }
15013
+ case "fd-chmod":
15014
+ case "fd-chown":
15015
+ case "fd-utimes":
15016
+ return this.fdMetadata(await file.stat(), req);
14527
15017
  }
14528
15018
  }
14529
15019
  keptPath;
@@ -14544,7 +15034,7 @@ var WasmProcess = class {
14544
15034
  async promote(req) {
14545
15035
  if (!this.fds.get(req.fd).file.held) throw new KernelError("EBADF");
14546
15036
  if (req.share !== void 0) {
14547
- this.fds.dup2(req.share, req.fd);
15037
+ await this.fds.dup2Clipboard(req.share, req.fd);
14548
15038
  return;
14549
15039
  }
14550
15040
  if (req.path === void 0) throw new KernelError("EINVAL");
@@ -14573,6 +15063,46 @@ var WasmProcess = class {
14573
15063
  this.fds.installAt(fd, heldFile(meta));
14574
15064
  return fd;
14575
15065
  }
15066
+ heldPath(vfs) {
15067
+ if (!vfs.path) throw new KernelError("EBADF");
15068
+ if (vfs.orphan) throw new KernelError("EOPNOTSUPP");
15069
+ return vfs.path;
15070
+ }
15071
+ heldFs() {
15072
+ return this.options.guard?.fs ?? this.options.fs;
15073
+ }
15074
+ async heldStat(vfs) {
15075
+ const stat = this.heldFs().stat;
15076
+ if (!stat) throw new KernelError("ENOSYS");
15077
+ return stat.call(this.heldFs(), this.heldPath(vfs));
15078
+ }
15079
+ async fdMetadata(vfs, req) {
15080
+ const path = this.heldPath(vfs);
15081
+ const fs = this.heldFs();
15082
+ const g = this.guarded();
15083
+ if (req.op === "fd-chown") {
15084
+ const ids = {
15085
+ ...req.uid !== void 0 ? { uid: req.uid } : {},
15086
+ ...req.gid !== void 0 ? { gid: req.gid } : {}
15087
+ };
15088
+ if (!this.options.owners || !fs.chown || !fs.stat) await this.heldStat(vfs);
15089
+ else await chownAs(fs, this.cred, path, { ...ids, follow: true });
15090
+ return { ok: true, kind: "void" };
15091
+ }
15092
+ const st = await this.heldStat(vfs);
15093
+ if (req.op === "fd-chmod") {
15094
+ if (!fs.chmod) throw new KernelError("ENOSYS");
15095
+ await fs.chmod(path, g ? heldChmodMode(g, path, st, req.mode) : req.mode);
15096
+ return { ok: true, kind: "void" };
15097
+ }
15098
+ if (!fs.utimes) throw new KernelError("ENOSYS");
15099
+ if (g) heldRetimable(g, path, st, req.now === true);
15100
+ const at = (ms, kept) => ms === null ? kept : req.now ? /* @__PURE__ */ new Date() : new Date(ms);
15101
+ const atime = at(req.atimeMs, st.atime);
15102
+ const mtime = at(req.mtimeMs, st.mtime);
15103
+ await fs.utimes(path, atime, mtime);
15104
+ return { ok: true, kind: "void" };
15105
+ }
14576
15106
  fdInfo(fd) {
14577
15107
  const file = this.fds.get(fd).file;
14578
15108
  const flags2 = this.fds.statusFlags(fd);
@@ -14791,7 +15321,10 @@ var WasmProcess = class {
14791
15321
  const { file, argv, env, cwd, cred, caller } = answer.grant;
14792
15322
  const stdio = [{ fd: 0 }, { fd: 1 }, { fd: 2 }];
14793
15323
  return {
14794
- pid: await this.children.spawn({ file, argv, env, cwd }, stdio, [], { cred, caller })
15324
+ pid: await this.children.spawn({ file, argv, env, cwd, pgid: 0 }, stdio, [], {
15325
+ cred,
15326
+ caller
15327
+ })
14795
15328
  };
14796
15329
  }
14797
15330
  async mountSyscall(req, cancelled) {
@@ -15095,7 +15628,9 @@ function holdingToken(opts, holds, caller) {
15095
15628
  ...opts.dac ? { dac: opts.dac, pid: opts.pid, created: caller.created } : {},
15096
15629
  ...opts.links !== void 0 ? { links: opts.links } : {},
15097
15630
  ...opts.fresh ? { fresh: (path) => opts.fresh?.(path, caller.umask()) } : {},
15631
+ ...opts.source ? { source: opts.source } : {},
15098
15632
  fs: opts.fs,
15633
+ noLinks: opts.noLinks,
15099
15634
  cwd: opts.cwd,
15100
15635
  ...opts.statfs ? { statfs: opts.statfs } : {},
15101
15636
  hold: (path, on, open, real, handle) => holds.hold(path, on, open, real, handle),
@@ -15121,6 +15656,8 @@ function processOptions(opts, header3, hooks, created3) {
15121
15656
  ...opts.outputFilter ? { outputFilter: opts.outputFilter } : {},
15122
15657
  ...opts.clipboard ? { clipboard: opts.clipboard, ...clipboardName(opts) } : {},
15123
15658
  ...opts.readonlyAt ? { readonlyAt: opts.readonlyAt } : {},
15659
+ ...opts.source ? { source: opts.source } : {},
15660
+ noLinks: opts.noLinks,
15124
15661
  ...opts.dac ? { guard: { dac: opts.dac, fs: opts.fs, created: created3, ...linksOf(opts) } } : {},
15125
15662
  ...opts.identity ? { identity: opts.identity } : {},
15126
15663
  ...opts.onSyscall ? { onSyscall: opts.onSyscall } : {},
@@ -15176,8 +15713,8 @@ function threadInit(opts, program, sab, cred, thread, main) {
15176
15713
  function spawnWasmProcess(opts) {
15177
15714
  const sab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
15178
15715
  const header3 = new Int32Array(sab, 0, SAB_HEADER_I32);
15179
- if (opts.blocked && opts.program.abi === "wasi")
15180
- Atomics.store(header3, SAB_I_BLOCKED, opts.blocked);
15716
+ const blocked = opts.fork?.wasi?.blocked ?? opts.blocked;
15717
+ if (blocked && anyBlocked(blocked) && opts.program.abi === "wasi") storeBlocked(header3, blocked);
15181
15718
  const threadSignals = /* @__PURE__ */ new Map();
15182
15719
  const created3 = new Created();
15183
15720
  const process = new WasmProcess(
@@ -15192,7 +15729,7 @@ function spawnWasmProcess(opts) {
15192
15729
  ...opts.program.abi === "js" ? {
15193
15730
  exitNow: (code) => exitNow(code),
15194
15731
  exitSignal: (sig) => queueMicrotask(() => finish(128 + sig, sig))
15195
- } : {}
15732
+ } : { exitSignal: (sig) => terminate(128 + sig, sig) }
15196
15733
  },
15197
15734
  created3
15198
15735
  )
@@ -15278,7 +15815,7 @@ function spawnWasmProcess(opts) {
15278
15815
  const tsab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
15279
15816
  const tw = opts.createWorker();
15280
15817
  const tsignals = threadSignalsOf(tsab);
15281
- if (thread.blocked) Atomics.store(tsignals.header, SAB_I_BLOCKED, thread.blocked);
15818
+ if (thread.blocked) storeBlocked(tsignals.header, thread.blocked);
15282
15819
  threadSignals.set(thread.tid, tsignals);
15283
15820
  const tdispatch = (req) => dying.serving(
15284
15821
  () => isWasmSyscall(req) ? process.syscall(req, threadCancel(tsignals)) : dispatchSyncFs(req)
@@ -15306,12 +15843,13 @@ function spawnWasmProcess(opts) {
15306
15843
  processInit(opts, sab, process.umask, copyCred(process.cred), lane?.lane),
15307
15844
  transferOf(opts, lane?.lane)
15308
15845
  );
15309
- const signal = (sig) => {
15310
- if (process.signal(sig) !== "terminate") return;
15311
- const code = sig === SIG.KILL ? 137 : 128 + sig;
15846
+ const terminate = (code, sig) => {
15312
15847
  if (opts.program.abi !== "wasi" || done4) finish(code, sig);
15313
15848
  else dying.start(code, sig, header3, process, () => finish(code, sig));
15314
15849
  };
15850
+ const signal = (sig) => {
15851
+ if (process.signal(sig) === "terminate") terminate(sig === SIG.KILL ? 137 : 128 + sig, sig);
15852
+ };
15315
15853
  const kill = (code = 137) => finish(code, code > 128 && code < 160 ? code - 128 : void 0);
15316
15854
  return {
15317
15855
  pid: opts.pid,
@@ -15322,7 +15860,7 @@ function spawnWasmProcess(opts) {
15322
15860
  onState: (listener) => process.onState(listener),
15323
15861
  memory: () => Atomics.load(header3, SAB_I_MEMORY) * 65536,
15324
15862
  ignoredSignals: (fork) => process.inheritable(fork),
15325
- blockedSignals: () => opts.program.abi === "wasi" ? Atomics.load(header3, SAB_I_BLOCKED) : 0,
15863
+ blockedSignals: () => opts.program.abi === "wasi" ? loadBlocked(header3) : [0, 0],
15326
15864
  signalMasks: () => process.signalMasks(),
15327
15865
  umask: () => process.umask,
15328
15866
  cred: () => copyCred(process.cred),
@@ -18428,14 +18966,16 @@ var SUDO_COMMAND = {
18428
18966
  var SUDO_SOURCE = [
18429
18967
  "export default async function sudo(ctx) {",
18430
18968
  " const say = (fd, text) => ctx.write(fd, text).catch(() => undefined);",
18969
+ ' const RELAYED = ["SIGTERM", "SIGHUP", "SIGINT", "SIGQUIT", "SIGUSR1", "SIGUSR2", "SIGALRM"];',
18970
+ " const NUMBER = { SIGTERM: 15, SIGHUP: 1, SIGINT: 2, SIGQUIT: 3, SIGUSR1: 10, SIGUSR2: 12, SIGALRM: 14 };",
18431
18971
  " let pid;",
18432
18972
  " let held;",
18433
18973
  " const relay = (sig) => {",
18434
18974
  " if (pid === undefined) held = sig;",
18435
- " else return ctx.kill(pid, sig).catch(() => undefined);",
18975
+ " else return ctx.kill(-pid, sig).catch(() => undefined);",
18436
18976
  " };",
18437
- ' for (const sig of ["SIGTERM", "SIGHUP"]) await ctx.signals.on(sig, relay);',
18438
- " const failed = () => (held === undefined ? 1 : 128 + held);",
18977
+ " for (const sig of RELAYED) await ctx.signals.on(sig, () => relay(sig));",
18978
+ " const failed = () => (held === undefined ? 1 : 128 + NUMBER[held]);",
18439
18979
  " let answer;",
18440
18980
  " try {",
18441
18981
  " answer = await ctx.sudo(ctx.argv.slice(1));",
@@ -18451,18 +18991,41 @@ var SUDO_SOURCE = [
18451
18991
  " await say(1, answer.list);",
18452
18992
  " return 0;",
18453
18993
  " }",
18454
- ' for (const sig of ["SIGINT", "SIGQUIT"]) await ctx.signals.on(sig, () => undefined);',
18455
18994
  " if (answer.run !== undefined) {",
18456
18995
  " try {",
18457
- " pid = (await ctx.spawn({ argv: answer.run })).pid;",
18996
+ " pid = (await ctx.spawn({ argv: answer.run, pgid: 0 })).pid;",
18458
18997
  " } catch (err) {",
18459
18998
  ' const why = err && err.code === "ENOENT" ? "command not found" : String(err && err.code);',
18460
18999
  ' await say(2, "sudo: " + answer.run[0] + ": " + why + "\\n");',
18461
19000
  " return failed();",
18462
19001
  " }",
18463
19002
  " } else pid = answer.pid;",
19003
+ ' const stat = () => new TextDecoder().decode(ctx.sync.fs.readFile("/proc/" + ctx.pid + "/stat"));',
19004
+ ' const mine = Number(stat().replace(/^.*\\) /, "").split(" ")[2]);',
19005
+ " const foreground = () => ctx.tty.foreground(0).catch(() => undefined);",
19006
+ " const owned = (await foreground()) === mine;",
19007
+ " const handOver = async () => {",
19008
+ " if ((await foreground()) === mine) await ctx.tty.setForeground(0, pid).catch(() => undefined);",
19009
+ " };",
19010
+ " const takeBack = async () => {",
19011
+ " if (owned && (await foreground()) === pid) await ctx.tty.setForeground(0, mine).catch(() => undefined);",
19012
+ " };",
19013
+ " if (owned) await handOver();",
18464
19014
  " if (held !== undefined) await relay(held);",
18465
- " return (await ctx.wait(pid)).status;",
19015
+ " for (;;) {",
19016
+ " const ended = await ctx.wait(pid, { untraced: true });",
19017
+ " if (!ended.stopped) {",
19018
+ " await takeBack();",
19019
+ " return ended.status;",
19020
+ " }",
19021
+ " const reading = ended.signal === 21 || ended.signal === 22;",
19022
+ " if (!(reading && (await foreground()) === mine)) {",
19023
+ " await takeBack();",
19024
+ ' ctx.sync.kill(ctx.pid, ended.signal === 19 ? "SIGTSTP" : ended.signal);',
19025
+ " }",
19026
+ " await handOver();",
19027
+ ' await ctx.kill(-pid, "SIGCONT").catch(() => undefined);',
19028
+ " }",
18466
19029
  "}",
18467
19030
  ""
18468
19031
  ].join("\n");
@@ -18478,6 +19041,7 @@ var WASM_MAGIC = [0, 97, 115, 109];
18478
19041
  var SHEBANG_MAX = 256;
18479
19042
  var MAX_INTERPRETERS = 4;
18480
19043
  var NOT_FOUND = 127;
19044
+ var NOT_EXECUTABLE = 126;
18481
19045
  var DECIDE_MS = 100;
18482
19046
  var INIT_PID = 1;
18483
19047
  var SHARED_DIRS = ["/tmp", "/home"];
@@ -18627,6 +19191,9 @@ function addTimes(into, pid, t) {
18627
19191
  var Launcher = class {
18628
19192
  fs;
18629
19193
  processFs;
19194
+ noLinksFs;
19195
+ mounted;
19196
+ protection;
18630
19197
  prefixes;
18631
19198
  writerTags;
18632
19199
  unguardedRoot;
@@ -18735,8 +19302,8 @@ var Launcher = class {
18735
19302
  changed: this.watchers.changed.bind(this.watchers),
18736
19303
  owners: this.owners
18737
19304
  });
18738
- const watched = this.watchers.wrap(this.mounts.wrap(options.fs));
18739
- this.base = this.owners ? this.freshModules.wrap(watched) : watched;
19305
+ this.mounted = this.mounts.wrap(options.fs);
19306
+ this.base = this.rooted(this.mounted);
18740
19307
  this.createWorker = options.createWorker;
18741
19308
  this.createDriverWorker = options.createDriverWorker;
18742
19309
  this.media = options.media ?? memoryMedia();
@@ -18773,12 +19340,7 @@ var Launcher = class {
18773
19340
  });
18774
19341
  enableCdp(this.net, this.cdp);
18775
19342
  this.facades.enable(this.net);
18776
- const fs = withCommandDirs(
18777
- this.base,
18778
- async () => new Set((await this.commands()).keys()),
18779
- (dir) => this.userCommandDir(dir),
18780
- this.owners
18781
- );
19343
+ const fs = this.commandFs(this.base);
18782
19344
  this.nodes = new VfsNodes(fs, (path) => this.released(path));
18783
19345
  this.nodes.keepsOnDisk = true;
18784
19346
  this.openFiles.add(this.nodes);
@@ -18794,15 +19356,23 @@ var Launcher = class {
18794
19356
  ...this.owners ? { dirOwner: { uid: 0, gid: 0, mode: 448 } } : {}
18795
19357
  }
18796
19358
  );
18797
- this.fs = this.unlinked.wrap(keepingOpen(fs, this.nodes));
19359
+ this.fs = this.processChain(fs, this.nodes, this.unlinked);
18798
19360
  this.writerTags = writerTags(options.protect);
18799
19361
  this.unlinkOwnerOption = options.unlinkOwner;
18800
19362
  if (this.writerTags.size > 0 && options.fs.protect && !options.fs.unguarded) {
18801
19363
  throw new TypeError("protect.writers needs a root file system with an unguarded view");
18802
19364
  }
19365
+ if (!options.fs.noLinks) throw new TypeError("the root file system needs a noLinks view");
18803
19366
  this.unguardedRoot = options.fs.unguarded?.() ?? options.fs;
18804
19367
  this.prefixes = protectedPrefixes(options.protect?.paths ?? []);
18805
- this.processFs = this.prefixes.length > 0 ? new ProtectedFs(this.prefixes).view(this.fs) : this.fs;
19368
+ this.protection = this.prefixes.length > 0 ? new ProtectedFs(this.prefixes) : void 0;
19369
+ this.processFs = this.protection?.view(this.fs) ?? this.fs;
19370
+ const noLinks = this.processChain(
19371
+ this.commandFs(this.rooted(this.noLinksOf(this.mounted))),
19372
+ this.nodes,
19373
+ this.unlinked
19374
+ );
19375
+ this.noLinksFs = this.protection?.view(noLinks) ?? noLinks;
18806
19376
  const integrity = options.protect?.integrity;
18807
19377
  integrityOptions(integrity);
18808
19378
  options.fs.protect?.(this.prefixes, Boolean(integrity));
@@ -19214,10 +19784,15 @@ var Launcher = class {
19214
19784
  }
19215
19785
  async unrunnable(file, cwd, scope) {
19216
19786
  const path = this.fs.resolvePath(cwd, file);
19787
+ if (await this.directory(file, path)) return "EACCES";
19217
19788
  const head = await this.base.readFileBuffer(path).catch(() => null);
19218
19789
  if (!head || head[0] === 35 && head[1] === 33) return "ENOENT";
19219
19790
  return binfmtOf(await this.interpreters(scope), path) ? "ENOENT" : "ENOEXEC";
19220
19791
  }
19792
+ async directory(file, path) {
19793
+ if (!file.includes("/") || COMMAND2.test(file)) return false;
19794
+ return (await this.base.stat(path).catch(() => void 0))?.isDirectory === true;
19795
+ }
19221
19796
  verifiedFs(fs) {
19222
19797
  const read = (path) => this.verified(path, KERNEL_CALLER, fs);
19223
19798
  return Object.assign(Object.create(fs), {
@@ -19357,10 +19932,29 @@ var Launcher = class {
19357
19932
  return this.jobs.sessionOf(pgid);
19358
19933
  }
19359
19934
  loopbackFor(pid) {
19360
- return new KernelLoopback(this.net, () => {
19361
- const cred = this.credOf(pid);
19362
- return { pid, uid: cred.euid, gid: cred.egid };
19935
+ return new KernelLoopback(
19936
+ this.net,
19937
+ () => {
19938
+ const cred = this.credOf(pid);
19939
+ return { pid, uid: cred.euid, gid: cred.egid };
19940
+ },
19941
+ () => this.connectorStamp(pid)
19942
+ );
19943
+ }
19944
+ sourceCheck(pid, writer) {
19945
+ const { integrity } = this;
19946
+ if (!integrity?.readsChecked || writer) return {};
19947
+ const caller = () => ({
19948
+ pid,
19949
+ uid: this.credOf(pid).euid,
19950
+ tag: this.tagOf(pid)
19363
19951
  });
19952
+ return {
19953
+ source: {
19954
+ read: (path, plain) => integrity.readSource(path, caller(), plain),
19955
+ open: (path, real) => integrity.openSource(path, caller(), real)
19956
+ }
19957
+ };
19364
19958
  }
19365
19959
  treeScrubs(req) {
19366
19960
  const inherited2 = req.ppid !== void 0 ? this.scrubs.get(req.ppid) : void 0;
@@ -19397,11 +19991,12 @@ var Launcher = class {
19397
19991
  },
19398
19992
  settling: () => this.settling.isSettling(pid),
19399
19993
  ...req.ignored ? { ignored: req.ignored } : {},
19400
- ...req.blocked ? { blocked: req.blocked } : {},
19994
+ ...anyBlocked(req.blocked) ? { blocked: req.blocked } : {},
19401
19995
  ...req.umask !== void 0 ? { umask: req.umask } : {},
19402
19996
  cred: req.cred,
19403
19997
  ...this.owners && req.program.abi === "wasi" ? { fresh: (path, umask) => this.freshModules.add(path, umask, pid) } : {},
19404
19998
  ...this.owners ? { owners: true } : {},
19999
+ ...this.sourceCheck(pid, stack.writer),
19405
20000
  ...linksOf2(req),
19406
20001
  sealed: this.sealed,
19407
20002
  ...scrubs ? { outputFilter: (file) => scrubbedAll(file, scrubs) } : {},
@@ -19414,6 +20009,7 @@ var Launcher = class {
19414
20009
  cwd: req.cwd,
19415
20010
  fds: req.fds,
19416
20011
  fs: stack.fs,
20012
+ noLinks: stack.noLinks,
19417
20013
  ...this.prefixes.length > 0 && !stack.writer ? { readonlyAt: (path) => isProtected(this.prefixes, path) } : {},
19418
20014
  createWorker: this.createWorker,
19419
20015
  onError: req.report,
@@ -19491,6 +20087,11 @@ var Launcher = class {
19491
20087
  return { shownPid: this.jobs.shown(req.ppid), ...ppid !== void 0 ? { ppid } : {} };
19492
20088
  }
19493
20089
  forget(pid) {
20090
+ const image = this.jobs.list().find((m) => m.pid === pid)?.execParent;
20091
+ const described2 = this.described.get(pid);
20092
+ if (image !== void 0 && described2 && this.described.has(image)) {
20093
+ this.described.set(image, described2);
20094
+ }
19494
20095
  this.jobs.remove(pid);
19495
20096
  this.described.delete(pid);
19496
20097
  const spent = this.spent.get(pid);
@@ -19606,10 +20207,11 @@ var Launcher = class {
19606
20207
  const root = member && rootOf(member).pid;
19607
20208
  return root !== void 0 && this.running(root) ? root : INIT_PID;
19608
20209
  };
20210
+ const superseded = new Set([...members.values()].map((m) => m.execParent));
19609
20211
  const listed = [];
19610
20212
  for (const member of members.values()) {
19611
20213
  const described2 = this.described.get(member.pid);
19612
- if (member.execed || !described2) continue;
20214
+ if (member.execed && superseded.has(member.pid) || !described2) continue;
19613
20215
  const cred = this.credOf(member.pid);
19614
20216
  if (!maySee(viewer, cred)) continue;
19615
20217
  const root = rootOf(member);
@@ -19794,7 +20396,7 @@ var Launcher = class {
19794
20396
  }
19795
20397
  signalMasksOf(pid) {
19796
20398
  const masks = this.processes.get(pid)?.signalMasks?.();
19797
- const blocked = this.blockedBy(pid);
20399
+ const [blocked] = this.blockedBy(pid);
19798
20400
  const sigBlk = blocked ? { sigBlk: blocked } : {};
19799
20401
  return masks ? { sigIgn: masks.ignored, sigCgt: masks.caught, ...sigBlk } : sigBlk;
19800
20402
  }
@@ -19802,14 +20404,14 @@ var Launcher = class {
19802
20404
  return this.settling.isSettling(pid) || (this.ignoredBy(pid) & sigbit(SIG.TTOU)) !== 0;
19803
20405
  }
19804
20406
  blockedBy(pid) {
19805
- return this.processes.get(pid)?.blockedSignals?.() ?? 0;
20407
+ return this.processes.get(pid)?.blockedSignals?.() ?? [0, 0];
19806
20408
  }
19807
20409
  ignoredBy(pid, fork = false) {
19808
20410
  return this.processes.get(pid)?.ignoredSignals(fork) ?? 0;
19809
20411
  }
19810
20412
  kill(pid, sig, sender, from) {
19811
20413
  const session = from === void 0 ? void 0 : this.jobs.sidOf(from);
19812
- const may = (target) => !sender || maySignal(sender, this.credOf(target)) || sig === SIG.CONT && session !== void 0 && this.jobs.sidOf(target) === session || from !== void 0 && this.imageOf(from) === this.imageOf(target) || from !== void 0 && this.granted.get(this.jobs.shown(target)) === from;
20414
+ const may = (target) => !sender || maySignal(sender, this.credOf(target)) || sig === SIG.CONT && session !== void 0 && this.jobs.sidOf(target) === session || from !== void 0 && this.imageOf(from) === this.imageOf(target) || from !== void 0 && this.granted.get(this.jobs.shown(target)) === from || from !== void 0 && this.granted.get(this.jobs.pgidOf(target) ?? 0) === from;
19813
20415
  if (pid === -1) {
19814
20416
  const sender2 = from === void 0 ? void 0 : this.imageOf(from);
19815
20417
  return this.killAll(sig, (member) => this.imageOf(member) !== sender2, may);
@@ -20172,19 +20774,20 @@ var Launcher = class {
20172
20774
  }
20173
20775
  stackFor(tag) {
20174
20776
  if (!this.isWriter(tag)) {
20175
- return { fs: this.processFs, nodes: this.nodes, unlinked: this.unlinked, writer: false };
20777
+ return {
20778
+ fs: this.processFs,
20779
+ noLinks: this.noLinksFs,
20780
+ nodes: this.nodes,
20781
+ unlinked: this.unlinked,
20782
+ writer: false
20783
+ };
20176
20784
  }
20177
20785
  this.writers ??= this.writerStack();
20178
20786
  return this.writers;
20179
20787
  }
20180
20788
  writerStack() {
20181
- const watched = this.watchers.wrap(this.mounts.wrap(this.unguardedRoot));
20182
- const fs = withCommandDirs(
20183
- this.owners ? this.freshModules.wrap(watched) : watched,
20184
- async () => new Set((await this.commands()).keys()),
20185
- (dir) => this.userCommandDir(dir),
20186
- this.owners
20187
- );
20789
+ const mounted = this.mounts.wrap(this.unguardedRoot);
20790
+ const fs = this.commandFs(this.rooted(mounted));
20188
20791
  const nodes = new VfsNodes(fs, (path) => this.released(path), this.nodes.entries);
20189
20792
  nodes.keepsOnDisk = true;
20190
20793
  this.openFiles.add(nodes);
@@ -20199,7 +20802,32 @@ var Launcher = class {
20199
20802
  ...this.owners ? { dirOwner: { uid: 0, gid: 0, mode: 448 } } : {}
20200
20803
  }
20201
20804
  );
20202
- return { fs: unlinked.wrap(keepingOpen(fs, nodes)), nodes, unlinked, writer: true };
20805
+ const noLinks = this.commandFs(this.rooted(this.noLinksOf(mounted)));
20806
+ return {
20807
+ fs: this.processChain(fs, nodes, unlinked),
20808
+ noLinks: this.processChain(noLinks, nodes, unlinked),
20809
+ nodes,
20810
+ unlinked,
20811
+ writer: true
20812
+ };
20813
+ }
20814
+ noLinksOf(mounted) {
20815
+ return mounted.noLinks.call(mounted);
20816
+ }
20817
+ rooted(mounted) {
20818
+ const watched = this.watchers.wrap(mounted);
20819
+ return this.owners ? this.freshModules.wrap(watched) : watched;
20820
+ }
20821
+ commandFs(root) {
20822
+ return withCommandDirs(
20823
+ root,
20824
+ async () => new Set((await this.commands()).keys()),
20825
+ (dir) => this.userCommandDir(dir),
20826
+ this.owners
20827
+ );
20828
+ }
20829
+ processChain(fs, nodes, unlinked) {
20830
+ return unlinked.wrap(keepingOpen(fs, nodes));
20203
20831
  }
20204
20832
  async starting(argv, dir, account, tag) {
20205
20833
  this.resetCatalogs();
@@ -20281,8 +20909,10 @@ var Launcher = class {
20281
20909
  const report = (message3) => stderr(encoder7.encode(`${message3}
20282
20910
  `));
20283
20911
  if (!planned) {
20284
- report(`${file}: command not found`);
20285
- return { status: NOT_FOUND, stdout: concat6(out), stderr: concat6(err) };
20912
+ const directory = await this.directory(file, this.fs.resolvePath(cwd, file));
20913
+ report(`${file}: ${directory ? "Is a directory" : "command not found"}`);
20914
+ const status2 = directory ? NOT_EXECUTABLE : NOT_FOUND;
20915
+ return { status: status2, stdout: concat6(out), stderr: concat6(err) };
20286
20916
  }
20287
20917
  const fds = new FdTable();
20288
20918
  fds.installAt(0, options.stdin ? bytesSource(options.stdin) : deviceFile("null"));