@ai-ecoverse/slicc-kernel 1.61.2 → 1.62.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,6 +8305,16 @@ 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
+ }
7961
8318
  async function dispatchSyncFs(req) {
7962
8319
  const entry = resolveSyncFsToken(req.token);
7963
8320
  if (!entry) return { ok: false, errno: "EACCES", message: "sync-fs: unknown or revoked token" };
@@ -7966,16 +8323,10 @@ async function dispatchSyncFs(req) {
7966
8323
  const path = fs.resolvePath(cwd, req.path);
7967
8324
  if (req.op === "kept") return json2(entry.keptFor?.(req.handle ?? -1) !== void 0);
7968
8325
  if (req.op === "stat" && req.handle !== void 0) return await keptStat(entry, req);
8326
+ if (req.op === "hold") return holdOp(entry, path, req);
8327
+ const refused2 = await guarded(entry, path, req);
8328
+ if (refused2) return refused2;
7969
8329
  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
8330
  if (entry.revoked?.(path)) return { ok: false, errno: "EIO", message: `${path} was unmounted` };
7980
8331
  if (req.op === "write" && req.mode === 1 || UNLINKED_OPS.has(req.op)) {
7981
8332
  const serve = async () => run(entry, await keptOr(entry, path, req.handle), req);
@@ -8149,9 +8500,9 @@ function mountTable(mounts = []) {
8149
8500
  for (const m of mounts) {
8150
8501
  const options = Object.entries(m.options).map(([k, v]) => v === "" ? k : `${k}=${v}`);
8151
8502
  if (m.state !== "ok") options.push(m.state);
8152
- const access = options.includes("ro") ? [] : ["rw"];
8503
+ const access2 = options.includes("ro") ? [] : ["rw"];
8153
8504
  lines2.push(
8154
- `${field2(m.source)} ${field2(m.target)} ${m.type} ${[...access, ...options].join(",")} 0 0`
8505
+ `${field2(m.source)} ${field2(m.target)} ${m.type} ${[...access2, ...options].join(",")} 0 0`
8155
8506
  );
8156
8507
  }
8157
8508
  return `${lines2.join("\n")}
@@ -8587,8 +8938,8 @@ var ProcTree = class {
8587
8938
  alive(pid) {
8588
8939
  return this.listing().processes.some((p) => p.pid === pid);
8589
8940
  }
8590
- node(parts) {
8591
- const [top, leaf, ...rest2] = parts;
8941
+ node(parts2) {
8942
+ const [top, leaf, ...rest2] = parts2;
8592
8943
  if (top === void 0) {
8593
8944
  return {
8594
8945
  kind: "dir",
@@ -8626,9 +8977,9 @@ var ProcTree = class {
8626
8977
  };
8627
8978
  }
8628
8979
  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;
8980
+ const parts2 = path.split("/").filter((p) => p !== "").slice(1);
8981
+ if (parts2[0] === "self" && (follow2 || parts2.length > 1)) parts2[0] = String(this.pid);
8982
+ return parts2;
8632
8983
  }
8633
8984
  find(path, follow2) {
8634
8985
  const node = this.node(this.parts(path, follow2));
@@ -8714,8 +9065,8 @@ function refusalOf(found) {
8714
9065
  return found === false ? { refused: "ENOTDIR" } : found;
8715
9066
  }
8716
9067
  function throughLink(target, done4, todo) {
8717
- const parts = target.split("/").filter((part) => part !== "");
8718
- return [target.startsWith("/") ? [] : done4, [...parts, ...todo]];
9068
+ const parts2 = target.split("/").filter((part) => part !== "");
9069
+ return [target.startsWith("/") ? [] : done4, [...parts2, ...todo]];
8719
9070
  }
8720
9071
  async function procAlias(fs, path, follow2) {
8721
9072
  const { readlink, lstat } = fs;
@@ -8725,14 +9076,14 @@ async function procAlias(fs, path, follow2) {
8725
9076
  () => false
8726
9077
  );
8727
9078
  if (!physical) return void 0;
8728
- const walk = procWalk(path, follow2);
8729
- let step = walk.next();
9079
+ const walk2 = procWalk(path, follow2);
9080
+ let step = walk2.next();
8730
9081
  while (!step.done) {
8731
9082
  const { at, check } = step.value;
8732
9083
  const link2 = await readlink.call(fs, at).catch(() => void 0);
8733
9084
  const isDir = (st) => st.isDirectory === true;
8734
9085
  const target = link2 ?? (check ? await lstat.call(fs, at).then(isDir, failed) : true);
8735
- step = walk.next(target);
9086
+ step = walk2.next(target);
8736
9087
  }
8737
9088
  return step.value;
8738
9089
  }
@@ -10079,10 +10430,10 @@ var VfsNode = class {
10079
10430
  this.serial(() => this.flush()).catch(() => void 0);
10080
10431
  }, delay);
10081
10432
  }
10082
- ensure(need) {
10433
+ ensure(need2) {
10083
10434
  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));
10435
+ if (cur.length >= need2) return cur;
10436
+ const grown = new Uint8Array(Math.max(need2, cur.length * 2, 256));
10086
10437
  grown.set(cur.subarray(0, this.length));
10087
10438
  this.data = grown;
10088
10439
  return grown;
@@ -10125,14 +10476,14 @@ var VfsNodes = class {
10125
10476
  return run2;
10126
10477
  }
10127
10478
  async entryKey(path) {
10128
- const parent = parentOf5(path);
10479
+ const parent = parentOf3(path);
10129
10480
  const dir = await this.real(parent, true, () => realDir(this.fs, parent));
10130
10481
  return joinName(dir, path.slice(path.lastIndexOf("/") + 1));
10131
10482
  }
10132
10483
  targetKey(path) {
10133
10484
  return this.real(path, true, () => realTarget(this.fs, path));
10134
10485
  }
10135
- async real(path, follow2, walk) {
10486
+ async real(path, follow2, walk2) {
10136
10487
  if (this.fs.realpath) {
10137
10488
  try {
10138
10489
  return await this.fs.realpath(path, follow2);
@@ -10140,13 +10491,13 @@ var VfsNodes = class {
10140
10491
  if (errCode(err) === "ELOOP") throw err;
10141
10492
  }
10142
10493
  }
10143
- return walk();
10494
+ return walk2();
10144
10495
  }
10145
10496
  createExclusive(path, owner) {
10146
10497
  return this.onEntry(path, async () => {
10147
10498
  const node = this.byPath.get(path);
10148
10499
  const held = node ? await node.serial(async () => node.holds()) : false;
10149
- if (held || await present(this.fs, path)) throw fsError("EEXIST", path);
10500
+ if (held || await present2(this.fs, path)) throw fsError("EEXIST", path);
10150
10501
  await this.fs.writeFile(path, new Uint8Array(0), owner);
10151
10502
  });
10152
10503
  }
@@ -10206,13 +10557,13 @@ var VfsNodes = class {
10206
10557
  if (id2 !== void 0) this.know(node, id2);
10207
10558
  return node;
10208
10559
  }
10209
- open(path, pin, writable = false, id2) {
10560
+ open(path, pin, writable2 = false, id2) {
10210
10561
  const node = this.named(path, id2) ?? this.create(path, id2, new VfsNode(this.fs, path, void 0, false, false, pin));
10211
10562
  node.opens++;
10212
- if (writable) node.writers++;
10563
+ if (writable2) node.writers++;
10213
10564
  return node;
10214
10565
  }
10215
- adopt(path, contents2, dirty, writable = false, id2) {
10566
+ adopt(path, contents2, dirty, writable2 = false, id2) {
10216
10567
  let node = this.named(path, id2);
10217
10568
  if (!node) {
10218
10569
  node = this.create(path, id2, new VfsNode(this.fs, path, contents2, false, dirty));
@@ -10220,7 +10571,7 @@ var VfsNodes = class {
10220
10571
  node.replace(contents2);
10221
10572
  }
10222
10573
  node.opens++;
10223
- if (writable) node.writers++;
10574
+ if (writable2) node.writers++;
10224
10575
  return node;
10225
10576
  }
10226
10577
  holds(prefix) {
@@ -10232,11 +10583,11 @@ var VfsNodes = class {
10232
10583
  if (node.writers > 0 && within5(path, prefix)) return true;
10233
10584
  return false;
10234
10585
  }
10235
- closed(node, writable = false) {
10586
+ closed(node, writable2 = false) {
10236
10587
  node.opens--;
10237
- if (writable) node.writers--;
10588
+ if (writable2) node.writers--;
10238
10589
  if (node.opens > 0) {
10239
- if (writable && node.writers === 0) this.onClosed?.(node.path);
10590
+ if (writable2 && node.writers === 0) this.onClosed?.(node.path);
10240
10591
  return;
10241
10592
  }
10242
10593
  this.forget(node);
@@ -10313,7 +10664,7 @@ var VfsNodes = class {
10313
10664
  }
10314
10665
  }
10315
10666
  };
10316
- async function present(fs, path, follow2 = false) {
10667
+ async function present2(fs, path, follow2 = false) {
10317
10668
  const look = (follow2 ? void 0 : fs.lstat) ?? fs.stat;
10318
10669
  try {
10319
10670
  await (look ? look.call(fs, path) : fs.readFileBuffer(path));
@@ -10327,7 +10678,7 @@ function errCode(err) {
10327
10678
  return err?.code;
10328
10679
  }
10329
10680
  async function targetDir(fs, path) {
10330
- return parentOf5(await realTarget(fs, path).catch(() => path));
10681
+ return parentOf3(await realTarget(fs, path).catch(() => path));
10331
10682
  }
10332
10683
  async function refuseReadonly(fs, path, flags, opts = {}) {
10333
10684
  const writes = (flags & O_ACCMODE) !== 0 || opts.truncate === true;
@@ -10377,10 +10728,10 @@ function keepingOpen(fs, nodes) {
10377
10728
  };
10378
10729
  }
10379
10730
  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);
10731
+ const access2 = opts.flags & O_ACCMODE;
10732
+ const readable2 = access2 !== O_WRONLY;
10733
+ const writable2 = access2 === O_WRONLY || access2 === O_RDWR;
10734
+ 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
10735
  let openError;
10385
10736
  const atOpen = (op) => {
10386
10737
  void node.serial(op).catch((err) => {
@@ -10401,12 +10752,12 @@ function vfsFile(fs, opts, nodes) {
10401
10752
  return op();
10402
10753
  });
10403
10754
  return new OpenFile({
10404
- read: readable ? (max) => serial(async () => {
10755
+ read: readable2 ? (max) => serial(async () => {
10405
10756
  const out = await node.pread(max, offset);
10406
10757
  offset += out.length;
10407
10758
  return out;
10408
10759
  }) : void 0,
10409
- write: writable ? (bytes) => serial(async () => {
10760
+ write: writable2 ? (bytes) => serial(async () => {
10410
10761
  if (opts.flags & O_APPEND) offset = await node.size();
10411
10762
  offset += await node.pwrite(bytes, offset);
10412
10763
  return bytes.length;
@@ -10421,15 +10772,15 @@ function vfsFile(fs, opts, nodes) {
10421
10772
  return offset;
10422
10773
  }),
10423
10774
  pread: (max, at) => serial(() => {
10424
- if (!readable) throw new KernelError("EBADF");
10775
+ if (!readable2) throw new KernelError("EBADF");
10425
10776
  return node.pread(max, at);
10426
10777
  }),
10427
10778
  pwrite: (bytes, at) => serial(() => {
10428
- if (!writable) throw new KernelError("EBADF");
10779
+ if (!writable2) throw new KernelError("EBADF");
10429
10780
  return node.pwrite(bytes, at);
10430
10781
  }),
10431
10782
  resize: (size) => serial(() => {
10432
- if (!writable) throw new KernelError("EBADF");
10783
+ if (!writable2) throw new KernelError("EBADF");
10433
10784
  return node.truncate(size);
10434
10785
  }),
10435
10786
  stat: () => serial(async () => ({
@@ -10443,7 +10794,7 @@ function vfsFile(fs, opts, nodes) {
10443
10794
  takeOpenError();
10444
10795
  await node.flush();
10445
10796
  } finally {
10446
- nodes?.closed(node, writable);
10797
+ nodes?.closed(node, writable2);
10447
10798
  }
10448
10799
  })
10449
10800
  });
@@ -10814,7 +11165,7 @@ var WasmProcess = class {
10814
11165
  this.fds.installAt(fd, this.openVfsFile(open));
10815
11166
  } else if (req.create) {
10816
11167
  await nodes.onEntry(path, async () => {
10817
- const fresh = this.options.owners && !await present(this.options.fs, path, true);
11168
+ const fresh = this.options.owners && !await present2(this.options.fs, path, true);
10818
11169
  this.fds.installAt(fd, this.openVfsFile(open, await nodes.identify(path)));
10819
11170
  await this.fds.get(fd).file.stat?.();
10820
11171
  if (fresh) await this.options.fs.chown?.(path, { ...this.newOwner(), kind: "new" });
@@ -10822,7 +11173,7 @@ var WasmProcess = class {
10822
11173
  } else {
10823
11174
  this.fds.installAt(fd, this.openVfsFile(open, await nodes.identify(path)));
10824
11175
  }
10825
- if (req.existing && !await present(this.options.fs, path, true)) {
11176
+ if (req.existing && !await present2(this.options.fs, path, true)) {
10826
11177
  throw new KernelError("ENOENT");
10827
11178
  }
10828
11179
  } catch (err) {
@@ -10842,14 +11193,14 @@ var WasmProcess = class {
10842
11193
  if (req.flags & WRITE_ACCESS || req.create || req.truncate || req.exclusive) {
10843
11194
  throw new KernelError("EROFS");
10844
11195
  }
10845
- const tree = new ProcTree(
11196
+ const tree2 = new ProcTree(
10846
11197
  {
10847
11198
  procList: () => this.options.processes?.() ?? { boot: 0, processes: [] },
10848
11199
  mountList: () => this.options.mounts?.() ?? []
10849
11200
  },
10850
11201
  (await this.options.identity?.())?.pid ?? this.pid
10851
11202
  );
10852
- return memoryFile(tree.read(path), path, req.position);
11203
+ return memoryFile(tree2.read(path), path, req.position);
10853
11204
  }
10854
11205
  openVfsFile(req, id2) {
10855
11206
  return vfsFile(
@@ -11001,7 +11352,7 @@ var WasmProcess = class {
11001
11352
  const held = handle === void 0 ? void 0 : this.keptHandle?.(handle);
11002
11353
  if (held !== void 0) return held;
11003
11354
  const kept = this.keptPath?.(path);
11004
- return kept !== void 0 && !await present(this.options.fs, path, true) ? kept : path;
11355
+ return kept !== void 0 && !await present2(this.options.fs, path, true) ? kept : path;
11005
11356
  }
11006
11357
  async promote(req) {
11007
11358
  if (!this.fds.get(req.fd).file.held) throw new KernelError("EBADF");
@@ -11511,6 +11862,7 @@ function inherited(opts) {
11511
11862
  function holdingToken(opts, holds, caller) {
11512
11863
  return mintSyncFsToken({
11513
11864
  ...opts.owners ? caller : {},
11865
+ ...opts.dac ? { dac: opts.dac, pid: opts.pid } : {},
11514
11866
  fs: opts.fs,
11515
11867
  cwd: opts.cwd,
11516
11868
  ...opts.statfs ? { statfs: opts.statfs } : {},
@@ -12140,9 +12492,9 @@ function serveFilesystem(port, handlers, capabilities = {}, options = {}) {
12140
12492
  let open = false;
12141
12493
  const reply = (message3, transfer = []) => port.postMessage(message3, transfer);
12142
12494
  async function hello(message3) {
12143
- const refused = driverVersionError(message3.protocol);
12144
- if (refused) {
12145
- reply({ hello: { protocol: DRIVER_PROTOCOL, error: refused, errno: "EPROTO" } });
12495
+ const refused2 = driverVersionError(message3.protocol);
12496
+ if (refused2) {
12497
+ reply({ hello: { protocol: DRIVER_PROTOCOL, error: refused2, errno: "EPROTO" } });
12146
12498
  return;
12147
12499
  }
12148
12500
  try {
@@ -12684,7 +13036,7 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
12684
13036
  }, againMs);
12685
13037
  timer.unref?.();
12686
13038
  });
12687
- async function writable(client, path, flags) {
13039
+ async function writable2(client, path, flags) {
12688
13040
  const { ifMatch: _, ...asked } = flags;
12689
13041
  const opening = () => client.call("open", { path: rel(path), ...asked });
12690
13042
  try {
@@ -12715,7 +13067,7 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
12715
13067
  async open(path, flags) {
12716
13068
  const client = current();
12717
13069
  const token = client.token;
12718
- const { opened: opened2, restore } = await writable(client, path, flags);
13070
+ const { opened: opened2, restore } = await writable2(client, path, flags);
12719
13071
  if (flags.ifMatch !== void 0 && opened2.attr?.etag !== flags.ifMatch) {
12720
13072
  await client.call("release", { fh: opened2.fh }, true);
12721
13073
  throw fsError2("ESTALE", `${path} changed on the host while it was being read`);
@@ -12888,7 +13240,7 @@ var PORT_DEAD_MS = 1e4;
12888
13240
  var PORT_LINGER_MS = 1e3;
12889
13241
  function timed(transport, ms) {
12890
13242
  const inflight = /* @__PURE__ */ new Set();
12891
- const guard = (name2, run2, late, expire) => new Promise((resolve, reject) => {
13243
+ const guard2 = (name2, run2, late, expire) => new Promise((resolve, reject) => {
12892
13244
  let expired = false;
12893
13245
  const running = run2();
12894
13246
  inflight.add(running);
@@ -12937,15 +13289,15 @@ function timed(transport, ms) {
12937
13289
  },
12938
13290
  retire: (why) => transport.retire?.(why),
12939
13291
  ...transport.cleanup ? { cleanup } : {},
12940
- call: (op, body, handle) => guard(
13292
+ call: (op, body, handle) => guard2(
12941
13293
  op,
12942
13294
  () => transport.call(op, body, handle),
12943
13295
  op === "open" ? release : void 0,
12944
13296
  op === "release" ? (running) => void cleanup("release", body ?? {}, running).catch(() => {
12945
13297
  }) : void 0
12946
13298
  ),
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)),
13299
+ read: (fh, offset, size, ifMatch) => guard2("read", () => transport.read(fh, offset, size, ifMatch)),
13300
+ write: (fh, offset, bytes) => guard2("write", () => transport.write(fh, offset, bytes)),
12949
13301
  watch: (signal, onEvent, onOpen) => transport.watch(signal, onEvent, onOpen)
12950
13302
  };
12951
13303
  }
@@ -13223,10 +13575,10 @@ var ChunkedBytes = class {
13223
13575
  }
13224
13576
  this.size = size;
13225
13577
  }
13226
- room(index, need) {
13578
+ room(index, need2) {
13227
13579
  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)));
13580
+ if (chunk2 && chunk2.length >= need2) return chunk2;
13581
+ const grown = new Uint8Array(Math.min(CHUNK3, Math.max(need2, (chunk2?.length ?? 0) * 2)));
13230
13582
  if (chunk2) grown.set(chunk2);
13231
13583
  this.chunks[index] = grown;
13232
13584
  return grown;
@@ -13273,14 +13625,14 @@ function tmpfs() {
13273
13625
  const root = make("directory", 16877);
13274
13626
  const handles = /* @__PURE__ */ new Map();
13275
13627
  let nextFh = 0;
13276
- const parts = (path) => path.split("/").filter(Boolean);
13628
+ const parts2 = (path) => path.split("/").filter(Boolean);
13277
13629
  const place = (dir, name2, node) => {
13278
13630
  dir.children.set(name2, node);
13279
13631
  dir.mtime = Date.now();
13280
13632
  };
13281
13633
  function find(path) {
13282
13634
  let node = root;
13283
- for (const name2 of parts(path)) {
13635
+ for (const name2 of parts2(path)) {
13284
13636
  if (node.kind !== "directory") throw fsError2("ENOTDIR", path);
13285
13637
  const next2 = node.children.get(name2);
13286
13638
  if (!next2) throw fsError2("ENOENT", path);
@@ -13289,7 +13641,7 @@ function tmpfs() {
13289
13641
  return node;
13290
13642
  }
13291
13643
  function parent(path) {
13292
- const names = parts(path);
13644
+ const names = parts2(path);
13293
13645
  const name2 = names.pop();
13294
13646
  if (name2 === void 0) throw fsError2("EBUSY", "the root of a mount");
13295
13647
  const dir = find(`/${names.join("/")}`);
@@ -13374,8 +13726,8 @@ function tmpfs() {
13374
13726
  dir.mtime = Date.now();
13375
13727
  },
13376
13728
  async rename(from, to) {
13377
- const a = `/${parts(from).join("/")}`;
13378
- const b = `/${parts(to).join("/")}`;
13729
+ const a = `/${parts2(from).join("/")}`;
13730
+ const b = `/${parts2(to).join("/")}`;
13379
13731
  if (b !== a && b.startsWith(`${a}/`)) throw fsError2("EINVAL", `${to} is inside ${from}`);
13380
13732
  const source = parent(from);
13381
13733
  const node = source.dir.children.get(source.name);
@@ -13734,11 +14086,13 @@ var Launcher = class {
13734
14086
  booting = new AbortController();
13735
14087
  fstab = Promise.resolve([]);
13736
14088
  usersMode;
14089
+ dac;
13737
14090
  nextPid = 1e3;
13738
14091
  boot = Date.now();
13739
14092
  terminals = 0;
13740
14093
  constructor(options) {
13741
14094
  this.usersMode = usersMode(options.users);
14095
+ this.dac = this.usersMode === "off" ? void 0 : new Dac(this.usersMode, options.onDenial);
13742
14096
  if (this.owners) options.fs.trackOwners?.();
13743
14097
  this.mounts = new MountTable({
13744
14098
  open: (type, spec) => this.driver(type, spec),
@@ -14069,6 +14423,7 @@ var Launcher = class {
14069
14423
  ...req.umask !== void 0 ? { umask: req.umask } : {},
14070
14424
  cred: req.cred,
14071
14425
  ...this.owners ? { owners: true } : {},
14426
+ ...this.dac ? { dac: this.dac } : {},
14072
14427
  program: req.program,
14073
14428
  argv0: req.argv0,
14074
14429
  args: req.args,
@@ -14670,10 +15025,11 @@ function usersOp(l, req) {
14670
15025
  if (req.op === "groups-remove-member") return l.users.removeMember(req.group, req.user);
14671
15026
  return l.users.list();
14672
15027
  }
14673
- function hostOptions(req) {
15028
+ function hostOptions(req, port) {
14674
15029
  return {
14675
15030
  ...req.hostname !== void 0 ? { hostname: req.hostname } : {},
14676
- ...req.users !== void 0 ? { users: req.users } : {}
15031
+ ...req.users !== void 0 ? { users: req.users } : {},
15032
+ ...req.denials ? { onDenial: (denial) => port.postMessage({ denial }) } : {}
14677
15033
  };
14678
15034
  }
14679
15035
  function serveKernel(port, deps) {
@@ -14750,7 +15106,7 @@ function serveKernel(port, deps) {
14750
15106
  ...deps.createDriverWorker ? { createDriverWorker: deps.createDriverWorker } : {},
14751
15107
  ...req.modules ? { modules: req.modules } : {},
14752
15108
  ...req.env ? { env: req.env } : {},
14753
- ...hostOptions(req),
15109
+ ...hostOptions(req, port),
14754
15110
  ...net,
14755
15111
  ...media ? { media } : {},
14756
15112
  onMountPending: (medium) => port.postMessage({ medium }),