@ai-ecoverse/slicc-kernel 1.65.0 → 1.65.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -392,7 +392,7 @@ The proxy answers `200` with `Content-Type: application/vnd.slicc.raw-fetch`: a
392
392
 
393
393
  ## Filesystem
394
394
 
395
- `/` is the OPFS root. Each process mounts the top-level directories that exist when it starts (plus `/usr` and `/bin`), so files in them are shared by all processes and visible through the OPFS API as soon as the process that wrote them has closed them; `run` resolves only after that. `createKernel` creates `/tmp` and `/home` in OPFS, so they are shared too. Files directly in `/`, and directories created there after a process started, are OPFS as well, the same for Emscripten and WASI programs; only `/dev` and `/proc` live in each process's memory. A program's `mkdir` (WASI `path_create_directory`, Emscripten's `mkdir`) is exclusive, as on Linux: it fails with `EEXIST` when the name exists, and of several processes or threads making one name at once exactly one succeeds, so a directory works as a lock. Under a missing directory it fails with `ENOENT`, as on Linux (`mkdir -p` and `os.makedirs` make the parents in the program); a JS program's `ctx.fs.mkdir` makes parents itself. The kernel makes the system `PNPM_HOME` (`/usr/local/share/pnpm`, or the embedder's) when it starts, since pnpm makes only its `bin` directory there. An OPFS or IndexedDB failure that has no POSIX counterpart is `EIO`; its message names the DOM error and the operation (`lookup`, `read`, `write`, `commit` or `sidecar`), and the kernel worker logs the first of each kind to its console. File contents are buffered per open file and written back on close, `fsync` and exit; metadata operations (`mkdir`, `rename`, `rm`, …) go straight to OPFS. The kernel worker is the only writer.
395
+ `/` is the OPFS root. Each process mounts the top-level directories that exist when it starts (plus `/usr` and `/bin`), so files in them are shared by all processes and visible through the OPFS API as soon as the process that wrote them has closed them; `run` resolves only after that. `createKernel` creates `/tmp` and `/home` in OPFS, so they are shared too. Files directly in `/`, and directories created there after a process started, are OPFS as well, the same for Emscripten and WASI programs; only `/dev` and `/proc` live in each process's memory. A program's `mkdir` (WASI `path_create_directory`, Emscripten's `mkdir`) is exclusive, as on Linux: it fails with `EEXIST` when the name exists, and of several processes or threads making one name at once exactly one succeeds, so a directory works as a lock. Under a missing directory it fails with `ENOENT`, as on Linux (`mkdir -p` and `os.makedirs` make the parents in the program); a JS program's `ctx.fs.mkdir` makes parents itself. The kernel makes the system `PNPM_HOME` (`/usr/local/share/pnpm`, or the embedder's) when it starts, since pnpm makes only its `bin` directory there. A full origin storage (`QuotaExceededError`, which also comes early in a browser's private or off-the-record contexts, whatever `navigator.storage.estimate()` says) is `ENOSPC`, never retried. An OPFS or IndexedDB failure that has no POSIX counterpart is `EIO`; its message names the DOM error and the operation (`lookup`, `read`, `write`, `commit` or `sidecar`), and the kernel worker logs the first of each kind to its console. File contents are buffered per open file and written back on close, `fsync` and exit; metadata operations (`mkdir`, `rename`, `rm`, …) go straight to OPFS. The kernel worker is the only writer.
396
396
 
397
397
  Directories are renamed with `FileSystemHandle.move()` where available, else by copy and delete.
398
398
 
@@ -1,18 +1,99 @@
1
+ // src/fs/path-map.ts
2
+ function parentOf(path) {
3
+ if (path === "/" || path === "") return void 0;
4
+ const at = path.lastIndexOf("/");
5
+ return at <= 0 ? "/" : path.slice(0, at);
6
+ }
7
+ var PathMap = class {
8
+ values = /* @__PURE__ */ new Map();
9
+ children = /* @__PURE__ */ new Map();
10
+ get size() {
11
+ return this.values.size;
12
+ }
13
+ has(path) {
14
+ return this.values.has(path);
15
+ }
16
+ get(path) {
17
+ return this.values.get(path);
18
+ }
19
+ keys() {
20
+ return this.values.keys();
21
+ }
22
+ set(path, value) {
23
+ if (!this.values.has(path)) this.link(path);
24
+ this.values.set(path, value);
25
+ return this;
26
+ }
27
+ delete(path) {
28
+ if (!this.values.delete(path)) return false;
29
+ this.prune(path);
30
+ return true;
31
+ }
32
+ subtree(root) {
33
+ const out = [];
34
+ const stack = [root];
35
+ for (let path = stack.pop(); path !== void 0; path = stack.pop()) {
36
+ if (this.values.has(path)) out.push(path);
37
+ const below = this.children.get(path);
38
+ if (below) for (const child of below) stack.push(child);
39
+ }
40
+ return out;
41
+ }
42
+ entries() {
43
+ return this.values.entries();
44
+ }
45
+ deleteTree(root) {
46
+ const stack = [root];
47
+ for (let path = stack.pop(); path !== void 0; path = stack.pop()) {
48
+ this.values.delete(path);
49
+ const below = this.children.get(path);
50
+ if (below) for (const child of below) stack.push(child);
51
+ this.children.delete(path);
52
+ }
53
+ this.prune(root);
54
+ }
55
+ clear() {
56
+ this.values.clear();
57
+ this.children.clear();
58
+ }
59
+ link(path) {
60
+ for (let child = path, parent = parentOf(path); parent !== void 0; ) {
61
+ const set2 = this.children.get(parent);
62
+ if (set2?.has(child)) return;
63
+ if (set2) set2.add(child);
64
+ else this.children.set(parent, /* @__PURE__ */ new Set([child]));
65
+ child = parent;
66
+ parent = parentOf(parent);
67
+ }
68
+ }
69
+ prune(path) {
70
+ for (let child = path, parent = parentOf(path); parent !== void 0; ) {
71
+ if (this.values.has(child) || this.children.has(child)) return;
72
+ const set2 = this.children.get(parent);
73
+ set2?.delete(child);
74
+ if (set2 && set2.size > 0) return;
75
+ this.children.delete(parent);
76
+ child = parent;
77
+ parent = parentOf(parent);
78
+ }
79
+ }
80
+ };
81
+
1
82
  // src/fs/meta.ts
2
83
  var META_DB = "slicc-kernel";
3
84
  var META_VERSION = 1;
4
85
  var META_STORE = "entries";
5
- function parentOf(path) {
86
+ function parentOf2(path) {
6
87
  return path.slice(0, path.lastIndexOf("/")) || "/";
7
88
  }
8
89
  function moved(entry, path) {
9
- return entry.link === void 0 ? { ...entry, path } : { ...entry, path, dir: parentOf(path) };
90
+ return entry.link === void 0 ? { ...entry, path } : { ...entry, path, dir: parentOf2(path) };
10
91
  }
11
92
  function under(path, root) {
12
93
  return path === root || path.startsWith(`${root}/`);
13
94
  }
14
95
  var MemoryMeta = class {
15
- entries = /* @__PURE__ */ new Map();
96
+ entries = new PathMap();
16
97
  async get(paths) {
17
98
  return paths.map((path) => this.entries.get(path));
18
99
  }
@@ -25,7 +106,7 @@ var MemoryMeta = class {
25
106
  }
26
107
  async move(from, to, root) {
27
108
  await this.remove([to]);
28
- const carried = [...this.entries.values()].filter((entry) => under(entry.path, from));
109
+ const carried = this.entries.subtree(from).map((path) => this.entries.get(path));
29
110
  for (const entry of carried) this.entries.delete(entry.path);
30
111
  for (const entry of carried) {
31
112
  const path = to + entry.path.slice(from.length);
@@ -34,18 +115,16 @@ var MemoryMeta = class {
34
115
  this.entries.set(to, moved(root(carried.find((entry) => entry.path === from)), to));
35
116
  }
36
117
  async remove(paths) {
37
- for (const key of [...this.entries.keys()]) {
38
- if (paths.some((path) => under(key, path))) this.entries.delete(key);
39
- }
118
+ for (const path of paths) this.entries.deleteTree(path);
40
119
  }
41
120
  async links(dir) {
42
- return [...this.entries.values()].filter((entry) => entry.dir === dir);
121
+ return [...this.entries.entries()].map(([, e]) => e).filter((entry) => entry.dir === dir);
43
122
  }
44
123
  async under(path) {
45
- return [...this.entries.values()].filter((entry) => under(entry.path, path));
124
+ return [...this.entries.entries()].map(([, e]) => e).filter((entry) => under(entry.path, path));
46
125
  }
47
126
  async all() {
48
- return [...this.entries.values()];
127
+ return [...this.entries.entries()].map(([, e]) => e);
49
128
  }
50
129
  };
51
130
  function descendants(path) {
@@ -59,7 +138,7 @@ function settle(transaction, value) {
59
138
  }
60
139
  var IndexedDbMeta = class {
61
140
  db;
62
- cache = /* @__PURE__ */ new Map();
141
+ cache = new PathMap();
63
142
  channel;
64
143
  generation = 0;
65
144
  constructor(db) {
@@ -72,9 +151,7 @@ var IndexedDbMeta = class {
72
151
  drop({ paths = [], trees = [] }) {
73
152
  this.generation++;
74
153
  for (const path of paths) this.cache.delete(path);
75
- for (const key of [...this.cache.keys()]) {
76
- if (trees.some((tree2) => under(key, tree2))) this.cache.delete(key);
77
- }
154
+ for (const tree2 of trees) this.cache.deleteTree(tree2);
78
155
  }
79
156
  changed(invalidation2) {
80
157
  this.drop(invalidation2);
@@ -299,15 +376,15 @@ function translate(err, path, op = "lookup", mapped = true) {
299
376
  if (typeof err?.code === "string") return err;
300
377
  const raw = err?.name;
301
378
  const name2 = typeof raw === "string" ? raw : "Error";
302
- const code = mapped && ERRNO_BY_DOM_ERROR[name2] || "EIO";
379
+ const code = name2 === "QuotaExceededError" ? "ENOSPC" : mapped && ERRNO_BY_DOM_ERROR[name2] || "EIO";
303
380
  const error = Object.assign(fsError(code, path), { domName: name2, op, cause: err });
304
- if (code !== "EIO") return error;
381
+ if (code !== "EIO" && code !== "ENOSPC") return error;
305
382
  const detail = err?.message;
306
383
  error.message += ` (OPFS ${op}: ${name2}${typeof detail === "string" && detail ? `: ${detail}` : ""})`;
307
384
  if (!reported.has(`${name2}/${op}`)) {
308
385
  reported.add(`${name2}/${op}`);
309
386
  console.warn(
310
- `slicc-kernel: ${error.message}, reported as EIO; logged once per error and operation`
387
+ `slicc-kernel: ${error.message}, reported as ${code}; logged once per error and operation`
311
388
  );
312
389
  }
313
390
  return error;
@@ -327,7 +404,7 @@ function missing(err) {
327
404
  function split(path) {
328
405
  return path.split("/").filter(Boolean);
329
406
  }
330
- function parentOf2(path) {
407
+ function parentOf3(path) {
331
408
  return path.slice(0, path.lastIndexOf("/")) || "/";
332
409
  }
333
410
  function baseOf(path) {
@@ -400,7 +477,7 @@ function isHandle(child) {
400
477
  var OpfsFs = class {
401
478
  root;
402
479
  meta;
403
- dirs = /* @__PURE__ */ new Map();
480
+ dirs = new PathMap();
404
481
  checked = /* @__PURE__ */ new WeakMap();
405
482
  channel;
406
483
  ranged;
@@ -449,7 +526,7 @@ var OpfsFs = class {
449
526
  async ownerAt(path, entry) {
450
527
  const own = ownOf(entry);
451
528
  if (own) return own;
452
- return path === "/" ? { ...ROOT_OWNER } : this.handedTo(parentOf2(path));
529
+ return path === "/" ? { ...ROOT_OWNER } : this.handedTo(parentOf3(path));
453
530
  }
454
531
  async owned(stat, path, entry) {
455
532
  return this.owners ? { ...stat, ...await this.ownerAt(path, entry) } : stat;
@@ -479,7 +556,7 @@ var OpfsFs = class {
479
556
  async chown(path, change) {
480
557
  if (!this.owners) return;
481
558
  if (change.kind === "new") {
482
- const at = resolvePath((await this.locate(parentOf2(path), true)).path, baseOf(path));
559
+ const at = resolvePath((await this.locate(parentOf3(path), true)).path, baseOf(path));
483
560
  const { uid = 0, gid = 0, mode } = change;
484
561
  return this.stampNew(at, { uid, gid, ...mode !== void 0 ? { mode } : {} }, false);
485
562
  }
@@ -543,7 +620,7 @@ var OpfsFs = class {
543
620
  return true;
544
621
  }
545
622
  forget(path) {
546
- for (const key of [...this.dirs.keys()]) if (within(key, path)) this.dirs.delete(key);
623
+ this.dirs.deleteTree(path);
547
624
  }
548
625
  async inDir(path, op) {
549
626
  const dir = await this.dir(path);
@@ -590,7 +667,7 @@ var OpfsFs = class {
590
667
  async linkAlong(path) {
591
668
  for (const entry of await this.meta.get(prefixes(path))) {
592
669
  if (entry?.link === void 0) continue;
593
- if (!await this.real(entry.path) && await this.real(parentOf2(entry.path))) return entry;
670
+ if (!await this.real(entry.path) && await this.real(parentOf3(entry.path))) return entry;
594
671
  }
595
672
  return void 0;
596
673
  }
@@ -605,7 +682,7 @@ var OpfsFs = class {
605
682
  if (!link2) throw err;
606
683
  if (link2.path === current && !follow2) return { path: current, link: link2 };
607
684
  const target = `${link2.link}${current.slice(link2.path.length)}`;
608
- current = target.split("/").includes("..") ? await this.physical(parentOf2(link2.path), target, hops + 1) : resolvePath(parentOf2(link2.path), target);
685
+ current = target.split("/").includes("..") ? await this.physical(parentOf3(link2.path), target, hops + 1) : resolvePath(parentOf3(link2.path), target);
609
686
  }
610
687
  }
611
688
  throw fsError("ELOOP", path);
@@ -795,8 +872,8 @@ var OpfsFs = class {
795
872
  if (!missing(err)) throw err;
796
873
  }
797
874
  const [entry] = await this.meta.get([path]);
798
- const wanted = entry?.link === void 0 ? path : resolvePath(parentOf2(path), entry.link);
799
- return [resolvePath((await this.locate(parentOf2(wanted), true)).path, baseOf(wanted)), false];
875
+ const wanted = entry?.link === void 0 ? path : resolvePath(parentOf3(path), entry.link);
876
+ return [resolvePath((await this.locate(parentOf3(wanted), true)).path, baseOf(wanted)), false];
800
877
  }
801
878
  async fileFor(path) {
802
879
  const [target, existed] = await this.writeTarget(path);
@@ -806,7 +883,7 @@ var OpfsFs = class {
806
883
  dir,
807
884
  (parent) => parent.getFileHandle(name2, { create: true })
808
885
  );
809
- if (!existed) this.touch(parentOf2(target));
886
+ if (!existed) this.touch(parentOf3(target));
810
887
  return [handle, existed, target];
811
888
  } catch (err) {
812
889
  const code = err?.name === "TypeMismatchError" ? "EISDIR" : null;
@@ -817,11 +894,13 @@ var OpfsFs = class {
817
894
  const [file, existed, target] = await this.fileFor(path);
818
895
  if (!existed) await this.stampNew(target, owner, false);
819
896
  if (!existed && content.length === 0) return;
897
+ let writable2;
820
898
  try {
821
- const writable2 = await file.createWritable();
899
+ writable2 = await file.createWritable();
822
900
  await writable2.write(content);
823
901
  await writable2.close();
824
902
  } catch (err) {
903
+ await writable2?.abort().catch(() => void 0);
825
904
  throw translate(err, path, "commit", false);
826
905
  }
827
906
  }
@@ -876,9 +955,9 @@ var OpfsFs = class {
876
955
  gid: owner.gid,
877
956
  ...mode ? { mode, parents: mode } : {}
878
957
  };
879
- await this.mkdir(parentOf2(path), { recursive: true, ...above ? { owner: above } : {} });
958
+ await this.mkdir(parentOf3(path), { recursive: true, ...above ? { owner: above } : {} });
880
959
  }
881
- const parent = await this.locate(parentOf2(path), true);
960
+ const parent = await this.locate(parentOf3(path), true);
882
961
  if (parent.handle?.kind !== "directory") throw fsError("ENOTDIR", path);
883
962
  await parent.handle.getDirectoryHandle(baseOf(path), { create: true });
884
963
  const made2 = `${parent.path === "/" ? "" : parent.path}/${baseOf(path)}`;
@@ -907,7 +986,7 @@ var OpfsFs = class {
907
986
  this.changed(found.path);
908
987
  }
909
988
  await this.meta.remove([found.path]);
910
- this.touch(parentOf2(found.path));
989
+ this.touch(parentOf3(found.path));
911
990
  }
912
991
  links() {
913
992
  return false;
@@ -919,7 +998,7 @@ var OpfsFs = class {
919
998
  if (from === to) return;
920
999
  const source = await this.locate(from, false);
921
1000
  this.forget(source.path);
922
- const parent = await this.locate(parentOf2(to), true);
1001
+ const parent = await this.locate(parentOf3(to), true);
923
1002
  const destination = resolvePath(parent.path, baseOf(to));
924
1003
  const sourceStat = await this.describe(source);
925
1004
  if (sourceStat.isDirectory && within(destination, source.path)) throw fsError("EINVAL", to);
@@ -947,7 +1026,7 @@ var OpfsFs = class {
947
1026
  if (source.link) {
948
1027
  if (target?.handle) await this.rm(destination);
949
1028
  await this.meta.move(source.path, destination, carry);
950
- this.touch(parentOf2(source.path), parent.path);
1029
+ this.touch(parentOf3(source.path), parent.path);
951
1030
  return;
952
1031
  }
953
1032
  const dir = parent.handle;
@@ -969,7 +1048,7 @@ var OpfsFs = class {
969
1048
  }
970
1049
  if (copied) await this.restamp(destination, timed2);
971
1050
  this.changed(source.path, destination);
972
- this.touch(parentOf2(source.path), parent.path);
1051
+ this.touch(parentOf3(source.path), parent.path);
973
1052
  }
974
1053
  async modified(path) {
975
1054
  const handle = await this.handle(path);
@@ -1015,7 +1094,7 @@ var OpfsFs = class {
1015
1094
  for await (const child of handle.values()) await this.copy(child, target, child.name);
1016
1095
  }
1017
1096
  async symlink(target, path, owner) {
1018
- const parent = await this.locate(parentOf2(path), true);
1097
+ const parent = await this.locate(parentOf3(path), true);
1019
1098
  if (parent.handle?.kind !== "directory") throw fsError("ENOTDIR", path);
1020
1099
  const at = resolvePath(parent.path, baseOf(path));
1021
1100
  if (await this.locate(at, false).then(
@@ -1091,7 +1170,7 @@ var OpfsFs = class {
1091
1170
  let removed = 0;
1092
1171
  for (const entry of await this.meta.all()) {
1093
1172
  const real = await this.real(entry.path);
1094
- const stale = entry.link === void 0 ? !real : real || !await this.real(parentOf2(entry.path));
1173
+ const stale = entry.link === void 0 ? !real : real || !await this.real(parentOf3(entry.path));
1095
1174
  if (!stale) continue;
1096
1175
  await this.meta.update(entry.path, () => void 0);
1097
1176
  removed++;
@@ -1262,7 +1341,7 @@ function versionError(theirs) {
1262
1341
 
1263
1342
  // src/fs/walk.ts
1264
1343
  var MAX_LINKS2 = 40;
1265
- function parentOf3(path) {
1344
+ function parentOf4(path) {
1266
1345
  return path.slice(0, path.lastIndexOf("/")) || "/";
1267
1346
  }
1268
1347
  async function mustBeDir(fs, path) {
@@ -1280,7 +1359,7 @@ async function realDir(fs, dir, budget = { hops: 0 }) {
1280
1359
  const part = queue.shift();
1281
1360
  if (part === ".") continue;
1282
1361
  if (part === "..") {
1283
- at = parentOf3(at);
1362
+ at = parentOf4(at);
1284
1363
  continue;
1285
1364
  }
1286
1365
  const next2 = at === "/" ? `/${part}` : `${at}/${part}`;
@@ -1302,7 +1381,7 @@ function joinName(dir, name2) {
1302
1381
  }
1303
1382
  async function realTarget(fs, path) {
1304
1383
  const budget = { hops: 0 };
1305
- let dir = await realDir(fs, parentOf3(path), budget);
1384
+ let dir = await realDir(fs, parentOf4(path), budget);
1306
1385
  let name2 = path.slice(path.lastIndexOf("/") + 1);
1307
1386
  for (; ; ) {
1308
1387
  const link2 = await linkOf(fs, joinName(dir, name2));
@@ -1310,7 +1389,7 @@ async function realTarget(fs, path) {
1310
1389
  if (budget.hops >= MAX_LINKS2) throw fsError("ELOOP", path);
1311
1390
  budget.hops++;
1312
1391
  const target = link2.startsWith("/") ? link2 : joinName(dir, link2);
1313
- dir = await realDir(fs, parentOf3(target), budget);
1392
+ dir = await realDir(fs, parentOf4(target), budget);
1314
1393
  name2 = target.slice(target.lastIndexOf("/") + 1);
1315
1394
  }
1316
1395
  return joinName(dir, name2);
@@ -1384,7 +1463,7 @@ async function walk(fs, cred, path, follow2) {
1384
1463
  if (!dir) return { path: joinName(at, [part, ...queue].join("/")), perm: void 0 };
1385
1464
  if (!permits(cred, dir, X_OK)) return { path, perm: void 0, denied: at };
1386
1465
  if (part === "..") {
1387
- at = parentOf3(at);
1466
+ at = parentOf4(at);
1388
1467
  continue;
1389
1468
  }
1390
1469
  const next2 = joinName(at, part);
@@ -1506,17 +1585,17 @@ async function stickyEntry(g, op, path, dir) {
1506
1585
  }
1507
1586
  async function inParent(g, op, path, old = false) {
1508
1587
  if (old === "replaced") {
1509
- const dir2 = await need(g, op, parentOf3(path), W_OK | X_OK, DIR, path);
1588
+ const dir2 = await need(g, op, parentOf4(path), W_OK | X_OK, DIR, path);
1510
1589
  return dir2.denied === void 0 ? stickyEntry(g, op, path, dir2) : void 0;
1511
1590
  }
1512
- if (!old) return void await need(g, op, parentOf3(path), W_OK | X_OK, DIR, path);
1513
- const dir = await access(g.fs, g.cred, parentOf3(path), W_OK | X_OK, DIR);
1591
+ if (!old) return void await need(g, op, parentOf4(path), W_OK | X_OK, DIR, path);
1592
+ const dir = await access(g.fs, g.cred, parentOf4(path), W_OK | X_OK, DIR);
1514
1593
  if (dir.denied === void 0) return stickyEntry(g, op, path, dir);
1515
1594
  const lookup = dir.denied === dir.path && dir.perm && permits(g.cred, dir.perm, X_OK);
1516
1595
  if (!lookup || await present(g.fs, entryIn(dir, path), false)) deny(g, op, path);
1517
1596
  }
1518
1597
  async function makeIn(g, op, path) {
1519
- for (let dir = parentOf3(path); ; dir = parentOf3(dir)) {
1598
+ for (let dir = parentOf4(path); ; dir = parentOf4(dir)) {
1520
1599
  const walked = await access(g.fs, g.cred, dir, W_OK | X_OK, DIR);
1521
1600
  if (walked.denied !== void 0) {
1522
1601
  if (!await present(g.fs, path, true)) deny(g, op, path);
@@ -1544,7 +1623,7 @@ async function renamable(g, from, to) {
1544
1623
  if (from === to) return void await need(g, "rename", from, 0, { follow: false });
1545
1624
  await inParent(g, "rename", from, true);
1546
1625
  await inParent(g, "rename", to, "replaced");
1547
- if (g.cred.euid === 0 || parentOf3(from) === parentOf3(to)) return;
1626
+ if (g.cred.euid === 0 || parentOf4(from) === parentOf4(to)) return;
1548
1627
  const st = await g.fs.lstat(from).catch(() => void 0);
1549
1628
  if (st?.isDirectory && !permits(g.cred, permOf(st), W_OK)) deny(g, "rename", from);
1550
1629
  }
@@ -1598,7 +1677,7 @@ async function writable(g, op, path) {
1598
1677
  }
1599
1678
  async function creatable(g, op, path) {
1600
1679
  const target = await need(g, op, path, 0);
1601
- if (target.denied === void 0) await need(g, op, parentOf3(target.path), W_OK | X_OK, DIR, path);
1680
+ if (target.denied === void 0) await need(g, op, parentOf4(target.path), W_OK | X_OK, DIR, path);
1602
1681
  }
1603
1682
  async function openable(g, op, path, want, create) {
1604
1683
  const st = create ? await g.fs.stat(path).catch(() => void 0) : void 0;
@@ -3516,7 +3595,7 @@ function cdpRegistry(send, gone) {
3516
3595
  function visibleTo(g, notify) {
3517
3596
  if (!g?.dac.enforcing) return notify;
3518
3597
  let queue = Promise.resolve();
3519
- const seen = async (path) => (await access(g.fs, g.cred, parentOf3(path), R_OK | X_OK, { directory: true })).denied === void 0;
3598
+ const seen = async (path) => (await access(g.fs, g.cred, parentOf4(path), R_OK | X_OK, { directory: true })).denied === void 0;
3520
3599
  return (change) => {
3521
3600
  queue = queue.then(async () => {
3522
3601
  if (!("paths" in change)) return notify(change);
@@ -6504,7 +6583,7 @@ function webLocks() {
6504
6583
  return globalThis.navigator?.locks;
6505
6584
  }
6506
6585
  var within3 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
6507
- var parentOf4 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
6586
+ var parentOf5 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
6508
6587
  var lockName = (instance) => `slicc-unlinked:${instance}`;
6509
6588
  var dirOf = (root) => root === "/" ? `/${UNLINKED_DIR}` : `${root}/${UNLINKED_DIR}`;
6510
6589
  var PathGate = class {
@@ -6637,7 +6716,7 @@ var UnlinkedKeeper = class {
6637
6716
  await this.serial(async () => {
6638
6717
  await Promise.allSettled([this.fs.rename(file.hidden, path)]);
6639
6718
  this.unlinkedName(file.hidden);
6640
- await this.tidy(parentOf4(file.hidden));
6719
+ await this.tidy(parentOf5(file.hidden));
6641
6720
  });
6642
6721
  }
6643
6722
  done4();
@@ -6678,7 +6757,7 @@ var UnlinkedKeeper = class {
6678
6757
  }
6679
6758
  await this.fs.rm(hidden).catch(() => void 0);
6680
6759
  this.unlinkedName(hidden);
6681
- await this.tidy(parentOf4(hidden));
6760
+ await this.tidy(parentOf5(hidden));
6682
6761
  }
6683
6762
  async linkedName(hidden) {
6684
6763
  if (!mayLink(this.fs, hidden)) return;
@@ -6723,10 +6802,10 @@ var UnlinkedKeeper = class {
6723
6802
  // src/fs/watch.ts
6724
6803
  var WATCH_BATCH_LIMIT = 1e3;
6725
6804
  var under2 = (path, dir) => dir === "/" || path.startsWith(`${dir}/`);
6726
- var parentOf5 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
6805
+ var parentOf6 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
6727
6806
  function touches(watcher, changed) {
6728
6807
  return watcher.paths.some(
6729
- (watched) => changed === watched || under2(watched, changed) || (watcher.recursive ? under2(changed, watched) : parentOf5(changed) === watched)
6808
+ (watched) => changed === watched || under2(watched, changed) || (watcher.recursive ? under2(changed, watched) : parentOf6(changed) === watched)
6730
6809
  );
6731
6810
  }
6732
6811
  var FsWatchers = class {
@@ -7303,7 +7382,7 @@ var HeldPaths = class extends Map {
7303
7382
  return this.revoked.has(path);
7304
7383
  }
7305
7384
  };
7306
- var parentOf6 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
7385
+ var parentOf7 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
7307
7386
  var readonly = (mount) => mount.caps.readonly === true || mount.options.ro !== void 0;
7308
7387
  function inodeless(mount, attr2) {
7309
7388
  if (attr2.ino !== void 0) return false;
@@ -7583,7 +7662,7 @@ var MountTable = class {
7583
7662
  mount.epoch++;
7584
7663
  for (const key of [...mount.attrs.keys()]) if (within4(key, path)) mount.attrs.delete(key);
7585
7664
  mount.lists.delete(path);
7586
- mount.lists.delete(parentOf6(path));
7665
+ mount.lists.delete(parentOf7(path));
7587
7666
  for (const key of [...mount.lists.keys()]) if (within4(key, path)) mount.lists.delete(key);
7588
7667
  }
7589
7668
  async getattr(mount, path, rel2, settled = false) {
@@ -7659,7 +7738,7 @@ var MountFs = class {
7659
7738
  if (attr2.kind !== "symlink") return real;
7660
7739
  if (hops >= MAX_LINKS6) throw errnoError("ELOOP", path);
7661
7740
  const target = await found.mount.conn.call({ op: "readlink", path: found.rel });
7662
- return this.follow(this.base.resolvePath(parentOf6(real), target), hops + 1, create);
7741
+ return this.follow(this.base.resolvePath(parentOf7(real), target), hops + 1, create);
7663
7742
  }
7664
7743
  async open(mount, rel2, write, owner) {
7665
7744
  return await mount.conn.call({
@@ -7675,8 +7754,8 @@ var MountFs = class {
7675
7754
  async driverOwner(found, real, owner, directory) {
7676
7755
  const { mount, rel: rel2 } = found;
7677
7756
  if (!owner || !mount.owners || !mount.caps.owners) return void 0;
7678
- const dir = parentOf6(real);
7679
- const above = statOf(await this.table.getattr(mount, dir, parentOf6(rel2)), dir, mount);
7757
+ const dir = parentOf7(real);
7758
+ const above = statOf(await this.table.getattr(mount, dir, parentOf7(rel2)), dir, mount);
7680
7759
  const setgid = (above.mode & S_ISGID4) !== 0;
7681
7760
  let mode = owner.mode;
7682
7761
  if (directory && setgid) mode = (mode ?? 493) | S_ISGID4;
@@ -8015,7 +8094,7 @@ var MountFs = class {
8015
8094
  }
8016
8095
  async named(path) {
8017
8096
  const at = normalizePath(path);
8018
- return joinName(await realDir(this, parentOf6(at)), at.slice(at.lastIndexOf("/") + 1));
8097
+ return joinName(await realDir(this, parentOf7(at)), at.slice(at.lastIndexOf("/") + 1));
8019
8098
  }
8020
8099
  async link(source, target, follow2 = false) {
8021
8100
  const from = follow2 ? await realTarget(this, normalizePath(source)) : await this.named(source);
@@ -10753,7 +10832,7 @@ var VfsNodes = class {
10753
10832
  return run2;
10754
10833
  }
10755
10834
  async entryKey(path) {
10756
- const parent = parentOf3(path);
10835
+ const parent = parentOf4(path);
10757
10836
  const dir = await this.real(parent, true, () => realDir(this.fs, parent));
10758
10837
  return joinName(dir, path.slice(path.lastIndexOf("/") + 1));
10759
10838
  }
@@ -10955,7 +11034,7 @@ function errCode(err) {
10955
11034
  return err?.code;
10956
11035
  }
10957
11036
  async function targetDir(fs, path) {
10958
- return parentOf3(await realTarget(fs, path).catch(() => path));
11037
+ return parentOf4(await realTarget(fs, path).catch(() => path));
10959
11038
  }
10960
11039
  async function refuseReadonly(fs, path, flags, opts = {}) {
10961
11040
  const writes = (flags & O_ACCMODE) !== 0 || opts.truncate === true;
@@ -15167,7 +15246,7 @@ var Launcher = class {
15167
15246
  async realLayoutPath(path) {
15168
15247
  if (!await this.base.exists(path)) return void 0;
15169
15248
  if (path === "/") return path;
15170
- return joinName(await realDir(this.base, parentOf3(path)), baseName(path));
15249
+ return joinName(await realDir(this.base, parentOf4(path)), baseName(path));
15171
15250
  }
15172
15251
  async provisionPnpmHome() {
15173
15252
  await this.base.mkdir(this.pnpmHome, { recursive: true }).catch(() => void 0);