@ai-ecoverse/slicc-kernel 1.91.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) {
@@ -10231,6 +10530,7 @@ async function storageUsage() {
10231
10530
  if (typeof estimate?.quota !== "number") return null;
10232
10531
  return { quota: estimate.quota, usage: estimate.usage ?? 0 };
10233
10532
  }
10533
+ var PLAIN_DIR_MODE = 493;
10234
10534
  var done = { ok: true, kind: "void" };
10235
10535
  function json2(value) {
10236
10536
  return { ok: true, kind: "json", json: value };
@@ -10240,6 +10540,27 @@ function ownerFor2(entry, mode) {
10240
10540
  if (!cred) return void 0;
10241
10541
  return ownerOf(cred, mode === void 0 ? void 0 : mode & ~(entry.umask?.() ?? 18));
10242
10542
  }
10543
+ function plainDirMode(entry, mode) {
10544
+ if (entry.cred || mode === void 0) return void 0;
10545
+ const wanted = mode & 4095 & ~(entry.umask?.() ?? 18);
10546
+ return wanted === PLAIN_DIR_MODE ? void 0 : wanted;
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
+ }
10243
10564
  function mkdirOptions(entry, mode = 511) {
10244
10565
  const owner = ownerFor2(entry, mode);
10245
10566
  if (!owner) return { recursive: true };
@@ -10251,23 +10572,39 @@ async function mkdirExclusive(entry, path, mode) {
10251
10572
  await entry.fs.mkdir(path, owner ? { owner } : {});
10252
10573
  }
10253
10574
  async function chown(entry, path, req) {
10575
+ const real = req.handle !== void 0 ? await keptOr(entry, path, req.handle) : path;
10254
10576
  const cred = entry.cred?.();
10255
10577
  if (!cred) {
10256
- 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));
10257
10579
  return done;
10258
10580
  }
10259
- await chownAs(entry.fs, cred, path, {
10581
+ await chownAs(entry.fs, cred, real, {
10260
10582
  ...req.uid !== void 0 ? { uid: req.uid } : {},
10261
10583
  ...req.gid !== void 0 ? { gid: req.gid } : {},
10262
10584
  follow: req.op === "chown"
10263
10585
  });
10264
10586
  return done;
10265
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
+ }
10266
10603
  async function run(entry, path, req) {
10267
10604
  const { fs, cwd } = entry;
10268
10605
  switch (req.op) {
10269
10606
  case "read":
10270
- return { ok: true, kind: "bytes", bytes: await fs.readFileBuffer(path) };
10607
+ return { ok: true, kind: "bytes", bytes: await readSource(entry, path) };
10271
10608
  case "write":
10272
10609
  await fs.writeFile(path, req.body ?? new Uint8Array(0), ownerFor2(entry, 438));
10273
10610
  return done;
@@ -10282,8 +10619,7 @@ async function run(entry, path, req) {
10282
10619
  case "readdir-stat":
10283
10620
  return json2(await readdirStat(fs, path));
10284
10621
  case "mkdir":
10285
- await (req.exclusive ? mkdirExclusive(entry, path, req.mode) : fs.mkdir(path, mkdirOptions(entry, req.mode)));
10286
- return done;
10622
+ return runMkdir(entry, path, req);
10287
10623
  case "rm":
10288
10624
  await fs.rm(path, { recursive: true });
10289
10625
  return done;
@@ -10309,7 +10645,7 @@ async function run(entry, path, req) {
10309
10645
  return done;
10310
10646
  case "create":
10311
10647
  await createExclusive(fs, path, ownerFor2(entry, req.mode ?? 438));
10312
- entry.created?.add(path);
10648
+ if (req.opening) entry.created?.add(path);
10313
10649
  entry.fresh?.(path);
10314
10650
  return done;
10315
10651
  case "readlink":
@@ -10320,10 +10656,10 @@ async function run(entry, path, req) {
10320
10656
  return done;
10321
10657
  }
10322
10658
  case "utimes":
10323
- await fs.utimes(path, new Date(req.atimeMs ?? 0), new Date(req.mtimeMs ?? 0));
10659
+ await fs.utimes(path, ...await timesOf(entry, path, req));
10324
10660
  return done;
10325
10661
  case "lutimes":
10326
- await lutimesOf(fs, path, new Date(req.atimeMs ?? 0), new Date(req.mtimeMs ?? 0));
10662
+ await lutimesOf(fs, path, ...await timesOf(entry, path, req));
10327
10663
  return done;
10328
10664
  case "chown":
10329
10665
  case "lchown":
@@ -10336,6 +10672,17 @@ async function run(entry, path, req) {
10336
10672
  return { ok: false, errno: "EINVAL", message: `sync-fs: unknown op '${req.op}'` };
10337
10673
  }
10338
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
+ }
10339
10686
  function guardOf(entry) {
10340
10687
  const cred = entry.dac && entry.cred?.();
10341
10688
  if (!cred || !entry.dac) return void 0;
@@ -10345,41 +10692,82 @@ function guardOf(entry) {
10345
10692
  var FOLLOWING = /* @__PURE__ */ new Set(["exists", "stat", "chown", "statfs"]);
10346
10693
  var LOOKS = /* @__PURE__ */ new Set(["exists", "stat", "lstat"]);
10347
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
+ }
10348
10703
  async function guard(g, path, req, cwd) {
10349
10704
  const { op } = req;
10350
- 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
+ }
10351
10709
  const other = () => g.fs.resolvePath(cwd, req.arg2 ?? "");
10352
10710
  switch (op) {
10353
10711
  case "readdir":
10354
- case "readdir-stat":
10355
- return listable(g, op, path);
10356
- case "mkdir":
10357
- 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
+ }
10358
10721
  case "create":
10359
10722
  case "symlink":
10360
- return inParent(g, op, path);
10723
+ await inParent(g, op, path);
10724
+ return { path: await walkSafe(g, path, op === "create") };
10361
10725
  case "unlink":
10362
- case "rmdir":
10363
- return inParent(g, op, path, true);
10364
- case "rm":
10365
- return removable(g, op, path);
10366
- case "rename":
10367
- return renamable(g, path, other());
10368
- case "link":
10369
- 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
+ }
10370
10751
  case "utimes":
10371
- case "lutimes":
10372
- 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
+ }
10373
10758
  case "read":
10374
10759
  case "pread":
10375
- 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) };
10376
10762
  case "pwrite":
10377
10763
  case "truncate":
10378
- 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) };
10379
10766
  case "write":
10380
- 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) };
10381
10769
  default:
10382
- return void 0;
10770
+ return { path: await walkSafe(g, path, true) };
10383
10771
  }
10384
10772
  }
10385
10773
  async function opening(entry, path, mode) {
@@ -10396,6 +10784,7 @@ async function holdOp(entry, path, req) {
10396
10784
  if (on && open) {
10397
10785
  await opening(entry, path, mode);
10398
10786
  const real = await entry.fs.realpath?.(path, true).catch(() => path);
10787
+ await entry.source?.open(path, real);
10399
10788
  return json2(entry.hold?.(path, on, open, real) ?? null);
10400
10789
  }
10401
10790
  entry.hold?.(path, on, open, void 0, req.handle);
@@ -10448,15 +10837,16 @@ async function mayChange(entry, path, req) {
10448
10837
  }
10449
10838
  async function guarded(entry, path, req) {
10450
10839
  const g = guardOf(entry);
10451
- if (!g) return void 0;
10452
- 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 {};
10453
10842
  try {
10454
10843
  await mayChange(entry, path, req);
10455
- await guard(g, path, req, entry.cwd);
10456
- return void 0;
10844
+ const paths = await guard(g, path, req, entry.cwd);
10845
+ return { paths };
10457
10846
  } catch (err) {
10458
10847
  const refusal = err.code === "EACCES";
10459
- return req.op === "exists" && refusal ? json2(false) : toErrno(err);
10848
+ const refused2 = req.op === "exists" && refusal ? json2(false) : toErrno(err);
10849
+ return { refused: refused2 };
10460
10850
  }
10461
10851
  }
10462
10852
  async function createExclusive(fs, path, owner) {
@@ -10482,6 +10872,7 @@ async function ranged(fs, path, req) {
10482
10872
  else await ops.truncate(path, req.length ?? 0);
10483
10873
  return done;
10484
10874
  }
10875
+ var CHOWN_OPS = /* @__PURE__ */ new Set(["chown", "lchown"]);
10485
10876
  var UNLINKED_OPS = /* @__PURE__ */ new Set(["read", "pread", "pwrite", "truncate"]);
10486
10877
  var OWN_OPS = /* @__PURE__ */ new Set(["rm", "unlink", "rmdir", "rename", "link"]);
10487
10878
  var TWO_PATHS = /* @__PURE__ */ new Set(["rename", "link"]);
@@ -10546,7 +10937,7 @@ async function rmPermitted(entry, path, req, refused2) {
10546
10937
  const st = await entry.fs.lstat(path).catch(() => void 0);
10547
10938
  if (!st?.isDirectory) return refused2;
10548
10939
  const unlisted = await guarded(entry, path, { ...req, op: "readdir" });
10549
- if (unlisted) return unlisted;
10940
+ if ("refused" in unlisted) return unlisted.refused;
10550
10941
  const names = await entry.fs.readdir(path).catch(() => void 0);
10551
10942
  if (!names) return refused2;
10552
10943
  let first;
@@ -10558,31 +10949,64 @@ async function rmPermitted(entry, path, req, refused2) {
10558
10949
  if (!child.ok) first ??= child;
10559
10950
  }
10560
10951
  if (first) return first;
10561
- const again = await guarded(entry, path, req);
10562
- 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);
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;
10563
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"]);
10564
10981
  async function dispatchOne(entry, req) {
10565
10982
  const { fs, cwd } = entry;
10566
10983
  try {
10567
10984
  const path = fs.resolvePath(cwd, req.path);
10568
- if (req.op === "kept") return json2(entry.keptFor?.(req.handle ?? -1) !== void 0);
10569
- 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;
10570
10989
  if (req.op === "access") return await accessOp(entry, path, req);
10571
10990
  if (req.op === "hold") return await holdOp(entry, path, req);
10572
- const refused2 = await guarded(entry, path, req);
10573
- if (refused2 && req.op === "rm" && !refused2.ok && DENIED.has(refused2.errno)) {
10574
- return await rmPermitted(entry, path, req, refused2);
10575
- }
10576
- if (refused2) return refused2;
10577
- if (req.op === "statfs") return json2(await usageAt(entry, path) ?? await storageUsage());
10578
- 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` };
10579
11003
  if (req.op === "write" && req.mode === 1 || UNLINKED_OPS.has(req.op)) {
10580
- const serve = async () => run(entry, await keptOr(entry, path, req.handle), req);
10581
- 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());
10582
11006
  }
10583
- if (!entry.own || !OWN_OPS.has(req.op)) return await run(entry, path, req);
10584
- const target = TWO_PATHS.has(req.op) ? fs.resolvePath(cwd, req.arg2 ?? "") : path;
10585
- 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);
10586
11010
  } catch (err) {
10587
11011
  return toErrno(err);
10588
11012
  }
@@ -10610,6 +11034,15 @@ var SAB_I_KILLED = 13;
10610
11034
  var SAB_I_ASYNC = 14;
10611
11035
  var SAB_I_STOP = 15;
10612
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);
10613
11046
  var U32 = 2 ** 32;
10614
11047
  function storeU53(header3, lo, hi, value) {
10615
11048
  Atomics.store(header3, hi, Math.floor(value / U32));
@@ -11136,7 +11569,7 @@ function mountTable(mounts = []) {
11136
11569
  }
11137
11570
 
11138
11571
  // src/version.ts
11139
- var KERNEL_VERSION = true ? "1.91.0" : "0.0.0-dev";
11572
+ var KERNEL_VERSION = true ? "1.93.0" : "0.0.0-dev";
11140
11573
  var OS_RELEASE = "6.0.0-slicc";
11141
11574
  function procVersion() {
11142
11575
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -11836,21 +12269,28 @@ function bodyOf(incoming, head, method, signal) {
11836
12269
  var KernelLoopback = class {
11837
12270
  net;
11838
12271
  cred;
11839
- constructor(net, cred) {
12272
+ stamp;
12273
+ constructor(net, cred, stamp) {
11840
12274
  this.net = net;
11841
12275
  this.cred = cred;
12276
+ this.stamp = stamp;
11842
12277
  }
11843
12278
  port(url) {
11844
12279
  return kernelLoopbackPort(url);
11845
12280
  }
11846
- connect(url, port, signal) {
12281
+ async connect(url, port, signal) {
11847
12282
  if (url.protocol === "https:" || url.protocol === "wss:") {
11848
12283
  throw errno("ENOTSUP", `TLS to the kernel's loopback is not supported: ${url.origin}`);
11849
12284
  }
11850
12285
  signal.throwIfAborted();
11851
12286
  const socket = this.net.socket("inet");
11852
12287
  try {
11853
- 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
+ );
11854
12294
  } catch (err) {
11855
12295
  socket.close();
11856
12296
  const refused2 = err instanceof KernelError && err.code === "ECONNREFUSED";
@@ -11864,7 +12304,7 @@ var KernelLoopback = class {
11864
12304
  const body = req.body ?? (/^(POST|PUT|PATCH)$/i.test(req.method) ? new Uint8Array(0) : void 0);
11865
12305
  const length2 = body ? [["Content-Length", String(body.length)]] : [];
11866
12306
  const head = requestHead(req.method, url, req.headers, [...length2, ["Connection", "close"]]);
11867
- const socket = this.connect(url, port, req.signal);
12307
+ const socket = await this.connect(url, port, req.signal);
11868
12308
  try {
11869
12309
  await writeAll2(socket, body ? concat([head, body]) : head, req.signal);
11870
12310
  const incoming = new Incoming(socket);
@@ -11885,7 +12325,7 @@ var KernelLoopback = class {
11885
12325
  }
11886
12326
  async websocket(req, port) {
11887
12327
  const url = new URL(req.url);
11888
- const socket = this.connect(url, port, req.signal);
12328
+ const socket = await this.connect(url, port, req.signal);
11889
12329
  try {
11890
12330
  const key = btoa(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(16))));
11891
12331
  const extra = [
@@ -11910,6 +12350,9 @@ var KernelLoopback = class {
11910
12350
  if (!upgraded || accept2 !== await acceptFor(key)) {
11911
12351
  throw errno("EPROTO", "the server did not accept the WebSocket key");
11912
12352
  }
12353
+ if (fieldValues(head.headers, "sec-websocket-extensions").length > 0) {
12354
+ throw errno("EPROTO", "the server offered an unsolicited extension");
12355
+ }
11913
12356
  const protocol = fieldValues(head.headers, "sec-websocket-protocol")[0] ?? "";
11914
12357
  return new LoopbackWebSocket(socket, incoming, protocol);
11915
12358
  } catch (err) {
@@ -12009,7 +12452,7 @@ var LoopbackWebSocket = class {
12009
12452
  })();
12010
12453
  }
12011
12454
  async pump(reader, deliver) {
12012
- let close = { code: 1006, reason: "" };
12455
+ let close = { code: 1006, reason: "", wasClean: false };
12013
12456
  try {
12014
12457
  for (; ; ) {
12015
12458
  await this.space();
@@ -12019,14 +12462,14 @@ var LoopbackWebSocket = class {
12019
12462
  else if (event.kind === "binary") deliver(event.bytes);
12020
12463
  else if (event.kind === "ping") this.write(clientFrame(OP_PONG, event.payload));
12021
12464
  else {
12022
- close = { code: event.code, reason: event.reason };
12465
+ close = { code: event.code, reason: event.reason, wasClean: true };
12023
12466
  if (!this.sentClose) this.sendClose(event.code === 1005 ? 1e3 : event.code, "");
12024
12467
  break;
12025
12468
  }
12026
12469
  }
12027
12470
  } catch (err) {
12028
12471
  if (err instanceof WsError) this.sendClose(err.code, err.message);
12029
- close = { code: err instanceof WsError ? err.code : 1006, reason: "" };
12472
+ close = { code: 1006, reason: "", wasClean: false };
12030
12473
  } finally {
12031
12474
  const late = setTimeout(() => this.stop.abort(), CLOSE_WAIT_MS3);
12032
12475
  await this.writes;
@@ -12152,14 +12595,22 @@ var WsHandles = class {
12152
12595
  return typeof next2.value === "string" ? { ok: true, kind: "json", json: { text: next2.value } } : { ok: true, kind: "bytes", bytes: next2.value };
12153
12596
  }
12154
12597
  const closed = await socket.closed;
12155
- 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
+ };
12156
12603
  } catch (e) {
12157
12604
  return fail("ECONNRESET", message(e));
12158
12605
  }
12159
12606
  }
12160
12607
  async closed(socket) {
12161
- const { code, reason } = await socket.closed;
12162
- 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
+ };
12163
12614
  }
12164
12615
  send(socket, req, entry) {
12165
12616
  const data = req.text ?? req.body;
@@ -12199,7 +12650,7 @@ function message2(e) {
12199
12650
  }
12200
12651
  function codeOf2(e) {
12201
12652
  const code = e?.code;
12202
- 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";
12203
12654
  }
12204
12655
  async function* once(bytes) {
12205
12656
  yield bytes;
@@ -12282,7 +12733,8 @@ var HttpHandles = class {
12282
12733
  if (port !== void 0) return { ok: false, errno: codeOf2(e), message: text };
12283
12734
  const status = e.status;
12284
12735
  if (text === NO_TRANSPORT || typeof status !== "number" || status === 502) {
12285
- const errno2 = text === NO_TRANSPORT ? "ENETUNREACH" : "ECONNREFUSED";
12736
+ const code = codeOf2(e);
12737
+ const errno2 = text === NO_TRANSPORT ? "ENETUNREACH" : code !== "EIO" ? code : "ECONNREFUSED";
12286
12738
  return { ok: false, errno: errno2, message: text };
12287
12739
  }
12288
12740
  entry.body = once(new TextEncoder().encode(text))[Symbol.asyncIterator]();
@@ -13752,6 +14204,7 @@ function aborted(signal) {
13752
14204
  signal.addEventListener("abort", () => reject(new KernelError("EINTR")), { once: true });
13753
14205
  });
13754
14206
  }
14207
+ var PLAIN_FILE_MODE = 420;
13755
14208
  var JOB_OPS = /* @__PURE__ */ new Set([
13756
14209
  "proc-setpgid",
13757
14210
  "proc-getpgid",
@@ -13774,7 +14227,7 @@ function isPtySyscall(req) {
13774
14227
  return req.op.startsWith("pty-");
13775
14228
  }
13776
14229
  function isVfsSyscall(req) {
13777
- 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";
13778
14231
  }
13779
14232
  function isFdSyscall(req) {
13780
14233
  return req.op.startsWith("fd-");
@@ -13794,6 +14247,9 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
13794
14247
  "fd-pwrite",
13795
14248
  "fd-resize",
13796
14249
  "fd-vfs-stat",
14250
+ "fd-chmod",
14251
+ "fd-chown",
14252
+ "fd-utimes",
13797
14253
  "fd-path-flush",
13798
14254
  "fd-path-unlinking",
13799
14255
  "fd-path-unlinked",
@@ -13824,6 +14280,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
13824
14280
  "fd-tty-owner",
13825
14281
  "fd-open-sealed",
13826
14282
  "fd-open-clipboard",
14283
+ "fd-clipboard-abort",
13827
14284
  "tty-get",
13828
14285
  "tty-set",
13829
14286
  "tty-winsz",
@@ -14169,7 +14626,11 @@ var WasmProcess = class {
14169
14626
  if (!stderr?.write || stderr.clipboard) return;
14170
14627
  const line = `${name2}: /dev/clipboard: ${clipboardLine(err.detail)}
14171
14628
  `;
14172
- 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
+ ]);
14173
14634
  }
14174
14635
  async write(fd, body, nonblock = false, cancelled) {
14175
14636
  const file = this.fds.get(fd).file;
@@ -14196,30 +14657,39 @@ var WasmProcess = class {
14196
14657
  await refuseReadonly(this.options.fs, req.path, req.flags, req);
14197
14658
  if (this.writesAt(req)) await this.options.fs.mayChange?.(req.path, !req.exclusive);
14198
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;
14199
14663
  const fd = this.fds.install(heldFile(), 3);
14200
14664
  try {
14201
14665
  const nodes = this.nodes;
14202
- 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);
14203
14667
  if (inProc(path)) {
14204
14668
  this.fds.installAt(fd, await this.procFile(req, path));
14205
14669
  return fd;
14206
14670
  }
14207
14671
  if (this.writesAt(req) && this.options.readonlyAt?.(path)) throw new KernelError("EROFS");
14208
14672
  const open = { ...req, path };
14673
+ const plain = this.plainMode(req.mode);
14209
14674
  if (req.exclusive) {
14210
- await nodes.createExclusive(path, this.newOwner());
14211
- this.fds.installAt(fd, this.openVfsFile(open));
14675
+ await nodes.createExclusive(path, this.newOwner(req.mode));
14676
+ if (plain !== void 0) await fs.chmod?.(path, plain);
14677
+ this.fds.installAt(fd, this.openVfsFile(open, void 0, fs));
14212
14678
  } else if (req.create) {
14213
14679
  await nodes.onEntry(path, async () => {
14214
- const fresh2 = this.options.owners && !await present2(this.options.fs, path, true);
14215
- this.fds.installAt(fd, this.openVfsFile(open, await nodes.identify(path)));
14680
+ const marks = this.options.owners || plain !== void 0;
14681
+ const fresh2 = marks && !await present2(fs, path, true);
14682
+ this.fds.installAt(fd, this.openVfsFile(open, await nodes.identify(path), fs));
14216
14683
  await this.fds.get(fd).file.stat?.();
14217
- if (fresh2) await this.options.fs.chown?.(path, { ...this.newOwner(), kind: "new" });
14684
+ if (fresh2 && plain !== void 0) await fs.chmod?.(path, plain);
14685
+ else if (fresh2) {
14686
+ await fs.chown?.(path, { ...this.newOwner(req.mode), kind: "new" });
14687
+ }
14218
14688
  });
14219
14689
  } else {
14220
- this.fds.installAt(fd, this.openVfsFile(open, await nodes.identify(path)));
14690
+ this.fds.installAt(fd, this.openVfsFile(open, await nodes.identify(path), fs));
14221
14691
  }
14222
- if (req.existing && !await present2(this.options.fs, path, true)) {
14692
+ if (req.existing && !await present2(fs, path, true)) {
14223
14693
  throw new KernelError("ENOENT");
14224
14694
  }
14225
14695
  } catch (err) {
@@ -14253,6 +14723,27 @@ var WasmProcess = class {
14253
14723
  writesAt(req) {
14254
14724
  return (req.flags & WRITE_ACCESS) !== 0 || req.truncate === true || req.create === true || req.exclusive === true;
14255
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
+ }
14256
14747
  async mayOpen(req) {
14257
14748
  const { guard: guard2 } = this.options;
14258
14749
  const inherited2 = req.orphan || req.contents !== void 0 || req.handle !== void 0;
@@ -14327,9 +14818,14 @@ var WasmProcess = class {
14327
14818
  if (!tty) throw new KernelError("ENOENT");
14328
14819
  return tty;
14329
14820
  }
14330
- newOwner() {
14821
+ plainMode(mode) {
14822
+ if (this.options.owners || mode === void 0) return void 0;
14823
+ const wanted = mode & 4095 & ~this.umask;
14824
+ return wanted === PLAIN_FILE_MODE ? void 0 : wanted;
14825
+ }
14826
+ newOwner(mode = 438) {
14331
14827
  if (!this.options.owners) return void 0;
14332
- return { uid: this.cred.euid, gid: this.cred.egid, mode: 438 & ~this.umask };
14828
+ return { uid: this.cred.euid, gid: this.cred.egid, mode: mode & 4095 & ~this.umask };
14333
14829
  }
14334
14830
  async openProc(req, path) {
14335
14831
  return this.fds.install(await this.procFile(req, path), 3);
@@ -14347,9 +14843,9 @@ var WasmProcess = class {
14347
14843
  );
14348
14844
  return memoryFile(tree2.read(path), path, req.position);
14349
14845
  }
14350
- openVfsFile(req, id2) {
14846
+ openVfsFile(req, id2, fs = this.options.fs) {
14351
14847
  return vfsFile(
14352
- this.options.fs,
14848
+ fs,
14353
14849
  {
14354
14850
  path: req.path,
14355
14851
  flags: req.flags,
@@ -14432,7 +14928,7 @@ var WasmProcess = class {
14432
14928
  return { ok: true, kind: "void" };
14433
14929
  case "fd-renumber":
14434
14930
  if (req.from !== req.to) {
14435
- this.fds.dup2(req.from, req.to);
14931
+ await this.fds.dup2Clipboard(req.from, req.to);
14436
14932
  if (!req.keep) await Promise.resolve(this.fds.close(req.from));
14437
14933
  }
14438
14934
  return { ok: true, kind: "void" };
@@ -14444,6 +14940,11 @@ var WasmProcess = class {
14444
14940
  return { ok: true, kind: "json", json: this.openSealed(req.flags) };
14445
14941
  case "fd-open-clipboard":
14446
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
+ }
14447
14948
  case "fd-tty-owner":
14448
14949
  return { ok: true, kind: "json", json: this.terminalByName(req.name).owner ?? null };
14449
14950
  case "fd-select": {
@@ -14496,12 +14997,23 @@ var WasmProcess = class {
14496
14997
  case "fd-resize":
14497
14998
  await file.resize(req.size);
14498
14999
  return { ok: true, kind: "void" };
14499
- case "fd-vfs-stat":
14500
- return {
14501
- ok: true,
14502
- kind: "json",
14503
- json: req.identity ? await this.identified(file.stat.bind(file), file.known?.bind(file)) : await file.stat()
14504
- };
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);
14505
15017
  }
14506
15018
  }
14507
15019
  keptPath;
@@ -14522,7 +15034,7 @@ var WasmProcess = class {
14522
15034
  async promote(req) {
14523
15035
  if (!this.fds.get(req.fd).file.held) throw new KernelError("EBADF");
14524
15036
  if (req.share !== void 0) {
14525
- this.fds.dup2(req.share, req.fd);
15037
+ await this.fds.dup2Clipboard(req.share, req.fd);
14526
15038
  return;
14527
15039
  }
14528
15040
  if (req.path === void 0) throw new KernelError("EINVAL");
@@ -14551,6 +15063,46 @@ var WasmProcess = class {
14551
15063
  this.fds.installAt(fd, heldFile(meta));
14552
15064
  return fd;
14553
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
+ }
14554
15106
  fdInfo(fd) {
14555
15107
  const file = this.fds.get(fd).file;
14556
15108
  const flags2 = this.fds.statusFlags(fd);
@@ -14769,7 +15321,10 @@ var WasmProcess = class {
14769
15321
  const { file, argv, env, cwd, cred, caller } = answer.grant;
14770
15322
  const stdio = [{ fd: 0 }, { fd: 1 }, { fd: 2 }];
14771
15323
  return {
14772
- 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
+ })
14773
15328
  };
14774
15329
  }
14775
15330
  async mountSyscall(req, cancelled) {
@@ -15069,11 +15624,13 @@ function inherited(opts) {
15069
15624
  }
15070
15625
  function holdingToken(opts, holds, caller) {
15071
15626
  return mintSyncFsToken({
15072
- ...opts.owners ? caller : {},
15627
+ ...opts.owners ? caller : { umask: caller.umask },
15073
15628
  ...opts.dac ? { dac: opts.dac, pid: opts.pid, created: caller.created } : {},
15074
15629
  ...opts.links !== void 0 ? { links: opts.links } : {},
15075
15630
  ...opts.fresh ? { fresh: (path) => opts.fresh?.(path, caller.umask()) } : {},
15631
+ ...opts.source ? { source: opts.source } : {},
15076
15632
  fs: opts.fs,
15633
+ noLinks: opts.noLinks,
15077
15634
  cwd: opts.cwd,
15078
15635
  ...opts.statfs ? { statfs: opts.statfs } : {},
15079
15636
  hold: (path, on, open, real, handle) => holds.hold(path, on, open, real, handle),
@@ -15099,6 +15656,8 @@ function processOptions(opts, header3, hooks, created3) {
15099
15656
  ...opts.outputFilter ? { outputFilter: opts.outputFilter } : {},
15100
15657
  ...opts.clipboard ? { clipboard: opts.clipboard, ...clipboardName(opts) } : {},
15101
15658
  ...opts.readonlyAt ? { readonlyAt: opts.readonlyAt } : {},
15659
+ ...opts.source ? { source: opts.source } : {},
15660
+ noLinks: opts.noLinks,
15102
15661
  ...opts.dac ? { guard: { dac: opts.dac, fs: opts.fs, created: created3, ...linksOf(opts) } } : {},
15103
15662
  ...opts.identity ? { identity: opts.identity } : {},
15104
15663
  ...opts.onSyscall ? { onSyscall: opts.onSyscall } : {},
@@ -15154,8 +15713,8 @@ function threadInit(opts, program, sab, cred, thread, main) {
15154
15713
  function spawnWasmProcess(opts) {
15155
15714
  const sab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
15156
15715
  const header3 = new Int32Array(sab, 0, SAB_HEADER_I32);
15157
- if (opts.blocked && opts.program.abi === "wasi")
15158
- 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);
15159
15718
  const threadSignals = /* @__PURE__ */ new Map();
15160
15719
  const created3 = new Created();
15161
15720
  const process = new WasmProcess(
@@ -15170,7 +15729,7 @@ function spawnWasmProcess(opts) {
15170
15729
  ...opts.program.abi === "js" ? {
15171
15730
  exitNow: (code) => exitNow(code),
15172
15731
  exitSignal: (sig) => queueMicrotask(() => finish(128 + sig, sig))
15173
- } : {}
15732
+ } : { exitSignal: (sig) => terminate(128 + sig, sig) }
15174
15733
  },
15175
15734
  created3
15176
15735
  )
@@ -15256,7 +15815,7 @@ function spawnWasmProcess(opts) {
15256
15815
  const tsab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
15257
15816
  const tw = opts.createWorker();
15258
15817
  const tsignals = threadSignalsOf(tsab);
15259
- if (thread.blocked) Atomics.store(tsignals.header, SAB_I_BLOCKED, thread.blocked);
15818
+ if (thread.blocked) storeBlocked(tsignals.header, thread.blocked);
15260
15819
  threadSignals.set(thread.tid, tsignals);
15261
15820
  const tdispatch = (req) => dying.serving(
15262
15821
  () => isWasmSyscall(req) ? process.syscall(req, threadCancel(tsignals)) : dispatchSyncFs(req)
@@ -15284,12 +15843,13 @@ function spawnWasmProcess(opts) {
15284
15843
  processInit(opts, sab, process.umask, copyCred(process.cred), lane?.lane),
15285
15844
  transferOf(opts, lane?.lane)
15286
15845
  );
15287
- const signal = (sig) => {
15288
- if (process.signal(sig) !== "terminate") return;
15289
- const code = sig === SIG.KILL ? 137 : 128 + sig;
15846
+ const terminate = (code, sig) => {
15290
15847
  if (opts.program.abi !== "wasi" || done4) finish(code, sig);
15291
15848
  else dying.start(code, sig, header3, process, () => finish(code, sig));
15292
15849
  };
15850
+ const signal = (sig) => {
15851
+ if (process.signal(sig) === "terminate") terminate(sig === SIG.KILL ? 137 : 128 + sig, sig);
15852
+ };
15293
15853
  const kill = (code = 137) => finish(code, code > 128 && code < 160 ? code - 128 : void 0);
15294
15854
  return {
15295
15855
  pid: opts.pid,
@@ -15300,7 +15860,7 @@ function spawnWasmProcess(opts) {
15300
15860
  onState: (listener) => process.onState(listener),
15301
15861
  memory: () => Atomics.load(header3, SAB_I_MEMORY) * 65536,
15302
15862
  ignoredSignals: (fork) => process.inheritable(fork),
15303
- blockedSignals: () => opts.program.abi === "wasi" ? Atomics.load(header3, SAB_I_BLOCKED) : 0,
15863
+ blockedSignals: () => opts.program.abi === "wasi" ? loadBlocked(header3) : [0, 0],
15304
15864
  signalMasks: () => process.signalMasks(),
15305
15865
  umask: () => process.umask,
15306
15866
  cred: () => copyCred(process.cred),
@@ -18406,14 +18966,16 @@ var SUDO_COMMAND = {
18406
18966
  var SUDO_SOURCE = [
18407
18967
  "export default async function sudo(ctx) {",
18408
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 };",
18409
18971
  " let pid;",
18410
18972
  " let held;",
18411
18973
  " const relay = (sig) => {",
18412
18974
  " if (pid === undefined) held = sig;",
18413
- " else return ctx.kill(pid, sig).catch(() => undefined);",
18975
+ " else return ctx.kill(-pid, sig).catch(() => undefined);",
18414
18976
  " };",
18415
- ' for (const sig of ["SIGTERM", "SIGHUP"]) await ctx.signals.on(sig, relay);',
18416
- " 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]);",
18417
18979
  " let answer;",
18418
18980
  " try {",
18419
18981
  " answer = await ctx.sudo(ctx.argv.slice(1));",
@@ -18429,18 +18991,41 @@ var SUDO_SOURCE = [
18429
18991
  " await say(1, answer.list);",
18430
18992
  " return 0;",
18431
18993
  " }",
18432
- ' for (const sig of ["SIGINT", "SIGQUIT"]) await ctx.signals.on(sig, () => undefined);',
18433
18994
  " if (answer.run !== undefined) {",
18434
18995
  " try {",
18435
- " pid = (await ctx.spawn({ argv: answer.run })).pid;",
18996
+ " pid = (await ctx.spawn({ argv: answer.run, pgid: 0 })).pid;",
18436
18997
  " } catch (err) {",
18437
18998
  ' const why = err && err.code === "ENOENT" ? "command not found" : String(err && err.code);',
18438
18999
  ' await say(2, "sudo: " + answer.run[0] + ": " + why + "\\n");',
18439
19000
  " return failed();",
18440
19001
  " }",
18441
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();",
18442
19014
  " if (held !== undefined) await relay(held);",
18443
- " 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
+ " }",
18444
19029
  "}",
18445
19030
  ""
18446
19031
  ].join("\n");
@@ -18456,6 +19041,7 @@ var WASM_MAGIC = [0, 97, 115, 109];
18456
19041
  var SHEBANG_MAX = 256;
18457
19042
  var MAX_INTERPRETERS = 4;
18458
19043
  var NOT_FOUND = 127;
19044
+ var NOT_EXECUTABLE = 126;
18459
19045
  var DECIDE_MS = 100;
18460
19046
  var INIT_PID = 1;
18461
19047
  var SHARED_DIRS = ["/tmp", "/home"];
@@ -18605,6 +19191,9 @@ function addTimes(into, pid, t) {
18605
19191
  var Launcher = class {
18606
19192
  fs;
18607
19193
  processFs;
19194
+ noLinksFs;
19195
+ mounted;
19196
+ protection;
18608
19197
  prefixes;
18609
19198
  writerTags;
18610
19199
  unguardedRoot;
@@ -18713,8 +19302,8 @@ var Launcher = class {
18713
19302
  changed: this.watchers.changed.bind(this.watchers),
18714
19303
  owners: this.owners
18715
19304
  });
18716
- const watched = this.watchers.wrap(this.mounts.wrap(options.fs));
18717
- this.base = this.owners ? this.freshModules.wrap(watched) : watched;
19305
+ this.mounted = this.mounts.wrap(options.fs);
19306
+ this.base = this.rooted(this.mounted);
18718
19307
  this.createWorker = options.createWorker;
18719
19308
  this.createDriverWorker = options.createDriverWorker;
18720
19309
  this.media = options.media ?? memoryMedia();
@@ -18751,12 +19340,7 @@ var Launcher = class {
18751
19340
  });
18752
19341
  enableCdp(this.net, this.cdp);
18753
19342
  this.facades.enable(this.net);
18754
- const fs = withCommandDirs(
18755
- this.base,
18756
- async () => new Set((await this.commands()).keys()),
18757
- (dir) => this.userCommandDir(dir),
18758
- this.owners
18759
- );
19343
+ const fs = this.commandFs(this.base);
18760
19344
  this.nodes = new VfsNodes(fs, (path) => this.released(path));
18761
19345
  this.nodes.keepsOnDisk = true;
18762
19346
  this.openFiles.add(this.nodes);
@@ -18772,15 +19356,23 @@ var Launcher = class {
18772
19356
  ...this.owners ? { dirOwner: { uid: 0, gid: 0, mode: 448 } } : {}
18773
19357
  }
18774
19358
  );
18775
- this.fs = this.unlinked.wrap(keepingOpen(fs, this.nodes));
19359
+ this.fs = this.processChain(fs, this.nodes, this.unlinked);
18776
19360
  this.writerTags = writerTags(options.protect);
18777
19361
  this.unlinkOwnerOption = options.unlinkOwner;
18778
19362
  if (this.writerTags.size > 0 && options.fs.protect && !options.fs.unguarded) {
18779
19363
  throw new TypeError("protect.writers needs a root file system with an unguarded view");
18780
19364
  }
19365
+ if (!options.fs.noLinks) throw new TypeError("the root file system needs a noLinks view");
18781
19366
  this.unguardedRoot = options.fs.unguarded?.() ?? options.fs;
18782
19367
  this.prefixes = protectedPrefixes(options.protect?.paths ?? []);
18783
- 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;
18784
19376
  const integrity = options.protect?.integrity;
18785
19377
  integrityOptions(integrity);
18786
19378
  options.fs.protect?.(this.prefixes, Boolean(integrity));
@@ -19192,10 +19784,15 @@ var Launcher = class {
19192
19784
  }
19193
19785
  async unrunnable(file, cwd, scope) {
19194
19786
  const path = this.fs.resolvePath(cwd, file);
19787
+ if (await this.directory(file, path)) return "EACCES";
19195
19788
  const head = await this.base.readFileBuffer(path).catch(() => null);
19196
19789
  if (!head || head[0] === 35 && head[1] === 33) return "ENOENT";
19197
19790
  return binfmtOf(await this.interpreters(scope), path) ? "ENOENT" : "ENOEXEC";
19198
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
+ }
19199
19796
  verifiedFs(fs) {
19200
19797
  const read = (path) => this.verified(path, KERNEL_CALLER, fs);
19201
19798
  return Object.assign(Object.create(fs), {
@@ -19335,10 +19932,29 @@ var Launcher = class {
19335
19932
  return this.jobs.sessionOf(pgid);
19336
19933
  }
19337
19934
  loopbackFor(pid) {
19338
- return new KernelLoopback(this.net, () => {
19339
- const cred = this.credOf(pid);
19340
- 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)
19341
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
+ };
19342
19958
  }
19343
19959
  treeScrubs(req) {
19344
19960
  const inherited2 = req.ppid !== void 0 ? this.scrubs.get(req.ppid) : void 0;
@@ -19375,11 +19991,12 @@ var Launcher = class {
19375
19991
  },
19376
19992
  settling: () => this.settling.isSettling(pid),
19377
19993
  ...req.ignored ? { ignored: req.ignored } : {},
19378
- ...req.blocked ? { blocked: req.blocked } : {},
19994
+ ...anyBlocked(req.blocked) ? { blocked: req.blocked } : {},
19379
19995
  ...req.umask !== void 0 ? { umask: req.umask } : {},
19380
19996
  cred: req.cred,
19381
19997
  ...this.owners && req.program.abi === "wasi" ? { fresh: (path, umask) => this.freshModules.add(path, umask, pid) } : {},
19382
19998
  ...this.owners ? { owners: true } : {},
19999
+ ...this.sourceCheck(pid, stack.writer),
19383
20000
  ...linksOf2(req),
19384
20001
  sealed: this.sealed,
19385
20002
  ...scrubs ? { outputFilter: (file) => scrubbedAll(file, scrubs) } : {},
@@ -19392,6 +20009,7 @@ var Launcher = class {
19392
20009
  cwd: req.cwd,
19393
20010
  fds: req.fds,
19394
20011
  fs: stack.fs,
20012
+ noLinks: stack.noLinks,
19395
20013
  ...this.prefixes.length > 0 && !stack.writer ? { readonlyAt: (path) => isProtected(this.prefixes, path) } : {},
19396
20014
  createWorker: this.createWorker,
19397
20015
  onError: req.report,
@@ -19469,6 +20087,11 @@ var Launcher = class {
19469
20087
  return { shownPid: this.jobs.shown(req.ppid), ...ppid !== void 0 ? { ppid } : {} };
19470
20088
  }
19471
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
+ }
19472
20095
  this.jobs.remove(pid);
19473
20096
  this.described.delete(pid);
19474
20097
  const spent = this.spent.get(pid);
@@ -19584,10 +20207,11 @@ var Launcher = class {
19584
20207
  const root = member && rootOf(member).pid;
19585
20208
  return root !== void 0 && this.running(root) ? root : INIT_PID;
19586
20209
  };
20210
+ const superseded = new Set([...members.values()].map((m) => m.execParent));
19587
20211
  const listed = [];
19588
20212
  for (const member of members.values()) {
19589
20213
  const described2 = this.described.get(member.pid);
19590
- if (member.execed || !described2) continue;
20214
+ if (member.execed && superseded.has(member.pid) || !described2) continue;
19591
20215
  const cred = this.credOf(member.pid);
19592
20216
  if (!maySee(viewer, cred)) continue;
19593
20217
  const root = rootOf(member);
@@ -19772,7 +20396,7 @@ var Launcher = class {
19772
20396
  }
19773
20397
  signalMasksOf(pid) {
19774
20398
  const masks = this.processes.get(pid)?.signalMasks?.();
19775
- const blocked = this.blockedBy(pid);
20399
+ const [blocked] = this.blockedBy(pid);
19776
20400
  const sigBlk = blocked ? { sigBlk: blocked } : {};
19777
20401
  return masks ? { sigIgn: masks.ignored, sigCgt: masks.caught, ...sigBlk } : sigBlk;
19778
20402
  }
@@ -19780,14 +20404,14 @@ var Launcher = class {
19780
20404
  return this.settling.isSettling(pid) || (this.ignoredBy(pid) & sigbit(SIG.TTOU)) !== 0;
19781
20405
  }
19782
20406
  blockedBy(pid) {
19783
- return this.processes.get(pid)?.blockedSignals?.() ?? 0;
20407
+ return this.processes.get(pid)?.blockedSignals?.() ?? [0, 0];
19784
20408
  }
19785
20409
  ignoredBy(pid, fork = false) {
19786
20410
  return this.processes.get(pid)?.ignoredSignals(fork) ?? 0;
19787
20411
  }
19788
20412
  kill(pid, sig, sender, from) {
19789
20413
  const session = from === void 0 ? void 0 : this.jobs.sidOf(from);
19790
- 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;
19791
20415
  if (pid === -1) {
19792
20416
  const sender2 = from === void 0 ? void 0 : this.imageOf(from);
19793
20417
  return this.killAll(sig, (member) => this.imageOf(member) !== sender2, may);
@@ -20150,19 +20774,20 @@ var Launcher = class {
20150
20774
  }
20151
20775
  stackFor(tag) {
20152
20776
  if (!this.isWriter(tag)) {
20153
- 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
+ };
20154
20784
  }
20155
20785
  this.writers ??= this.writerStack();
20156
20786
  return this.writers;
20157
20787
  }
20158
20788
  writerStack() {
20159
- const watched = this.watchers.wrap(this.mounts.wrap(this.unguardedRoot));
20160
- const fs = withCommandDirs(
20161
- this.owners ? this.freshModules.wrap(watched) : watched,
20162
- async () => new Set((await this.commands()).keys()),
20163
- (dir) => this.userCommandDir(dir),
20164
- this.owners
20165
- );
20789
+ const mounted = this.mounts.wrap(this.unguardedRoot);
20790
+ const fs = this.commandFs(this.rooted(mounted));
20166
20791
  const nodes = new VfsNodes(fs, (path) => this.released(path), this.nodes.entries);
20167
20792
  nodes.keepsOnDisk = true;
20168
20793
  this.openFiles.add(nodes);
@@ -20177,7 +20802,32 @@ var Launcher = class {
20177
20802
  ...this.owners ? { dirOwner: { uid: 0, gid: 0, mode: 448 } } : {}
20178
20803
  }
20179
20804
  );
20180
- 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));
20181
20831
  }
20182
20832
  async starting(argv, dir, account, tag) {
20183
20833
  this.resetCatalogs();
@@ -20259,8 +20909,10 @@ var Launcher = class {
20259
20909
  const report = (message3) => stderr(encoder7.encode(`${message3}
20260
20910
  `));
20261
20911
  if (!planned) {
20262
- report(`${file}: command not found`);
20263
- 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) };
20264
20916
  }
20265
20917
  const fds = new FdTable();
20266
20918
  fds.installAt(0, options.stdin ? bytesSource(options.stdin) : deviceFile("null"));