@ai-ecoverse/slicc-kernel 1.61.2 → 1.62.1

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.
@@ -73,7 +73,7 @@ var IndexedDbMeta = class {
73
73
  this.generation++;
74
74
  for (const path of paths) this.cache.delete(path);
75
75
  for (const key of [...this.cache.keys()]) {
76
- if (trees.some((tree) => under(key, tree))) this.cache.delete(key);
76
+ if (trees.some((tree2) => under(key, tree2))) this.cache.delete(key);
77
77
  }
78
78
  }
79
79
  changed(invalidation2) {
@@ -195,13 +195,13 @@ function fsError(code, path) {
195
195
  return Object.assign(new Error(`${code}: ${path}`), { code });
196
196
  }
197
197
  function normalizePath(path) {
198
- const parts = [];
198
+ const parts2 = [];
199
199
  for (const part of path.split("/")) {
200
200
  if (part === "" || part === ".") continue;
201
- if (part === "..") parts.pop();
202
- else parts.push(part);
201
+ if (part === "..") parts2.pop();
202
+ else parts2.push(part);
203
203
  }
204
- return `/${parts.join("/")}`;
204
+ return `/${parts2.join("/")}`;
205
205
  }
206
206
  function resolvePath(base, path) {
207
207
  return normalizePath(path.startsWith("/") ? path : `${base}/${path}`);
@@ -239,8 +239,8 @@ function inherit(entries) {
239
239
  return { ...ROOT_OWNER };
240
240
  }
241
241
  function chain(path) {
242
- const parts = path.split("/").filter(Boolean);
243
- return ["/", ...parts.map((_, i) => `/${parts.slice(0, i + 1).join("/")}`)];
242
+ const parts2 = path.split("/").filter(Boolean);
243
+ return ["/", ...parts2.map((_, i) => `/${parts2.slice(0, i + 1).join("/")}`)];
244
244
  }
245
245
  function sameOwner(a, b) {
246
246
  return a.uid === b.uid && a.gid === b.gid;
@@ -315,10 +315,10 @@ function translate(err, path, op = "lookup", mapped = true) {
315
315
  var META_OPS = ["get", "update", "move", "remove", "links", "under", "all"];
316
316
  function sidecar(meta) {
317
317
  const pathOf = ([first]) => [first, "/"].flat().find((p) => typeof p === "string");
318
- const guarded = (op) => (...args) => meta[op].apply(meta, args).catch((err) => {
318
+ const guarded2 = (op) => (...args) => meta[op].apply(meta, args).catch((err) => {
319
319
  throw translate(err, pathOf(args), "sidecar", false);
320
320
  });
321
- return Object.fromEntries(META_OPS.map((op) => [op, guarded(op)]));
321
+ return Object.fromEntries(META_OPS.map((op) => [op, guarded2(op)]));
322
322
  }
323
323
  function missing(err) {
324
324
  const code = err?.code;
@@ -334,8 +334,8 @@ function baseOf(path) {
334
334
  return path.slice(path.lastIndexOf("/") + 1);
335
335
  }
336
336
  function prefixes(path) {
337
- const parts = split(path);
338
- return parts.map((_, i) => `/${parts.slice(0, i + 1).join("/")}`);
337
+ const parts2 = split(path);
338
+ return parts2.map((_, i) => `/${parts2.slice(0, i + 1).join("/")}`);
339
339
  }
340
340
  function within(path, root) {
341
341
  return path === root || path.startsWith(`${root}/`);
@@ -356,39 +356,39 @@ async function openSync(handle) {
356
356
  }
357
357
  }
358
358
  }
359
- function writeAll(access, at, bytes) {
359
+ function writeAll(access2, at, bytes) {
360
360
  for (let done4 = 0; done4 < bytes.length; ) {
361
- const n = access.write(bytes.subarray(done4), { at: at + done4 });
361
+ const n = access2.write(bytes.subarray(done4), { at: at + done4 });
362
362
  if (!(n > 0))
363
363
  throw Object.assign(new Error("the file system took no more bytes"), { code: "ENOSPC" });
364
364
  done4 += n;
365
365
  }
366
366
  }
367
367
  async function positioned(handle, op) {
368
- const access = await openSync(handle);
369
- if (access) {
368
+ const access2 = await openSync(handle);
369
+ if (access2) {
370
370
  try {
371
371
  op({
372
- write: (at, bytes) => writeAll(access, at, bytes),
373
- truncate: (n) => access.truncate(n)
372
+ write: (at, bytes) => writeAll(access2, at, bytes),
373
+ truncate: (n) => access2.truncate(n)
374
374
  });
375
- access.flush();
375
+ access2.flush();
376
376
  } finally {
377
- access.close();
377
+ access2.close();
378
378
  }
379
379
  return;
380
380
  }
381
- const writable = await handle.createWritable({ keepExistingData: true });
381
+ const writable2 = await handle.createWritable({ keepExistingData: true });
382
382
  try {
383
383
  await op({
384
- write: (at, bytes) => writable.write({ type: "write", position: at, data: bytes }),
385
- truncate: (n) => writable.truncate(n)
384
+ write: (at, bytes) => writable2.write({ type: "write", position: at, data: bytes }),
385
+ truncate: (n) => writable2.truncate(n)
386
386
  });
387
387
  } catch (err) {
388
- await writable.abort();
388
+ await writable2.abort();
389
389
  throw err;
390
390
  }
391
- await writable.close();
391
+ await writable2.close();
392
392
  }
393
393
  function syncAccessHandles(scope = globalThis) {
394
394
  const handle = scope.FileSystemFileHandle;
@@ -427,6 +427,14 @@ var OpfsFs = class {
427
427
  trackOwners() {
428
428
  this.owners = true;
429
429
  }
430
+ async ancestry(path) {
431
+ const entries = await this.meta.get(chain(path));
432
+ return entries.map((entry, i) => ({
433
+ ...ownOf(entry) ?? (i === 0 ? ROOT_OWNER : inherit(entries.slice(0, i))),
434
+ ...entry?.mode !== void 0 ? { mode: entry.mode } : {},
435
+ ...entry?.link !== void 0 ? { link: entry.link } : {}
436
+ }));
437
+ }
430
438
  async handedTo(dir) {
431
439
  return inherit(await this.meta.get(chain(dir)));
432
440
  }
@@ -491,30 +499,30 @@ var OpfsFs = class {
491
499
  const [stored] = source.link ? [source.link] : await this.meta.get([source.path]);
492
500
  return ownOf(stored) ? void 0 : this.ownerAt(source.path, stored);
493
501
  }
494
- async walk(parts, from, start2) {
502
+ async walk(parts2, from, start2) {
495
503
  let dir = start2;
496
- for (let at = from; at < parts.length; at++) {
497
- dir = await dir.getDirectoryHandle(parts[at]);
498
- this.dirs.set(`/${parts.slice(0, at + 1).join("/")}`, dir);
504
+ for (let at = from; at < parts2.length; at++) {
505
+ dir = await dir.getDirectoryHandle(parts2[at]);
506
+ this.dirs.set(`/${parts2.slice(0, at + 1).join("/")}`, dir);
499
507
  this.checked.set(dir, performance.now());
500
508
  }
501
509
  return dir;
502
510
  }
503
511
  async dir(path) {
504
- const parts = split(path);
505
- let from = parts.length;
512
+ const parts2 = split(path);
513
+ let from = parts2.length;
506
514
  let cached;
507
515
  for (; from > 0 && !cached; from--)
508
- cached = this.dirs.get(`/${parts.slice(0, from).join("/")}`);
516
+ cached = this.dirs.get(`/${parts2.slice(0, from).join("/")}`);
509
517
  if (cached) from++;
510
- const at = `/${parts.slice(0, from).join("/")}`;
518
+ const at = `/${parts2.slice(0, from).join("/")}`;
511
519
  if (cached && !await this.current(at, cached)) {
512
520
  this.forget(at);
513
521
  cached = void 0;
514
522
  from = 0;
515
523
  }
516
524
  try {
517
- return await this.walk(parts, from, cached ?? this.root);
525
+ return await this.walk(parts2, from, cached ?? this.root);
518
526
  } catch (err) {
519
527
  throw translate(err, path);
520
528
  }
@@ -544,10 +552,10 @@ var OpfsFs = class {
544
552
  return kind === "file" ? parent.getFileHandle(name2) : parent.getDirectoryHandle(name2);
545
553
  }
546
554
  async parent(path) {
547
- const parts = split(path);
548
- const name2 = parts.pop();
555
+ const parts2 = split(path);
556
+ const name2 = parts2.pop();
549
557
  if (name2 === void 0) throw fsError("EBUSY", path);
550
- return [`/${parts.join("/")}`, name2];
558
+ return [`/${parts2.join("/")}`, name2];
551
559
  }
552
560
  async handle(path) {
553
561
  if (split(path).length === 0) return this.root;
@@ -749,19 +757,19 @@ var OpfsFs = class {
749
757
  }
750
758
  async writeSession(path, truncate) {
751
759
  const handle = (await this.fileFor(path))[0];
752
- const writable = await this.retrying(
760
+ const writable2 = await this.retrying(
753
761
  path,
754
762
  () => handle.createWritable({ keepExistingData: !truncate }),
755
763
  true
756
764
  );
757
765
  return {
758
- write: (at, bytes) => writable.write({ type: "write", position: at, data: bytes }).catch((err) => {
766
+ write: (at, bytes) => writable2.write({ type: "write", position: at, data: bytes }).catch((err) => {
759
767
  throw translate(err, path, "write", false);
760
768
  }),
761
- truncate: (size) => writable.truncate(size).catch((err) => {
769
+ truncate: (size) => writable2.truncate(size).catch((err) => {
762
770
  throw translate(err, path, "write", false);
763
771
  }),
764
- close: () => writable.close().catch((err) => {
772
+ close: () => writable2.close().catch((err) => {
765
773
  throw translate(err, path, "commit", false);
766
774
  })
767
775
  };
@@ -799,9 +807,9 @@ var OpfsFs = class {
799
807
  if (!existed) await this.stampNew(target, owner, false);
800
808
  if (!existed && content.length === 0) return;
801
809
  try {
802
- const writable = await file.createWritable();
803
- await writable.write(content);
804
- await writable.close();
810
+ const writable2 = await file.createWritable();
811
+ await writable2.write(content);
812
+ await writable2.close();
805
813
  } catch (err) {
806
814
  throw translate(err, path, "commit", false);
807
815
  }
@@ -987,9 +995,9 @@ var OpfsFs = class {
987
995
  }
988
996
  async copy(handle, dir, name2) {
989
997
  if (handle.kind === "file") {
990
- const writable = await (await dir.getFileHandle(name2, { create: true })).createWritable();
991
- await writable.write(await handle.getFile());
992
- await writable.close();
998
+ const writable2 = await (await dir.getFileHandle(name2, { create: true })).createWritable();
999
+ await writable2.write(await handle.getFile());
1000
+ await writable2.close();
993
1001
  return;
994
1002
  }
995
1003
  const target = await dir.getDirectoryHandle(name2, { create: true });
@@ -1241,6 +1249,298 @@ function versionError(theirs) {
1241
1249
  return `slicc-kernel client protocol ${named} is not supported: this side speaks ${PROTOCOL.join(".")}`;
1242
1250
  }
1243
1251
 
1252
+ // src/fs/walk.ts
1253
+ var MAX_LINKS2 = 40;
1254
+ function parentOf3(path) {
1255
+ return path.slice(0, path.lastIndexOf("/")) || "/";
1256
+ }
1257
+ async function mustBeDir(fs, path) {
1258
+ if (!fs.lstat) return;
1259
+ const st = await fs.lstat(path);
1260
+ if (st.isDirectory !== true) throw fsError("ENOTDIR", path);
1261
+ }
1262
+ async function linkOf(fs, path) {
1263
+ return fs.readlink?.(path).catch(() => void 0);
1264
+ }
1265
+ async function realDir(fs, dir, budget = { hops: 0 }) {
1266
+ const queue = dir.split("/").filter(Boolean);
1267
+ let at = "/";
1268
+ while (queue.length > 0) {
1269
+ const part = queue.shift();
1270
+ if (part === ".") continue;
1271
+ if (part === "..") {
1272
+ at = parentOf3(at);
1273
+ continue;
1274
+ }
1275
+ const next2 = at === "/" ? `/${part}` : `${at}/${part}`;
1276
+ const link2 = await linkOf(fs, next2);
1277
+ if (link2 === void 0) {
1278
+ if (queue.find((p) => p !== ".") === "..") await mustBeDir(fs, next2);
1279
+ at = next2;
1280
+ continue;
1281
+ }
1282
+ if (budget.hops >= MAX_LINKS2) throw fsError("ELOOP", dir);
1283
+ budget.hops++;
1284
+ queue.unshift(...link2.split("/").filter(Boolean));
1285
+ if (link2.startsWith("/")) at = "/";
1286
+ }
1287
+ return at;
1288
+ }
1289
+ function joinName(dir, name2) {
1290
+ return dir === "/" ? `/${name2}` : `${dir}/${name2}`;
1291
+ }
1292
+ async function realTarget(fs, path) {
1293
+ const budget = { hops: 0 };
1294
+ let dir = await realDir(fs, parentOf3(path), budget);
1295
+ let name2 = path.slice(path.lastIndexOf("/") + 1);
1296
+ for (; ; ) {
1297
+ const link2 = await linkOf(fs, joinName(dir, name2));
1298
+ if (link2 === void 0) break;
1299
+ if (budget.hops >= MAX_LINKS2) throw fsError("ELOOP", path);
1300
+ budget.hops++;
1301
+ const target = link2.startsWith("/") ? link2 : joinName(dir, link2);
1302
+ dir = await realDir(fs, parentOf3(target), budget);
1303
+ name2 = target.slice(target.lastIndexOf("/") + 1);
1304
+ }
1305
+ return joinName(dir, name2);
1306
+ }
1307
+
1308
+ // src/fs/access.ts
1309
+ var R_OK = 4;
1310
+ var W_OK = 2;
1311
+ var X_OK = 1;
1312
+ var MAX_LINKS3 = 40;
1313
+ var S_ISVTX = 512;
1314
+ function permOf(st, link2) {
1315
+ return {
1316
+ uid: st.uid ?? 0,
1317
+ gid: st.gid ?? 0,
1318
+ mode: st.mode & 4095,
1319
+ ...link2 !== void 0 ? { link: link2 } : {}
1320
+ };
1321
+ }
1322
+ function permits(cred, perm, want, directory = true) {
1323
+ const mode = perm.mode ?? (directory ? 493 : 420);
1324
+ if (cred.euid === 0) return (want & X_OK) === 0 || directory || (mode & 73) !== 0;
1325
+ const shift = cred.euid === perm.uid ? 6 : inGroup(cred, perm.gid) ? 3 : 0;
1326
+ return (mode >> shift & want) === want;
1327
+ }
1328
+ function sticky(cred, dir, entry) {
1329
+ if (cred.euid === 0 || ((dir.mode ?? 0) & S_ISVTX) === 0) return true;
1330
+ return cred.euid === dir.uid || cred.euid === (entry ?? dir).uid;
1331
+ }
1332
+ async function ancestryOf(fs, path) {
1333
+ if (fs.ancestry) return fs.ancestry(path);
1334
+ const out = [];
1335
+ for (const at of chain(path)) {
1336
+ const st = await fs.lstat(at).catch(() => void 0);
1337
+ if (!st) break;
1338
+ out.push(permOf(st, st.isSymbolicLink ? await fs.readlink(at) : void 0));
1339
+ }
1340
+ return out;
1341
+ }
1342
+ var Perms = class {
1343
+ known = /* @__PURE__ */ new Map();
1344
+ fs;
1345
+ constructor(fs) {
1346
+ this.fs = fs;
1347
+ }
1348
+ async learn(path) {
1349
+ const names = chain(path);
1350
+ const perms = await ancestryOf(this.fs, path);
1351
+ for (let i = 0; i < names.length; i++) {
1352
+ const perm = perms[i];
1353
+ this.known.set(names[i], perm);
1354
+ if (perm === void 0 || perm.link !== void 0) break;
1355
+ }
1356
+ }
1357
+ async at(path) {
1358
+ if (!this.known.has(path)) await this.learn(path);
1359
+ return this.known.get(path);
1360
+ }
1361
+ };
1362
+ function parts(path) {
1363
+ return path.split("/").filter((part) => part !== "" && part !== ".");
1364
+ }
1365
+ async function walk(fs, cred, path, follow2) {
1366
+ const perms = new Perms(fs);
1367
+ await perms.learn(path);
1368
+ const queue = parts(path);
1369
+ let at = "/";
1370
+ for (let hops = 0; queue.length > 0; ) {
1371
+ const part = queue.shift();
1372
+ const dir = await perms.at(at);
1373
+ if (!dir) return { path: joinName(at, [part, ...queue].join("/")), perm: void 0 };
1374
+ if (!permits(cred, dir, X_OK)) return { path, perm: void 0, denied: at };
1375
+ if (part === "..") {
1376
+ at = parentOf3(at);
1377
+ continue;
1378
+ }
1379
+ const next2 = joinName(at, part);
1380
+ const perm = await perms.at(next2);
1381
+ const last = queue.length === 0;
1382
+ if (perm?.link !== void 0 && (!last || follow2)) {
1383
+ if (++hops > MAX_LINKS3) return { path: next2, perm: void 0 };
1384
+ queue.unshift(...parts(perm.link));
1385
+ if (perm.link.startsWith("/")) at = "/";
1386
+ continue;
1387
+ }
1388
+ if (last) return { path: next2, perm };
1389
+ at = next2;
1390
+ }
1391
+ return { path: at, perm: await perms.at(at) };
1392
+ }
1393
+ async function present(fs, path, directory) {
1394
+ const st = await fs.lstat(path).catch(() => void 0);
1395
+ return st !== void 0 && (!directory || st.isDirectory);
1396
+ }
1397
+ async function access(fs, cred, path, want, options = {}) {
1398
+ const walked = await walk(fs, cred, path, options.follow !== false);
1399
+ if (walked.denied !== void 0) {
1400
+ if (await present(fs, walked.denied, true)) return walked;
1401
+ return { path, perm: void 0, missing: true };
1402
+ }
1403
+ const { perm } = walked;
1404
+ if (!perm) return { ...walked, missing: true };
1405
+ if (want === 0 || permits(cred, perm, want, options.directory !== false)) return walked;
1406
+ if (await present(fs, walked.path, false)) return { ...walked, denied: walked.path };
1407
+ return { ...walked, missing: true };
1408
+ }
1409
+ function refused(path, errno = "EACCES") {
1410
+ return fsError(errno, path);
1411
+ }
1412
+
1413
+ // src/fs/dac.ts
1414
+ var MAX_DENIAL_KEYS = 1e4;
1415
+ var S_ISGID3 = 1024;
1416
+ var Dac = class {
1417
+ mode;
1418
+ hook;
1419
+ log;
1420
+ seen = /* @__PURE__ */ new Set();
1421
+ suppressed = false;
1422
+ constructor(mode, hook, log = (line) => console.warn(line)) {
1423
+ this.mode = mode;
1424
+ this.hook = hook;
1425
+ this.log = log;
1426
+ }
1427
+ get enforcing() {
1428
+ return this.mode === "enforce";
1429
+ }
1430
+ denied(denial) {
1431
+ try {
1432
+ this.hook?.({ ...denial });
1433
+ } catch {
1434
+ }
1435
+ if (this.mode === "report") this.note(denial);
1436
+ else throw refused(denial.path, denial.errno);
1437
+ }
1438
+ note(d) {
1439
+ const key = `${d.uid}\0${d.op}\0${d.errno}\0${d.path}`;
1440
+ if (this.seen.has(key)) return;
1441
+ if (this.seen.size >= MAX_DENIAL_KEYS) {
1442
+ if (!this.suppressed) this.log("slicc-kernel users report: more denials suppressed");
1443
+ this.suppressed = true;
1444
+ return;
1445
+ }
1446
+ this.seen.add(key);
1447
+ this.log(
1448
+ `slicc-kernel users report: ${d.errno} ${d.op} ${d.path} (uid ${d.uid}, pid ${d.pid})`
1449
+ );
1450
+ }
1451
+ };
1452
+ function deny(g, op, path, errno = "EACCES") {
1453
+ g.dac.denied({ pid: g.pid, uid: g.cred.euid, op, path, errno });
1454
+ }
1455
+ async function need(g, op, path, want, options = {}, shown = path) {
1456
+ const walked = await access(g.fs, g.cred, path, want, options);
1457
+ if (walked.denied !== void 0) deny(g, op, shown);
1458
+ return walked;
1459
+ }
1460
+ async function stickyEntry(g, op, path, dir) {
1461
+ const perm = dir.perm;
1462
+ if (!perm || sticky(g.cred, perm, perm)) return;
1463
+ const entry = joinName(dir.path, path.slice(path.lastIndexOf("/") + 1));
1464
+ if (sticky(g.cred, perm, (await ancestryOf(g.fs, entry)).at(-1))) return;
1465
+ if (await present(g.fs, entry, false)) deny(g, op, path, "EPERM");
1466
+ }
1467
+ async function inParent(g, op, path, old = false) {
1468
+ const dir = await need(g, op, parentOf3(path), W_OK | X_OK, {}, path);
1469
+ if (old && dir.denied === void 0) await stickyEntry(g, op, path, dir);
1470
+ }
1471
+ async function makeIn(g, op, path) {
1472
+ for (let dir = parentOf3(path); ; dir = parentOf3(dir)) {
1473
+ const walked = await access(g.fs, g.cred, dir, W_OK | X_OK);
1474
+ if (walked.denied !== void 0) {
1475
+ if (!await present(g.fs, path, true)) deny(g, op, path);
1476
+ return;
1477
+ }
1478
+ if (!walked.missing || dir === "/") return;
1479
+ }
1480
+ }
1481
+ async function tree(g, op, dir, perm) {
1482
+ if (!permits(g.cred, perm, R_OK | W_OK | X_OK)) return deny(g, op, dir);
1483
+ for (const [name2, st] of await g.fs.readdirStat(dir)) {
1484
+ if (!st) continue;
1485
+ const child = joinName(dir, name2);
1486
+ if (!sticky(g.cred, perm, permOf(st))) deny(g, op, child, "EPERM");
1487
+ if (st.isDirectory) await tree(g, op, child, permOf(st));
1488
+ }
1489
+ }
1490
+ async function removable(g, op, path) {
1491
+ await inParent(g, op, path, true);
1492
+ if (g.cred.euid === 0) return;
1493
+ const st = await g.fs.lstat(path).catch(() => void 0);
1494
+ if (st?.isDirectory) await tree(g, op, path, permOf(st));
1495
+ }
1496
+ async function renamable(g, from, to) {
1497
+ if (from === to) return void await need(g, "rename", from, 0, { follow: false });
1498
+ await inParent(g, "rename", from, true);
1499
+ await inParent(g, "rename", to, true);
1500
+ if (g.cred.euid === 0 || parentOf3(from) === parentOf3(to)) return;
1501
+ const st = await g.fs.lstat(from).catch(() => void 0);
1502
+ if (st?.isDirectory && !permits(g.cred, permOf(st), W_OK)) deny(g, "rename", from);
1503
+ }
1504
+ async function linkable(g, from, to, follow2) {
1505
+ const source = await need(g, "link", from, 0, { follow: follow2 });
1506
+ const perm = source.perm;
1507
+ if (perm && g.cred.euid !== 0 && perm.uid !== g.cred.euid) {
1508
+ const safe = permits(g.cred, perm, R_OK | W_OK, false);
1509
+ if (!safe && await present(g.fs, source.path, false)) deny(g, "link", from, "EPERM");
1510
+ }
1511
+ await inParent(g, "link", to);
1512
+ }
1513
+ async function ownerOnly(g, op, path, follow2) {
1514
+ const walked = await need(g, op, path, 0, { follow: follow2 });
1515
+ const perm = walked.perm;
1516
+ if (!perm || g.cred.euid === 0 || perm.uid === g.cred.euid) return walked;
1517
+ if (await present(g.fs, walked.path, false)) deny(g, op, path, "EPERM");
1518
+ return walked;
1519
+ }
1520
+ async function chmodMode(g, path, mode) {
1521
+ const perm = (await ownerOnly(g, "chmod", path, true)).perm;
1522
+ if (!g.dac.enforcing || !perm || g.cred.euid === 0 || inGroup(g.cred, perm.gid)) return mode;
1523
+ return mode & ~S_ISGID3;
1524
+ }
1525
+ async function retimable(g, op, path) {
1526
+ await ownerOnly(g, op, path, op !== "lutimes");
1527
+ }
1528
+ async function listable(g, op, path) {
1529
+ await need(g, op, path, R_OK | X_OK, { directory: true });
1530
+ }
1531
+ async function searchable(g, op, path, follow2) {
1532
+ await need(g, op, path, 0, { follow: follow2 });
1533
+ }
1534
+ async function readable(g, op, path) {
1535
+ await need(g, op, path, R_OK, { directory: false });
1536
+ }
1537
+ async function writable(g, op, path) {
1538
+ const st = await g.fs.stat(path).catch(() => void 0);
1539
+ if (st) return void await need(g, op, path, W_OK, { directory: st.isDirectory });
1540
+ const target = await need(g, op, path, 0);
1541
+ if (target.denied === void 0) await need(g, op, parentOf3(target.path), W_OK | X_OK, {}, path);
1542
+ }
1543
+
1244
1544
  // src/kernel/pipe.ts
1245
1545
  var PIPE_CAPACITY = 64 * 1024;
1246
1546
  var PipeError = class extends Error {
@@ -1484,7 +1784,7 @@ function written(device, bytes) {
1484
1784
  if (device === "full" && bytes.length > 0) throw new KernelError("ENOSPC");
1485
1785
  return bytes.length;
1486
1786
  }
1487
- function deviceFile(device, access) {
1787
+ function deviceFile(device, access2) {
1488
1788
  const read = async (max) => {
1489
1789
  if (device === "null") return new Uint8Array(0);
1490
1790
  const out = new Uint8Array(max);
@@ -1496,10 +1796,10 @@ function deviceFile(device, access) {
1496
1796
  return out;
1497
1797
  };
1498
1798
  return new OpenFile({
1499
- ...access !== "write" ? { read } : {},
1500
- ...access !== "read" ? { write: async (bytes) => written(device, bytes) } : {},
1799
+ ...access2 !== "write" ? { read } : {},
1800
+ ...access2 !== "read" ? { write: async (bytes) => written(device, bytes) } : {},
1501
1801
  seek: async () => 0,
1502
- heldMeta: { device, ...access ? { access } : {} },
1802
+ heldMeta: { device, ...access2 ? { access: access2 } : {} },
1503
1803
  close: () => {
1504
1804
  }
1505
1805
  });
@@ -2751,9 +3051,9 @@ var UserDb = class {
2751
3051
  if (await this.realDir(pnpm)) await this.fs.chmod(pnpm, scoop ? 488 : 448);
2752
3052
  }
2753
3053
  async realDir(path) {
2754
- const parts = path.split("/").filter(Boolean);
2755
- for (let n = 1; n <= parts.length; n++) {
2756
- const at = `/${parts.slice(0, n).join("/")}`;
3054
+ const parts2 = path.split("/").filter(Boolean);
3055
+ for (let n = 1; n <= parts2.length; n++) {
3056
+ const at = `/${parts2.slice(0, n).join("/")}`;
2757
3057
  const st = await this.fs.lstat(at).catch(() => void 0);
2758
3058
  if (!st?.isDirectory) return false;
2759
3059
  }
@@ -2954,7 +3254,7 @@ var UserDb = class {
2954
3254
  };
2955
3255
 
2956
3256
  // src/client/serve-client.ts
2957
- var MAX_LINKS2 = 40;
3257
+ var MAX_LINKS4 = 40;
2958
3258
  function act(session, req, signal) {
2959
3259
  if (req.action === "write") session.write(req.bytes);
2960
3260
  else if (req.action === "resize") session.resize(req.cols, req.rows);
@@ -2962,26 +3262,50 @@ function act(session, req, signal) {
2962
3262
  else session.close();
2963
3263
  }
2964
3264
  async function realpath(fs, path) {
2965
- let parts = fs.resolvePath("/", path).split("/").filter(Boolean);
3265
+ let parts2 = fs.resolvePath("/", path).split("/").filter(Boolean);
2966
3266
  let resolved = "";
2967
- for (let hops = 0; parts.length > 0; ) {
2968
- const [name2, ...rest2] = parts;
3267
+ for (let hops = 0; parts2.length > 0; ) {
3268
+ const [name2, ...rest2] = parts2;
2969
3269
  const next2 = `${resolved}/${name2}`;
2970
3270
  if (!(await fs.lstat(next2)).isSymbolicLink) {
2971
3271
  resolved = next2;
2972
- parts = rest2;
3272
+ parts2 = rest2;
2973
3273
  continue;
2974
3274
  }
2975
- if (++hops > MAX_LINKS2) throw fsError("ELOOP", path);
3275
+ if (++hops > MAX_LINKS4) throw fsError("ELOOP", path);
2976
3276
  const target = await fs.readlink(next2);
2977
3277
  const base = target.startsWith("/") ? "/" : resolved || "/";
2978
- parts = [...fs.resolvePath(base, target).split("/").filter(Boolean), ...rest2];
3278
+ parts2 = [...fs.resolvePath(base, target).split("/").filter(Boolean), ...rest2];
2979
3279
  resolved = "";
2980
3280
  }
2981
3281
  return resolved || "/";
2982
3282
  }
2983
- async function fsCall(fs, method, args, owner) {
3283
+ var CLIENT_CHECKS = {
3284
+ readFile: (g, at) => readable(g, "read", at(0)),
3285
+ writeFile: (g, at) => writable(g, "write", at(0)),
3286
+ stat: (g, at) => searchable(g, "stat", at(0), true),
3287
+ lstat: (g, at) => searchable(g, "lstat", at(0), false),
3288
+ readdir: (g, at) => listable(g, "readdir", at(0)),
3289
+ mkdir: (g, at) => makeIn(g, "mkdir", at(0)),
3290
+ rm: (g, at) => removable(g, "rm", at(0)),
3291
+ rename: (g, at) => renamable(g, at(0), at(1)),
3292
+ realpath: (g, at) => searchable(g, "realpath", at(0), true),
3293
+ symlink: (g, at) => inParent(g, "symlink", at(1)),
3294
+ readlink: (g, at) => searchable(g, "readlink", at(0), false),
3295
+ exists: (g, at) => searchable(g, "exists", at(0), true)
3296
+ };
3297
+ async function fsCall(fs, method, args, owner, g) {
2984
3298
  const path = (i) => fs.resolvePath("/", String(args[i]));
3299
+ if (g) {
3300
+ const denied2 = await Promise.resolve(CLIENT_CHECKS[method](g, path)).then(
3301
+ () => false,
3302
+ (err) => {
3303
+ if (method === "exists") return true;
3304
+ throw err;
3305
+ }
3306
+ );
3307
+ if (denied2) return { result: false };
3308
+ }
2985
3309
  switch (method) {
2986
3310
  case "readFile": {
2987
3311
  const bytes = (await fs.readFileBuffer(path(0))).slice();
@@ -3044,6 +3368,10 @@ function ownerFor(l, self) {
3044
3368
  if (!l.owners || !self) return void 0;
3045
3369
  return { uid: self.uid, gid: self.gid, umask: self.umask ?? 18 };
3046
3370
  }
3371
+ function guardFor(l, self) {
3372
+ if (!l.dac || !self) return void 0;
3373
+ return { fs: l.fs, cred: credOf(self), pid: 0, dac: l.dac };
3374
+ }
3047
3375
  async function accountOf(l, user) {
3048
3376
  const found = await l.users.lookup(user);
3049
3377
  if (!found) throw fsError("ENOENT", `no user ${String(user)}`);
@@ -3112,17 +3440,28 @@ function cdpRegistry(send, gone) {
3112
3440
  }
3113
3441
  };
3114
3442
  }
3115
- function watchOp(l, req, watches, send) {
3443
+ function visibleTo(g, notify) {
3444
+ if (!g?.dac.enforcing) return notify;
3445
+ let queue = Promise.resolve();
3446
+ const seen = async (path) => (await access(g.fs, g.cred, parentOf3(path), R_OK | X_OK, { directory: true })).denied === void 0;
3447
+ return (change) => {
3448
+ queue = queue.then(async () => {
3449
+ if (!("paths" in change)) return notify(change);
3450
+ const paths = [];
3451
+ for (const path of change.paths) if (await seen(path)) paths.push(path);
3452
+ if (paths.length > 0) notify({ paths });
3453
+ });
3454
+ };
3455
+ }
3456
+ async function watchOp(l, req, watches, send, g) {
3116
3457
  if (req.op === "unwatch") {
3117
3458
  watches.get(req.watch)?.();
3118
3459
  watches.delete(req.watch);
3119
3460
  return { result: true };
3120
3461
  }
3121
- const unwatch = l.watchers.watch(
3122
- req.paths,
3123
- { recursive: req.recursive },
3124
- (change) => send({ watch: req.id, change })
3125
- );
3462
+ if (g) for (const path of req.paths) await readable(g, "watch", l.fs.resolvePath("/", path));
3463
+ const notify = visibleTo(g, (change) => send({ watch: req.id, change }));
3464
+ const unwatch = l.watchers.watch(req.paths, { recursive: req.recursive }, notify);
3126
3465
  watches.set(req.id, unwatch);
3127
3466
  return { result: req.id };
3128
3467
  }
@@ -3156,9 +3495,9 @@ function serveClient(port, host) {
3156
3495
  ended.resolve();
3157
3496
  }
3158
3497
  async function hello(message3) {
3159
- const refused = versionError(message3.protocol);
3160
- if (refused) {
3161
- send({ hello: { protocol: PROTOCOL, error: refused } });
3498
+ const refused2 = versionError(message3.protocol);
3499
+ if (refused2) {
3500
+ send({ hello: { protocol: PROTOCOL, error: refused2 } });
3162
3501
  detach();
3163
3502
  return;
3164
3503
  }
@@ -3238,10 +3577,10 @@ function serveClient(port, host) {
3238
3577
  if (!FS_METHODS.includes(req.method)) {
3239
3578
  throw new Error(`unknown file system call ${req.method}`);
3240
3579
  }
3241
- return fsCall(l.fs, req.method, req.args, ownerFor(l, self));
3580
+ return fsCall(l.fs, req.method, req.args, ownerFor(l, self), guardFor(l, self));
3242
3581
  case "watch":
3243
3582
  case "unwatch":
3244
- return watchOp(l, req, watches, send);
3583
+ return watchOp(l, req, watches, send, guardFor(l, self));
3245
3584
  case "mount":
3246
3585
  return { result: await l.mount(req.spec) };
3247
3586
  case "umount":
@@ -4184,8 +4523,8 @@ var RealmProxy = class {
4184
4523
  }
4185
4524
  async connect(conn, incoming, req, idle) {
4186
4525
  const target = tunnelTarget(req);
4187
- const refused = target.port === 443 ? await this.tunnelReady(target) : void 0;
4188
- if (refused) throw refused;
4526
+ const refused2 = target.port === 443 ? await this.tunnelReady(target) : void 0;
4527
+ if (refused2) throw refused2;
4189
4528
  await conn.write(latin1Bytes("HTTP/1.1 200 Connection Established\r\n\r\n"), this.stop.signal);
4190
4529
  try {
4191
4530
  const first = await incoming.peek(this.stop.signal);
@@ -4328,10 +4667,10 @@ var RealmProxy = class {
4328
4667
  };
4329
4668
 
4330
4669
  // src/kernel/net/x509.ts
4331
- function concat2(parts) {
4332
- const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
4670
+ function concat2(parts2) {
4671
+ const out = new Uint8Array(parts2.reduce((n, p) => n + p.length, 0));
4333
4672
  let at = 0;
4334
- for (const p of parts) {
4673
+ for (const p of parts2) {
4335
4674
  out.set(p, at);
4336
4675
  at += p.length;
4337
4676
  }
@@ -4347,8 +4686,8 @@ function tlv(tag, ...contents2) {
4347
4686
  const body = concat2(contents2);
4348
4687
  return concat2([Uint8Array.of(tag), length(body.length), body]);
4349
4688
  }
4350
- var seq = (...parts) => tlv(48, ...parts);
4351
- var set = (...parts) => tlv(49, ...parts);
4689
+ var seq = (...parts2) => tlv(48, ...parts2);
4690
+ var set = (...parts2) => tlv(49, ...parts2);
4352
4691
  var octets = (bytes) => tlv(4, bytes);
4353
4692
  var bits = (bytes, unused = 0) => tlv(3, Uint8Array.of(unused), bytes);
4354
4693
  var TRUE = tlv(1, Uint8Array.of(255));
@@ -4436,8 +4775,8 @@ function randomSerial() {
4436
4775
  function ipv4(host) {
4437
4776
  const m = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(host);
4438
4777
  if (!m) return void 0;
4439
- const parts = m.slice(1).map(Number);
4440
- return parts.every((p) => p <= 255) ? Uint8Array.from(parts) : void 0;
4778
+ const parts2 = m.slice(1).map(Number);
4779
+ return parts2.every((p) => p <= 255) ? Uint8Array.from(parts2) : void 0;
4441
4780
  }
4442
4781
  function ipv6(host) {
4443
4782
  if (!host.includes(":") || !/^[0-9A-Fa-f:.]+$/.test(host)) return void 0;
@@ -5211,9 +5550,9 @@ var RemoteTransport = class {
5211
5550
  // src/kernel/net/routes.ts
5212
5551
  var HOST_ADDRESS = HOST_LOOPBACK_ADDRESS.join(".");
5213
5552
  function ipv4Bytes(text) {
5214
- const parts = text.split(".");
5215
- if (parts.length !== 4 || !parts.every((p) => /^\d{1,3}$/.test(p))) return void 0;
5216
- const bytes = parts.map(Number);
5553
+ const parts2 = text.split(".");
5554
+ if (parts2.length !== 4 || !parts2.every((p) => /^\d{1,3}$/.test(p))) return void 0;
5555
+ const bytes = parts2.map(Number);
5217
5556
  return bytes.every((b) => b <= 255) ? bytes : void 0;
5218
5557
  }
5219
5558
  function ipv6Groups(text) {
@@ -5240,9 +5579,9 @@ function addressBits(host) {
5240
5579
  return v6 ? { v6: true, bits: toBits(v6, 16) } : void 0;
5241
5580
  }
5242
5581
  function parsePrefix(text) {
5243
- const parts = text.trim().split("/");
5244
- if (parts.length > 2) return void 0;
5245
- const [address = "", size] = parts;
5582
+ const parts2 = text.trim().split("/");
5583
+ if (parts2.length > 2) return void 0;
5584
+ const [address = "", size] = parts2;
5246
5585
  const parsed2 = addressBits(address);
5247
5586
  if (!parsed2) return void 0;
5248
5587
  const max = parsed2.v6 ? 128 : 32;
@@ -5903,7 +6242,7 @@ function binfmtOf(binfmts, path) {
5903
6242
  var S_IFDIR2 = 16384;
5904
6243
  var S_IFREG2 = 32768;
5905
6244
  var COMMAND = /^\/(?:usr\/)?bin\/([^/]+)$/;
5906
- var MAX_LINKS3 = 40;
6245
+ var MAX_LINKS5 = 40;
5907
6246
  function missing2(err) {
5908
6247
  return err?.code === "ENOENT";
5909
6248
  }
@@ -5923,7 +6262,7 @@ function synthetic(directory, path) {
5923
6262
  }
5924
6263
  async function followLinks(fs, path) {
5925
6264
  let current = path;
5926
- for (let hops = 0; hops < MAX_LINKS3; hops++) {
6265
+ for (let hops = 0; hops < MAX_LINKS5; hops++) {
5927
6266
  const st = await fs.lstat(current).catch(() => void 0);
5928
6267
  if (!st?.isSymbolicLink) return current;
5929
6268
  const parent = current.slice(0, current.lastIndexOf("/")) || "/";
@@ -6030,6 +6369,7 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
6030
6369
  readlink: (path) => fs.readlink(path),
6031
6370
  ...fs.realpath ? { realpath: fs.realpath.bind(fs) } : {},
6032
6371
  ...fs.chown ? { chown: fs.chown.bind(fs) } : {},
6372
+ ...fs.ancestry ? { ancestry: fs.ancestry.bind(fs) } : {},
6033
6373
  chmod: (path, mode) => fs.chmod(path, mode),
6034
6374
  utimes: (path, atime, mtime) => fs.utimes(path, atime, mtime),
6035
6375
  lutimes: (path, atime, mtime) => lutimesOf(fs, path, atime, mtime),
@@ -6042,12 +6382,12 @@ var MAX_HOPS = 40;
6042
6382
  function start(path) {
6043
6383
  return { done: [], todo: path.split("/").filter((p) => p !== ""), hops: 0 };
6044
6384
  }
6045
- function next(walk) {
6046
- while (walk.todo.length > 0) {
6047
- const part = walk.todo.shift();
6385
+ function next(walk2) {
6386
+ while (walk2.todo.length > 0) {
6387
+ const part = walk2.todo.shift();
6048
6388
  if (part === ".") continue;
6049
6389
  if (part === "..") {
6050
- walk.done.pop();
6390
+ walk2.done.pop();
6051
6391
  continue;
6052
6392
  }
6053
6393
  return part;
@@ -6055,33 +6395,33 @@ function next(walk) {
6055
6395
  return void 0;
6056
6396
  }
6057
6397
  function lexical(path) {
6058
- const walk = start(path);
6059
- for (let part = next(walk); part !== void 0; part = next(walk)) walk.done.push(part);
6060
- return `/${walk.done.join("/")}`;
6398
+ const walk2 = start(path);
6399
+ for (let part = next(walk2); part !== void 0; part = next(walk2)) walk2.done.push(part);
6400
+ return `/${walk2.done.join("/")}`;
6061
6401
  }
6062
- function rest(walk, part) {
6063
- return lexical(`/${[...walk.done, part, ...walk.todo].join("/")}`);
6402
+ function rest(walk2, part) {
6403
+ return lexical(`/${[...walk2.done, part, ...walk2.todo].join("/")}`);
6064
6404
  }
6065
- function follow(walk, target) {
6066
- if (++walk.hops > MAX_HOPS) return false;
6067
- if (target.startsWith("/")) walk.done.length = 0;
6068
- walk.todo = [...target.split("/").filter((p) => p !== ""), ...walk.todo];
6405
+ function follow(walk2, target) {
6406
+ if (++walk2.hops > MAX_HOPS) return false;
6407
+ if (target.startsWith("/")) walk2.done.length = 0;
6408
+ walk2.todo = [...target.split("/").filter((p) => p !== ""), ...walk2.todo];
6069
6409
  return true;
6070
6410
  }
6071
6411
  async function physicalPathAsync(fs, path) {
6072
- const walk = start(path);
6073
- for (let part = next(walk); part !== void 0; part = next(walk)) {
6074
- const at = `/${[...walk.done, part].join("/")}`;
6412
+ const walk2 = start(path);
6413
+ for (let part = next(walk2); part !== void 0; part = next(walk2)) {
6414
+ const at = `/${[...walk2.done, part].join("/")}`;
6075
6415
  let target;
6076
6416
  try {
6077
6417
  target = (await fs.lstat(at)).isSymbolicLink ? await fs.readlink(at) : void 0;
6078
6418
  } catch {
6079
- return rest(walk, part);
6419
+ return rest(walk2, part);
6080
6420
  }
6081
- if (target === void 0) walk.done.push(part);
6082
- else if (!follow(walk, target)) return lexical(path);
6421
+ if (target === void 0) walk2.done.push(part);
6422
+ else if (!follow(walk2, target)) return lexical(path);
6083
6423
  }
6084
- return `/${walk.done.join("/")}`;
6424
+ return `/${walk2.done.join("/")}`;
6085
6425
  }
6086
6426
 
6087
6427
  // src/fs/unlinked.ts
@@ -6090,7 +6430,7 @@ function webLocks() {
6090
6430
  return globalThis.navigator?.locks;
6091
6431
  }
6092
6432
  var within3 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
6093
- var parentOf3 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
6433
+ var parentOf4 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
6094
6434
  var lockName = (instance) => `slicc-unlinked:${instance}`;
6095
6435
  var dirOf = (root) => root === "/" ? `/${UNLINKED_DIR}` : `${root}/${UNLINKED_DIR}`;
6096
6436
  var PathGate = class {
@@ -6221,7 +6561,7 @@ var UnlinkedKeeper = class {
6221
6561
  await this.serial(async () => {
6222
6562
  await Promise.allSettled([this.fs.rename(file.hidden, path)]);
6223
6563
  this.unlinkedName(file.hidden);
6224
- await this.tidy(parentOf3(file.hidden));
6564
+ await this.tidy(parentOf4(file.hidden));
6225
6565
  });
6226
6566
  }
6227
6567
  done4();
@@ -6261,7 +6601,7 @@ var UnlinkedKeeper = class {
6261
6601
  }
6262
6602
  await this.fs.rm(hidden).catch(() => void 0);
6263
6603
  this.unlinkedName(hidden);
6264
- await this.tidy(parentOf3(hidden));
6604
+ await this.tidy(parentOf4(hidden));
6265
6605
  }
6266
6606
  async linkedName(hidden) {
6267
6607
  if (!mayLink(this.fs, hidden)) return;
@@ -6306,10 +6646,10 @@ var UnlinkedKeeper = class {
6306
6646
  // src/fs/watch.ts
6307
6647
  var WATCH_BATCH_LIMIT = 1e3;
6308
6648
  var under2 = (path, dir) => dir === "/" || path.startsWith(`${dir}/`);
6309
- var parentOf4 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
6649
+ var parentOf5 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
6310
6650
  function touches(watcher, changed) {
6311
6651
  return watcher.paths.some(
6312
- (watched) => changed === watched || under2(watched, changed) || (watcher.recursive ? under2(changed, watched) : parentOf4(changed) === watched)
6652
+ (watched) => changed === watched || under2(watched, changed) || (watcher.recursive ? under2(changed, watched) : parentOf5(changed) === watched)
6313
6653
  );
6314
6654
  }
6315
6655
  var FsWatchers = class {
@@ -6358,9 +6698,9 @@ var FsWatchers = class {
6358
6698
  mkdir: async (path, options) => {
6359
6699
  const created3 = [];
6360
6700
  if (options?.recursive) {
6361
- const parts = normalizePath(path).split("/").filter(Boolean);
6362
- for (let at = 1; at < parts.length; at++) {
6363
- const ancestor = `/${parts.slice(0, at).join("/")}`;
6701
+ const parts2 = normalizePath(path).split("/").filter(Boolean);
6702
+ for (let at = 1; at < parts2.length; at++) {
6703
+ const ancestor = `/${parts2.slice(0, at).join("/")}`;
6364
6704
  if (created3.length > 0 || !await fs.exists(ancestor)) created3.push(ancestor);
6365
6705
  }
6366
6706
  }
@@ -6589,62 +6929,6 @@ var ChildTable = class {
6589
6929
  }
6590
6930
  };
6591
6931
 
6592
- // src/fs/walk.ts
6593
- var MAX_LINKS4 = 40;
6594
- function parentOf5(path) {
6595
- return path.slice(0, path.lastIndexOf("/")) || "/";
6596
- }
6597
- async function mustBeDir(fs, path) {
6598
- if (!fs.lstat) return;
6599
- const st = await fs.lstat(path);
6600
- if (st.isDirectory !== true) throw fsError("ENOTDIR", path);
6601
- }
6602
- async function linkOf(fs, path) {
6603
- return fs.readlink?.(path).catch(() => void 0);
6604
- }
6605
- async function realDir(fs, dir, budget = { hops: 0 }) {
6606
- const queue = dir.split("/").filter(Boolean);
6607
- let at = "/";
6608
- while (queue.length > 0) {
6609
- const part = queue.shift();
6610
- if (part === ".") continue;
6611
- if (part === "..") {
6612
- at = parentOf5(at);
6613
- continue;
6614
- }
6615
- const next2 = at === "/" ? `/${part}` : `${at}/${part}`;
6616
- const link2 = await linkOf(fs, next2);
6617
- if (link2 === void 0) {
6618
- if (queue.find((p) => p !== ".") === "..") await mustBeDir(fs, next2);
6619
- at = next2;
6620
- continue;
6621
- }
6622
- if (budget.hops >= MAX_LINKS4) throw fsError("ELOOP", dir);
6623
- budget.hops++;
6624
- queue.unshift(...link2.split("/").filter(Boolean));
6625
- if (link2.startsWith("/")) at = "/";
6626
- }
6627
- return at;
6628
- }
6629
- function joinName(dir, name2) {
6630
- return dir === "/" ? `/${name2}` : `${dir}/${name2}`;
6631
- }
6632
- async function realTarget(fs, path) {
6633
- const budget = { hops: 0 };
6634
- let dir = await realDir(fs, parentOf5(path), budget);
6635
- let name2 = path.slice(path.lastIndexOf("/") + 1);
6636
- for (; ; ) {
6637
- const link2 = await linkOf(fs, joinName(dir, name2));
6638
- if (link2 === void 0) break;
6639
- if (budget.hops >= MAX_LINKS4) throw fsError("ELOOP", path);
6640
- budget.hops++;
6641
- const target = link2.startsWith("/") ? link2 : joinName(dir, link2);
6642
- dir = await realDir(fs, parentOf5(target), budget);
6643
- name2 = target.slice(target.lastIndexOf("/") + 1);
6644
- }
6645
- return joinName(dir, name2);
6646
- }
6647
-
6648
6932
  // src/mount/protocol.ts
6649
6933
  var DRIVER_PROTOCOL = [1, 2];
6650
6934
  function driverVersionError(theirs) {
@@ -6707,8 +6991,8 @@ var DriverConnection = class _DriverConnection {
6707
6991
  } finally {
6708
6992
  clearTimeout(timer);
6709
6993
  }
6710
- const refused = driverVersionError(hello.protocol);
6711
- if (refused) throw errnoError("EPROTO", refused);
6994
+ const refused2 = driverVersionError(hello.protocol);
6995
+ if (refused2) throw errnoError("EPROTO", refused2);
6712
6996
  if (hello.error) throw errnoError(hello.errno ?? "EIO", hello.error);
6713
6997
  return new _DriverConnection(port, hello.capabilities ?? {}, waiting, timeoutMs, options.onFail);
6714
6998
  }
@@ -6748,7 +7032,7 @@ var DriverConnection = class _DriverConnection {
6748
7032
  };
6749
7033
 
6750
7034
  // src/mount/mount-fs.ts
6751
- var S_ISGID3 = 1024;
7035
+ var S_ISGID4 = 1024;
6752
7036
  var ID = /^\d{1,10}$/;
6753
7037
  var MAX_ID2 = 4294967294;
6754
7038
  function idOption(value) {
@@ -6761,7 +7045,7 @@ function checkIds(options) {
6761
7045
  if (!ID.test(value) || Number(value) > MAX_ID2) throw errnoError("EINVAL", `${key}=${value}`);
6762
7046
  }
6763
7047
  }
6764
- var MAX_LINKS5 = 40;
7048
+ var MAX_LINKS6 = 40;
6765
7049
  var STALE_RETRIES = 3;
6766
7050
  var SETTLE_ROUNDS = 4;
6767
7051
  var DEFAULT_IO = 8 * 1024 * 1024;
@@ -6999,12 +7283,12 @@ var MountTable = class {
6999
7283
  }
7000
7284
  list() {
7001
7285
  const mounted = [...this.mounts.values()].map(
7002
- ({ type, source, target, options, state, error, present: present2 }) => ({
7286
+ ({ type, source, target, options, state, error, present: present3 }) => ({
7003
7287
  type,
7004
7288
  source,
7005
7289
  target,
7006
7290
  options: { ...options },
7007
- state: state === "ok" && present2?.() === false ? "nomedium" : state,
7291
+ state: state === "ok" && present3?.() === false ? "nomedium" : state,
7008
7292
  ...error ? { error } : {}
7009
7293
  })
7010
7294
  );
@@ -7279,7 +7563,7 @@ var MountFs = class {
7279
7563
  throw err;
7280
7564
  }
7281
7565
  if (attr2.kind !== "symlink") return real;
7282
- if (hops >= MAX_LINKS5) throw errnoError("ELOOP", path);
7566
+ if (hops >= MAX_LINKS6) throw errnoError("ELOOP", path);
7283
7567
  const target = await found.mount.conn.call({ op: "readlink", path: found.rel });
7284
7568
  return this.follow(this.base.resolvePath(parentOf6(real), target), hops + 1, create);
7285
7569
  }
@@ -7299,9 +7583,9 @@ var MountFs = class {
7299
7583
  if (!owner || !mount.owners || !mount.caps.owners) return void 0;
7300
7584
  const dir = parentOf6(real);
7301
7585
  const above = statOf(await this.table.getattr(mount, dir, parentOf6(rel2)), dir, mount);
7302
- const setgid = (above.mode & S_ISGID3) !== 0;
7586
+ const setgid = (above.mode & S_ISGID4) !== 0;
7303
7587
  let mode = owner.mode;
7304
- if (directory && setgid) mode = (mode ?? 493) | S_ISGID3;
7588
+ if (directory && setgid) mode = (mode ?? 493) | S_ISGID4;
7305
7589
  return {
7306
7590
  uid: owner.uid,
7307
7591
  gid: setgid ? above.gid : owner.gid,
@@ -7576,14 +7860,14 @@ var MountFs = class {
7576
7860
  await mount.conn.call({ op: "mkdir", path: rel2, ...made2 });
7577
7861
  return;
7578
7862
  }
7579
- const parts = rel2.split("/").filter(Boolean);
7580
- for (let n = 1; n <= parts.length; n++) {
7581
- const sub = `/${parts.slice(0, n).join("/")}`;
7863
+ const parts2 = rel2.split("/").filter(Boolean);
7864
+ for (let n = 1; n <= parts2.length; n++) {
7865
+ const sub = `/${parts2.slice(0, n).join("/")}`;
7582
7866
  const full = normalizePath(`${mount.target}${sub}`);
7583
7867
  const exists = await this.table.getattr(mount, full, sub).catch(() => void 0);
7584
7868
  if (exists?.kind === "directory") continue;
7585
7869
  if (exists) throw errnoError("ENOTDIR", full);
7586
- const own = n === parts.length ? owner : owner && { ...owner, mode: owner.parents };
7870
+ const own = n === parts2.length ? owner : owner && { ...owner, mode: owner.parents };
7587
7871
  const made2 = await this.driverOwner({ mount, rel: sub }, full, own, true);
7588
7872
  this.table.forget(mount, full);
7589
7873
  await mount.conn.call({ op: "mkdir", path: sub, ...made2 });
@@ -7676,6 +7960,20 @@ var MountFs = class {
7676
7960
  if (this.at(real)) throw errnoError("ENOSYS", path);
7677
7961
  return real;
7678
7962
  }
7963
+ async ancestry(path) {
7964
+ const names = chain(normalizePath(path));
7965
+ const first = names.findIndex((name2) => this.at(name2));
7966
+ if (first < 0) return ancestryOf(this.base, path);
7967
+ const out = first === 0 ? [] : await ancestryOf(this.base, names[first - 1]);
7968
+ if (out.length < first || out.some((perm) => perm?.link !== void 0)) return out;
7969
+ for (const name2 of names.slice(first)) {
7970
+ const st = await this.lstat(name2).catch(() => void 0);
7971
+ if (!st) break;
7972
+ out.push(permOf(st, st.isSymbolicLink ? await this.readlink(name2) : void 0));
7973
+ if (st.isSymbolicLink) break;
7974
+ }
7975
+ return out;
7976
+ }
7679
7977
  async readlink(path) {
7680
7978
  const found = this.at(path);
7681
7979
  if (!found) return this.base.readlink(path);
@@ -7886,9 +8184,11 @@ async function run(entry, path, req) {
7886
8184
  return done;
7887
8185
  case "readlink":
7888
8186
  return json2(await fs.readlink(path));
7889
- case "chmod":
7890
- await fs.chmod(path, req.mode ?? 0);
8187
+ case "chmod": {
8188
+ const g = guardOf(entry);
8189
+ await fs.chmod(path, g ? await chmodMode(g, path, req.mode ?? 0) : req.mode ?? 0);
7891
8190
  return done;
8191
+ }
7892
8192
  case "utimes":
7893
8193
  await fs.utimes(path, new Date(req.atimeMs ?? 0), new Date(req.mtimeMs ?? 0));
7894
8194
  return done;
@@ -7906,6 +8206,53 @@ async function run(entry, path, req) {
7906
8206
  return { ok: false, errno: "EINVAL", message: `sync-fs: unknown op '${req.op}'` };
7907
8207
  }
7908
8208
  }
8209
+ function guardOf(entry) {
8210
+ const cred = entry.dac && entry.cred?.();
8211
+ if (!cred || !entry.dac) return void 0;
8212
+ return { fs: entry.fs, cred, pid: entry.pid ?? 0, dac: entry.dac };
8213
+ }
8214
+ var FOLLOWING = /* @__PURE__ */ new Set(["exists", "stat", "chown", "statfs"]);
8215
+ var NOT_FOLLOWING = /* @__PURE__ */ new Set(["lstat", "readlink", "lchown"]);
8216
+ async function guard(g, path, req, cwd) {
8217
+ const { op } = req;
8218
+ if (FOLLOWING.has(op) || NOT_FOLLOWING.has(op)) return searchable(g, op, path, FOLLOWING.has(op));
8219
+ const other = () => g.fs.resolvePath(cwd, req.arg2 ?? "");
8220
+ switch (op) {
8221
+ case "readdir":
8222
+ case "readdir-stat":
8223
+ return listable(g, op, path);
8224
+ case "mkdir":
8225
+ return makeIn(g, op, path);
8226
+ case "create":
8227
+ case "symlink":
8228
+ return inParent(g, op, path);
8229
+ case "unlink":
8230
+ case "rmdir":
8231
+ return inParent(g, op, path, true);
8232
+ case "rm":
8233
+ return removable(g, op, path);
8234
+ case "rename":
8235
+ return renamable(g, path, other());
8236
+ case "link":
8237
+ return linkable(g, path, other(), req.mode === 1);
8238
+ case "utimes":
8239
+ case "lutimes":
8240
+ return retimable(g, op, path);
8241
+ default:
8242
+ return void 0;
8243
+ }
8244
+ }
8245
+ async function guarded(entry, path, req) {
8246
+ const g = guardOf(entry);
8247
+ if (!g) return void 0;
8248
+ try {
8249
+ await guard(g, path, req, entry.cwd);
8250
+ return void 0;
8251
+ } catch (err) {
8252
+ const refusal = err.code === "EACCES";
8253
+ return req.op === "exists" && refusal ? json2(false) : toErrno(err);
8254
+ }
8255
+ }
7909
8256
  async function createExclusive(fs, path, owner) {
7910
8257
  if (fs.createExclusive) return fs.createExclusive(path, owner);
7911
8258
  if (await fs.lstat(path).then(
@@ -7958,24 +8305,56 @@ async function owned(entry, own, req, path, target) {
7958
8305
  }
7959
8306
  return own(target, () => run(entry, path, req));
7960
8307
  }
8308
+ async function holdOp(entry, path, req) {
8309
+ const mode = req.mode ?? 0;
8310
+ const [on, open] = [(mode & 1) === 1, (mode & 2) === 2];
8311
+ if (on && open) {
8312
+ const real = await entry.fs.realpath?.(path, true).catch(() => path);
8313
+ return json2(entry.hold?.(path, on, open, real) ?? null);
8314
+ }
8315
+ entry.hold?.(path, on, open, void 0, req.handle);
8316
+ return done;
8317
+ }
8318
+ var IDENTITY_OPS = /* @__PURE__ */ new Set([
8319
+ "write",
8320
+ "pwrite",
8321
+ "truncate",
8322
+ "chmod",
8323
+ "utimes",
8324
+ "chown",
8325
+ "lchown"
8326
+ ]);
7961
8327
  async function dispatchSyncFs(req) {
7962
8328
  const entry = resolveSyncFsToken(req.token);
7963
8329
  if (!entry) return { ok: false, errno: "EACCES", message: "sync-fs: unknown or revoked token" };
8330
+ if (!req.identity || !IDENTITY_OPS.has(req.op)) return dispatchOne(entry, req);
8331
+ const { fs, cwd } = entry;
8332
+ const path = fs.resolvePath(cwd, req.path);
8333
+ const identified = async (held) => {
8334
+ const own = held ? { ...entry, fs: { ...fs, writeFile: held.writeFile } } : entry;
8335
+ const result = await dispatchOne(own, req);
8336
+ if (!result.ok) return result;
8337
+ if (held && !held.direct) return json2(null);
8338
+ const look = req.op === "lchown" ? fs.lstat : fs.stat;
8339
+ const st = await look.call(fs, path).catch(() => void 0);
8340
+ return json2(st ? statJson(st) : null);
8341
+ };
8342
+ try {
8343
+ return await (fs.exclusive ? fs.exclusive(path, identified, req.op !== "lchown") : identified());
8344
+ } catch (err) {
8345
+ return toErrno(err);
8346
+ }
8347
+ }
8348
+ async function dispatchOne(entry, req) {
7964
8349
  const { fs, cwd } = entry;
7965
8350
  try {
7966
8351
  const path = fs.resolvePath(cwd, req.path);
7967
8352
  if (req.op === "kept") return json2(entry.keptFor?.(req.handle ?? -1) !== void 0);
7968
8353
  if (req.op === "stat" && req.handle !== void 0) return await keptStat(entry, req);
8354
+ if (req.op === "hold") return holdOp(entry, path, req);
8355
+ const refused2 = await guarded(entry, path, req);
8356
+ if (refused2) return refused2;
7969
8357
  if (req.op === "statfs") return json2(await entry.statfs?.(path) ?? await storageUsage());
7970
- if (req.op === "hold") {
7971
- const [on, open] = [((req.mode ?? 0) & 1) === 1, ((req.mode ?? 0) & 2) === 2];
7972
- if (on && open) {
7973
- const real = await fs.realpath?.(path, true).catch(() => path);
7974
- return json2(entry.hold?.(path, on, open, real) ?? null);
7975
- }
7976
- entry.hold?.(path, on, open, void 0, req.handle);
7977
- return done;
7978
- }
7979
8358
  if (entry.revoked?.(path)) return { ok: false, errno: "EIO", message: `${path} was unmounted` };
7980
8359
  if (req.op === "write" && req.mode === 1 || UNLINKED_OPS.has(req.op)) {
7981
8360
  const serve = async () => run(entry, await keptOr(entry, path, req.handle), req);
@@ -8149,9 +8528,9 @@ function mountTable(mounts = []) {
8149
8528
  for (const m of mounts) {
8150
8529
  const options = Object.entries(m.options).map(([k, v]) => v === "" ? k : `${k}=${v}`);
8151
8530
  if (m.state !== "ok") options.push(m.state);
8152
- const access = options.includes("ro") ? [] : ["rw"];
8531
+ const access2 = options.includes("ro") ? [] : ["rw"];
8153
8532
  lines2.push(
8154
- `${field2(m.source)} ${field2(m.target)} ${m.type} ${[...access, ...options].join(",")} 0 0`
8533
+ `${field2(m.source)} ${field2(m.target)} ${m.type} ${[...access2, ...options].join(",")} 0 0`
8155
8534
  );
8156
8535
  }
8157
8536
  return `${lines2.join("\n")}
@@ -8587,8 +8966,8 @@ var ProcTree = class {
8587
8966
  alive(pid) {
8588
8967
  return this.listing().processes.some((p) => p.pid === pid);
8589
8968
  }
8590
- node(parts) {
8591
- const [top, leaf, ...rest2] = parts;
8969
+ node(parts2) {
8970
+ const [top, leaf, ...rest2] = parts2;
8592
8971
  if (top === void 0) {
8593
8972
  return {
8594
8973
  kind: "dir",
@@ -8626,9 +9005,9 @@ var ProcTree = class {
8626
9005
  };
8627
9006
  }
8628
9007
  parts(path, follow2) {
8629
- const parts = path.split("/").filter((p) => p !== "").slice(1);
8630
- if (parts[0] === "self" && (follow2 || parts.length > 1)) parts[0] = String(this.pid);
8631
- return parts;
9008
+ const parts2 = path.split("/").filter((p) => p !== "").slice(1);
9009
+ if (parts2[0] === "self" && (follow2 || parts2.length > 1)) parts2[0] = String(this.pid);
9010
+ return parts2;
8632
9011
  }
8633
9012
  find(path, follow2) {
8634
9013
  const node = this.node(this.parts(path, follow2));
@@ -8714,8 +9093,8 @@ function refusalOf(found) {
8714
9093
  return found === false ? { refused: "ENOTDIR" } : found;
8715
9094
  }
8716
9095
  function throughLink(target, done4, todo) {
8717
- const parts = target.split("/").filter((part) => part !== "");
8718
- return [target.startsWith("/") ? [] : done4, [...parts, ...todo]];
9096
+ const parts2 = target.split("/").filter((part) => part !== "");
9097
+ return [target.startsWith("/") ? [] : done4, [...parts2, ...todo]];
8719
9098
  }
8720
9099
  async function procAlias(fs, path, follow2) {
8721
9100
  const { readlink, lstat } = fs;
@@ -8725,14 +9104,14 @@ async function procAlias(fs, path, follow2) {
8725
9104
  () => false
8726
9105
  );
8727
9106
  if (!physical) return void 0;
8728
- const walk = procWalk(path, follow2);
8729
- let step = walk.next();
9107
+ const walk2 = procWalk(path, follow2);
9108
+ let step = walk2.next();
8730
9109
  while (!step.done) {
8731
9110
  const { at, check } = step.value;
8732
9111
  const link2 = await readlink.call(fs, at).catch(() => void 0);
8733
9112
  const isDir = (st) => st.isDirectory === true;
8734
9113
  const target = link2 ?? (check ? await lstat.call(fs, at).then(isDir, failed) : true);
8735
- step = walk.next(target);
9114
+ step = walk2.next(target);
8736
9115
  }
8737
9116
  return step.value;
8738
9117
  }
@@ -9966,7 +10345,9 @@ var VfsNode = class {
9966
10345
  this.ranged = new AsyncRangedFile(io, this.path, this.pin.size, this.pin.version);
9967
10346
  return this.ranged;
9968
10347
  }
9969
- const st = io && !this.orphaned ? await this.fs.stat?.(this.path).catch(() => void 0) : void 0;
10348
+ const at = this.path;
10349
+ const st = io && !this.orphaned ? await this.fs.stat?.(at).catch(() => void 0) : void 0;
10350
+ this.note(at, st);
9970
10351
  if (io && st?.ranged) {
9971
10352
  this.ranged = new AsyncRangedFile(io, this.path, st.size ?? 0, st.version);
9972
10353
  return this.ranged;
@@ -10026,12 +10407,23 @@ var VfsNode = class {
10026
10407
  const stat = this.fs.stat?.bind(this.fs);
10027
10408
  if (!stat || this.orphaned) return Promise.resolve(void 0);
10028
10409
  const parent = this.path.slice(0, this.path.lastIndexOf("/")) || "/";
10029
- this.cap ??= stat(this.path).catch(() => stat(parent)).then(
10410
+ const at = this.path;
10411
+ this.cap ??= stat(at).then((st) => {
10412
+ this.note(at, st);
10413
+ return st;
10414
+ }).catch(() => stat(parent)).then(
10030
10415
  (st) => st.maxFile,
10031
10416
  () => void 0
10032
10417
  );
10033
10418
  return this.cap;
10034
10419
  }
10420
+ seen;
10421
+ note(path, st) {
10422
+ if (st?.ino !== void 0) this.seen = { path, ino: st.ino };
10423
+ }
10424
+ known() {
10425
+ return this.seen?.path === this.path && !this.orphaned ? this.seen.ino : void 0;
10426
+ }
10035
10427
  async fits(end) {
10036
10428
  const max = await this.limit();
10037
10429
  if (max !== void 0 && end > max) throw new KernelError("EFBIG");
@@ -10079,10 +10471,10 @@ var VfsNode = class {
10079
10471
  this.serial(() => this.flush()).catch(() => void 0);
10080
10472
  }, delay);
10081
10473
  }
10082
- ensure(need) {
10474
+ ensure(need2) {
10083
10475
  const cur = this.data ?? new Uint8Array(0);
10084
- if (cur.length >= need) return cur;
10085
- const grown = new Uint8Array(Math.max(need, cur.length * 2, 256));
10476
+ if (cur.length >= need2) return cur;
10477
+ const grown = new Uint8Array(Math.max(need2, cur.length * 2, 256));
10086
10478
  grown.set(cur.subarray(0, this.length));
10087
10479
  this.data = grown;
10088
10480
  return grown;
@@ -10125,14 +10517,14 @@ var VfsNodes = class {
10125
10517
  return run2;
10126
10518
  }
10127
10519
  async entryKey(path) {
10128
- const parent = parentOf5(path);
10520
+ const parent = parentOf3(path);
10129
10521
  const dir = await this.real(parent, true, () => realDir(this.fs, parent));
10130
10522
  return joinName(dir, path.slice(path.lastIndexOf("/") + 1));
10131
10523
  }
10132
10524
  targetKey(path) {
10133
10525
  return this.real(path, true, () => realTarget(this.fs, path));
10134
10526
  }
10135
- async real(path, follow2, walk) {
10527
+ async real(path, follow2, walk2) {
10136
10528
  if (this.fs.realpath) {
10137
10529
  try {
10138
10530
  return await this.fs.realpath(path, follow2);
@@ -10140,13 +10532,13 @@ var VfsNodes = class {
10140
10532
  if (errCode(err) === "ELOOP") throw err;
10141
10533
  }
10142
10534
  }
10143
- return walk();
10535
+ return walk2();
10144
10536
  }
10145
10537
  createExclusive(path, owner) {
10146
10538
  return this.onEntry(path, async () => {
10147
10539
  const node = this.byPath.get(path);
10148
10540
  const held = node ? await node.serial(async () => node.holds()) : false;
10149
- if (held || await present(this.fs, path)) throw fsError("EEXIST", path);
10541
+ if (held || await present2(this.fs, path)) throw fsError("EEXIST", path);
10150
10542
  await this.fs.writeFile(path, new Uint8Array(0), owner);
10151
10543
  });
10152
10544
  }
@@ -10206,13 +10598,13 @@ var VfsNodes = class {
10206
10598
  if (id2 !== void 0) this.know(node, id2);
10207
10599
  return node;
10208
10600
  }
10209
- open(path, pin, writable = false, id2) {
10601
+ open(path, pin, writable2 = false, id2) {
10210
10602
  const node = this.named(path, id2) ?? this.create(path, id2, new VfsNode(this.fs, path, void 0, false, false, pin));
10211
10603
  node.opens++;
10212
- if (writable) node.writers++;
10604
+ if (writable2) node.writers++;
10213
10605
  return node;
10214
10606
  }
10215
- adopt(path, contents2, dirty, writable = false, id2) {
10607
+ adopt(path, contents2, dirty, writable2 = false, id2) {
10216
10608
  let node = this.named(path, id2);
10217
10609
  if (!node) {
10218
10610
  node = this.create(path, id2, new VfsNode(this.fs, path, contents2, false, dirty));
@@ -10220,7 +10612,7 @@ var VfsNodes = class {
10220
10612
  node.replace(contents2);
10221
10613
  }
10222
10614
  node.opens++;
10223
- if (writable) node.writers++;
10615
+ if (writable2) node.writers++;
10224
10616
  return node;
10225
10617
  }
10226
10618
  holds(prefix) {
@@ -10232,11 +10624,11 @@ var VfsNodes = class {
10232
10624
  if (node.writers > 0 && within5(path, prefix)) return true;
10233
10625
  return false;
10234
10626
  }
10235
- closed(node, writable = false) {
10627
+ closed(node, writable2 = false) {
10236
10628
  node.opens--;
10237
- if (writable) node.writers--;
10629
+ if (writable2) node.writers--;
10238
10630
  if (node.opens > 0) {
10239
- if (writable && node.writers === 0) this.onClosed?.(node.path);
10631
+ if (writable2 && node.writers === 0) this.onClosed?.(node.path);
10240
10632
  return;
10241
10633
  }
10242
10634
  this.forget(node);
@@ -10313,7 +10705,7 @@ var VfsNodes = class {
10313
10705
  }
10314
10706
  }
10315
10707
  };
10316
- async function present(fs, path, follow2 = false) {
10708
+ async function present2(fs, path, follow2 = false) {
10317
10709
  const look = (follow2 ? void 0 : fs.lstat) ?? fs.stat;
10318
10710
  try {
10319
10711
  await (look ? look.call(fs, path) : fs.readFileBuffer(path));
@@ -10327,7 +10719,7 @@ function errCode(err) {
10327
10719
  return err?.code;
10328
10720
  }
10329
10721
  async function targetDir(fs, path) {
10330
- return parentOf5(await realTarget(fs, path).catch(() => path));
10722
+ return parentOf3(await realTarget(fs, path).catch(() => path));
10331
10723
  }
10332
10724
  async function refuseReadonly(fs, path, flags, opts = {}) {
10333
10725
  const writes = (flags & O_ACCMODE) !== 0 || opts.truncate === true;
@@ -10345,6 +10737,18 @@ async function refuseReadonly(fs, path, flags, opts = {}) {
10345
10737
  function keepingOpen(fs, nodes) {
10346
10738
  return {
10347
10739
  ...fs,
10740
+ async exclusive(path, run2, follow2 = true) {
10741
+ const at = fs.resolvePath("/", path);
10742
+ const key = follow2 ? nodes.targetKey(at) : nodes.entryKey(at);
10743
+ const locked = await key.catch(() => at);
10744
+ return nodes.onEntry(
10745
+ locked,
10746
+ () => run2({
10747
+ writeFile: (p, content, owner) => fs.writeFile(p, content, owner),
10748
+ direct: locked === at
10749
+ })
10750
+ );
10751
+ },
10348
10752
  async rm(path, options) {
10349
10753
  const at = await nodes.entryKey(fs.resolvePath("/", path));
10350
10754
  await nodes.unlinking(at);
@@ -10377,10 +10781,10 @@ function keepingOpen(fs, nodes) {
10377
10781
  };
10378
10782
  }
10379
10783
  function vfsFile(fs, opts, nodes) {
10380
- const access = opts.flags & O_ACCMODE;
10381
- const readable = access !== O_WRONLY;
10382
- const writable = access === O_WRONLY || access === O_RDWR;
10383
- const node = !nodes || opts.orphan ? new VfsNode(fs, opts.path, opts.contents, opts.orphan === true, opts.dirty === true) : opts.contents !== void 0 ? nodes.adopt(opts.path, opts.contents, opts.dirty === true, writable, opts.id) : nodes.open(opts.path, opts.pin, writable, opts.id);
10784
+ const access2 = opts.flags & O_ACCMODE;
10785
+ const readable2 = access2 !== O_WRONLY;
10786
+ const writable2 = access2 === O_WRONLY || access2 === O_RDWR;
10787
+ const node = !nodes || opts.orphan ? new VfsNode(fs, opts.path, opts.contents, opts.orphan === true, opts.dirty === true) : opts.contents !== void 0 ? nodes.adopt(opts.path, opts.contents, opts.dirty === true, writable2, opts.id) : nodes.open(opts.path, opts.pin, writable2, opts.id);
10384
10788
  let openError;
10385
10789
  const atOpen = (op) => {
10386
10790
  void node.serial(op).catch((err) => {
@@ -10401,12 +10805,12 @@ function vfsFile(fs, opts, nodes) {
10401
10805
  return op();
10402
10806
  });
10403
10807
  return new OpenFile({
10404
- read: readable ? (max) => serial(async () => {
10808
+ read: readable2 ? (max) => serial(async () => {
10405
10809
  const out = await node.pread(max, offset);
10406
10810
  offset += out.length;
10407
10811
  return out;
10408
10812
  }) : void 0,
10409
- write: writable ? (bytes) => serial(async () => {
10813
+ write: writable2 ? (bytes) => serial(async () => {
10410
10814
  if (opts.flags & O_APPEND) offset = await node.size();
10411
10815
  offset += await node.pwrite(bytes, offset);
10412
10816
  return bytes.length;
@@ -10421,15 +10825,15 @@ function vfsFile(fs, opts, nodes) {
10421
10825
  return offset;
10422
10826
  }),
10423
10827
  pread: (max, at) => serial(() => {
10424
- if (!readable) throw new KernelError("EBADF");
10828
+ if (!readable2) throw new KernelError("EBADF");
10425
10829
  return node.pread(max, at);
10426
10830
  }),
10427
10831
  pwrite: (bytes, at) => serial(() => {
10428
- if (!writable) throw new KernelError("EBADF");
10832
+ if (!writable2) throw new KernelError("EBADF");
10429
10833
  return node.pwrite(bytes, at);
10430
10834
  }),
10431
10835
  resize: (size) => serial(() => {
10432
- if (!writable) throw new KernelError("EBADF");
10836
+ if (!writable2) throw new KernelError("EBADF");
10433
10837
  return node.truncate(size);
10434
10838
  }),
10435
10839
  stat: () => serial(async () => ({
@@ -10437,13 +10841,14 @@ function vfsFile(fs, opts, nodes) {
10437
10841
  size: await node.size(),
10438
10842
  ...node.orphaned ? { orphan: true } : {}
10439
10843
  })),
10844
+ known: () => node.known(),
10440
10845
  flush: () => serial(() => node.flush()),
10441
10846
  close: () => node.serial(async () => {
10442
10847
  try {
10443
10848
  takeOpenError();
10444
10849
  await node.flush();
10445
10850
  } finally {
10446
- nodes?.closed(node, writable);
10851
+ nodes?.closed(node, writable2);
10447
10852
  }
10448
10853
  })
10449
10854
  });
@@ -10814,7 +11219,7 @@ var WasmProcess = class {
10814
11219
  this.fds.installAt(fd, this.openVfsFile(open));
10815
11220
  } else if (req.create) {
10816
11221
  await nodes.onEntry(path, async () => {
10817
- const fresh = this.options.owners && !await present(this.options.fs, path, true);
11222
+ const fresh = this.options.owners && !await present2(this.options.fs, path, true);
10818
11223
  this.fds.installAt(fd, this.openVfsFile(open, await nodes.identify(path)));
10819
11224
  await this.fds.get(fd).file.stat?.();
10820
11225
  if (fresh) await this.options.fs.chown?.(path, { ...this.newOwner(), kind: "new" });
@@ -10822,7 +11227,7 @@ var WasmProcess = class {
10822
11227
  } else {
10823
11228
  this.fds.installAt(fd, this.openVfsFile(open, await nodes.identify(path)));
10824
11229
  }
10825
- if (req.existing && !await present(this.options.fs, path, true)) {
11230
+ if (req.existing && !await present2(this.options.fs, path, true)) {
10826
11231
  throw new KernelError("ENOENT");
10827
11232
  }
10828
11233
  } catch (err) {
@@ -10831,6 +11236,20 @@ var WasmProcess = class {
10831
11236
  }
10832
11237
  return fd;
10833
11238
  }
11239
+ async identified(stat, known) {
11240
+ const first = await stat();
11241
+ if (first.orphan) return first;
11242
+ const ino = known?.();
11243
+ if (ino !== void 0) return { ...first, ino };
11244
+ const { fs } = this.options;
11245
+ const look = async () => {
11246
+ const now = await stat();
11247
+ if (now.path !== first.path || now.orphan) return now;
11248
+ const st = await fs.stat?.(now.path).catch(() => void 0);
11249
+ return st?.ino === void 0 ? now : { ...now, ino: st.ino };
11250
+ };
11251
+ return fs.exclusive ? fs.exclusive(first.path, look) : look();
11252
+ }
10834
11253
  newOwner() {
10835
11254
  if (!this.options.owners) return void 0;
10836
11255
  return { uid: this.cred.euid, gid: this.cred.egid, mode: 438 & ~this.umask };
@@ -10842,14 +11261,14 @@ var WasmProcess = class {
10842
11261
  if (req.flags & WRITE_ACCESS || req.create || req.truncate || req.exclusive) {
10843
11262
  throw new KernelError("EROFS");
10844
11263
  }
10845
- const tree = new ProcTree(
11264
+ const tree2 = new ProcTree(
10846
11265
  {
10847
11266
  procList: () => this.options.processes?.() ?? { boot: 0, processes: [] },
10848
11267
  mountList: () => this.options.mounts?.() ?? []
10849
11268
  },
10850
11269
  (await this.options.identity?.())?.pid ?? this.pid
10851
11270
  );
10852
- return memoryFile(tree.read(path), path, req.position);
11271
+ return memoryFile(tree2.read(path), path, req.position);
10853
11272
  }
10854
11273
  openVfsFile(req, id2) {
10855
11274
  return vfsFile(
@@ -10992,7 +11411,11 @@ var WasmProcess = class {
10992
11411
  await file.resize(req.size);
10993
11412
  return { ok: true, kind: "void" };
10994
11413
  case "fd-vfs-stat":
10995
- return { ok: true, kind: "json", json: await file.stat() };
11414
+ return {
11415
+ ok: true,
11416
+ kind: "json",
11417
+ json: req.identity ? await this.identified(file.stat.bind(file), file.known?.bind(file)) : await file.stat()
11418
+ };
10996
11419
  }
10997
11420
  }
10998
11421
  keptPath;
@@ -11001,7 +11424,7 @@ var WasmProcess = class {
11001
11424
  const held = handle === void 0 ? void 0 : this.keptHandle?.(handle);
11002
11425
  if (held !== void 0) return held;
11003
11426
  const kept = this.keptPath?.(path);
11004
- return kept !== void 0 && !await present(this.options.fs, path, true) ? kept : path;
11427
+ return kept !== void 0 && !await present2(this.options.fs, path, true) ? kept : path;
11005
11428
  }
11006
11429
  async promote(req) {
11007
11430
  if (!this.fds.get(req.fd).file.held) throw new KernelError("EBADF");
@@ -11511,6 +11934,7 @@ function inherited(opts) {
11511
11934
  function holdingToken(opts, holds, caller) {
11512
11935
  return mintSyncFsToken({
11513
11936
  ...opts.owners ? caller : {},
11937
+ ...opts.dac ? { dac: opts.dac, pid: opts.pid } : {},
11514
11938
  fs: opts.fs,
11515
11939
  cwd: opts.cwd,
11516
11940
  ...opts.statfs ? { statfs: opts.statfs } : {},
@@ -12140,9 +12564,9 @@ function serveFilesystem(port, handlers, capabilities = {}, options = {}) {
12140
12564
  let open = false;
12141
12565
  const reply = (message3, transfer = []) => port.postMessage(message3, transfer);
12142
12566
  async function hello(message3) {
12143
- const refused = driverVersionError(message3.protocol);
12144
- if (refused) {
12145
- reply({ hello: { protocol: DRIVER_PROTOCOL, error: refused, errno: "EPROTO" } });
12567
+ const refused2 = driverVersionError(message3.protocol);
12568
+ if (refused2) {
12569
+ reply({ hello: { protocol: DRIVER_PROTOCOL, error: refused2, errno: "EPROTO" } });
12146
12570
  return;
12147
12571
  }
12148
12572
  try {
@@ -12684,7 +13108,7 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
12684
13108
  }, againMs);
12685
13109
  timer.unref?.();
12686
13110
  });
12687
- async function writable(client, path, flags) {
13111
+ async function writable2(client, path, flags) {
12688
13112
  const { ifMatch: _, ...asked } = flags;
12689
13113
  const opening = () => client.call("open", { path: rel(path), ...asked });
12690
13114
  try {
@@ -12715,7 +13139,7 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
12715
13139
  async open(path, flags) {
12716
13140
  const client = current();
12717
13141
  const token = client.token;
12718
- const { opened: opened2, restore } = await writable(client, path, flags);
13142
+ const { opened: opened2, restore } = await writable2(client, path, flags);
12719
13143
  if (flags.ifMatch !== void 0 && opened2.attr?.etag !== flags.ifMatch) {
12720
13144
  await client.call("release", { fh: opened2.fh }, true);
12721
13145
  throw fsError2("ESTALE", `${path} changed on the host while it was being read`);
@@ -12888,7 +13312,7 @@ var PORT_DEAD_MS = 1e4;
12888
13312
  var PORT_LINGER_MS = 1e3;
12889
13313
  function timed(transport, ms) {
12890
13314
  const inflight = /* @__PURE__ */ new Set();
12891
- const guard = (name2, run2, late, expire) => new Promise((resolve, reject) => {
13315
+ const guard2 = (name2, run2, late, expire) => new Promise((resolve, reject) => {
12892
13316
  let expired = false;
12893
13317
  const running = run2();
12894
13318
  inflight.add(running);
@@ -12937,15 +13361,15 @@ function timed(transport, ms) {
12937
13361
  },
12938
13362
  retire: (why) => transport.retire?.(why),
12939
13363
  ...transport.cleanup ? { cleanup } : {},
12940
- call: (op, body, handle) => guard(
13364
+ call: (op, body, handle) => guard2(
12941
13365
  op,
12942
13366
  () => transport.call(op, body, handle),
12943
13367
  op === "open" ? release : void 0,
12944
13368
  op === "release" ? (running) => void cleanup("release", body ?? {}, running).catch(() => {
12945
13369
  }) : void 0
12946
13370
  ),
12947
- read: (fh, offset, size, ifMatch) => guard("read", () => transport.read(fh, offset, size, ifMatch)),
12948
- write: (fh, offset, bytes) => guard("write", () => transport.write(fh, offset, bytes)),
13371
+ read: (fh, offset, size, ifMatch) => guard2("read", () => transport.read(fh, offset, size, ifMatch)),
13372
+ write: (fh, offset, bytes) => guard2("write", () => transport.write(fh, offset, bytes)),
12949
13373
  watch: (signal, onEvent, onOpen) => transport.watch(signal, onEvent, onOpen)
12950
13374
  };
12951
13375
  }
@@ -13223,10 +13647,10 @@ var ChunkedBytes = class {
13223
13647
  }
13224
13648
  this.size = size;
13225
13649
  }
13226
- room(index, need) {
13650
+ room(index, need2) {
13227
13651
  const chunk2 = this.chunks[index];
13228
- if (chunk2 && chunk2.length >= need) return chunk2;
13229
- const grown = new Uint8Array(Math.min(CHUNK3, Math.max(need, (chunk2?.length ?? 0) * 2)));
13652
+ if (chunk2 && chunk2.length >= need2) return chunk2;
13653
+ const grown = new Uint8Array(Math.min(CHUNK3, Math.max(need2, (chunk2?.length ?? 0) * 2)));
13230
13654
  if (chunk2) grown.set(chunk2);
13231
13655
  this.chunks[index] = grown;
13232
13656
  return grown;
@@ -13273,14 +13697,14 @@ function tmpfs() {
13273
13697
  const root = make("directory", 16877);
13274
13698
  const handles = /* @__PURE__ */ new Map();
13275
13699
  let nextFh = 0;
13276
- const parts = (path) => path.split("/").filter(Boolean);
13700
+ const parts2 = (path) => path.split("/").filter(Boolean);
13277
13701
  const place = (dir, name2, node) => {
13278
13702
  dir.children.set(name2, node);
13279
13703
  dir.mtime = Date.now();
13280
13704
  };
13281
13705
  function find(path) {
13282
13706
  let node = root;
13283
- for (const name2 of parts(path)) {
13707
+ for (const name2 of parts2(path)) {
13284
13708
  if (node.kind !== "directory") throw fsError2("ENOTDIR", path);
13285
13709
  const next2 = node.children.get(name2);
13286
13710
  if (!next2) throw fsError2("ENOENT", path);
@@ -13289,7 +13713,7 @@ function tmpfs() {
13289
13713
  return node;
13290
13714
  }
13291
13715
  function parent(path) {
13292
- const names = parts(path);
13716
+ const names = parts2(path);
13293
13717
  const name2 = names.pop();
13294
13718
  if (name2 === void 0) throw fsError2("EBUSY", "the root of a mount");
13295
13719
  const dir = find(`/${names.join("/")}`);
@@ -13374,8 +13798,8 @@ function tmpfs() {
13374
13798
  dir.mtime = Date.now();
13375
13799
  },
13376
13800
  async rename(from, to) {
13377
- const a = `/${parts(from).join("/")}`;
13378
- const b = `/${parts(to).join("/")}`;
13801
+ const a = `/${parts2(from).join("/")}`;
13802
+ const b = `/${parts2(to).join("/")}`;
13379
13803
  if (b !== a && b.startsWith(`${a}/`)) throw fsError2("EINVAL", `${to} is inside ${from}`);
13380
13804
  const source = parent(from);
13381
13805
  const node = source.dir.children.get(source.name);
@@ -13734,11 +14158,13 @@ var Launcher = class {
13734
14158
  booting = new AbortController();
13735
14159
  fstab = Promise.resolve([]);
13736
14160
  usersMode;
14161
+ dac;
13737
14162
  nextPid = 1e3;
13738
14163
  boot = Date.now();
13739
14164
  terminals = 0;
13740
14165
  constructor(options) {
13741
14166
  this.usersMode = usersMode(options.users);
14167
+ this.dac = this.usersMode === "off" ? void 0 : new Dac(this.usersMode, options.onDenial);
13742
14168
  if (this.owners) options.fs.trackOwners?.();
13743
14169
  this.mounts = new MountTable({
13744
14170
  open: (type, spec) => this.driver(type, spec),
@@ -14069,6 +14495,7 @@ var Launcher = class {
14069
14495
  ...req.umask !== void 0 ? { umask: req.umask } : {},
14070
14496
  cred: req.cred,
14071
14497
  ...this.owners ? { owners: true } : {},
14498
+ ...this.dac ? { dac: this.dac } : {},
14072
14499
  program: req.program,
14073
14500
  argv0: req.argv0,
14074
14501
  args: req.args,
@@ -14670,10 +15097,11 @@ function usersOp(l, req) {
14670
15097
  if (req.op === "groups-remove-member") return l.users.removeMember(req.group, req.user);
14671
15098
  return l.users.list();
14672
15099
  }
14673
- function hostOptions(req) {
15100
+ function hostOptions(req, port) {
14674
15101
  return {
14675
15102
  ...req.hostname !== void 0 ? { hostname: req.hostname } : {},
14676
- ...req.users !== void 0 ? { users: req.users } : {}
15103
+ ...req.users !== void 0 ? { users: req.users } : {},
15104
+ ...req.denials ? { onDenial: (denial) => port.postMessage({ denial }) } : {}
14677
15105
  };
14678
15106
  }
14679
15107
  function serveKernel(port, deps) {
@@ -14750,7 +15178,7 @@ function serveKernel(port, deps) {
14750
15178
  ...deps.createDriverWorker ? { createDriverWorker: deps.createDriverWorker } : {},
14751
15179
  ...req.modules ? { modules: req.modules } : {},
14752
15180
  ...req.env ? { env: req.env } : {},
14753
- ...hostOptions(req),
15181
+ ...hostOptions(req, port),
14754
15182
  ...net,
14755
15183
  ...media ? { media } : {},
14756
15184
  onMountPending: (medium) => port.postMessage({ medium }),