@ai-ecoverse/slicc-kernel 1.62.4 → 1.64.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.
package/dist/node.js CHANGED
@@ -331,15 +331,24 @@ async function present(fs, path, directory) {
331
331
  const st = await fs.lstat(path).catch(() => void 0);
332
332
  return st !== void 0 && (!directory || st.isDirectory);
333
333
  }
334
+ async function finalOf(fs, walked, want, options) {
335
+ const perm = walked.perm;
336
+ if ((want & X_OK) === 0 || options.directory !== void 0) {
337
+ return [perm, options.directory !== false];
338
+ }
339
+ const look = options.follow === false ? fs.lstat : fs.stat;
340
+ const st = await look.call(fs, walked.path).catch(() => void 0);
341
+ return st ? [permOf(st), st.isDirectory] : [perm, false];
342
+ }
334
343
  async function access(fs, cred, path, want, options = {}) {
335
344
  const walked = await walk(fs, cred, path, options.follow !== false);
336
345
  if (walked.denied !== void 0) {
337
346
  if (await present(fs, walked.denied, true)) return walked;
338
347
  return { path, perm: void 0, missing: true };
339
348
  }
340
- const { perm } = walked;
341
- if (!perm) return { ...walked, missing: true };
342
- if (want === 0 || permits(cred, perm, want, options.directory !== false)) return walked;
349
+ if (!walked.perm) return { ...walked, missing: true };
350
+ const [perm, directory] = await finalOf(fs, walked, want, options);
351
+ if (want === 0 || permits(cred, perm, want, directory)) return walked;
343
352
  if (await present(fs, walked.path, false)) return { ...walked, denied: walked.path };
344
353
  return { ...walked, missing: true };
345
354
  }
@@ -350,6 +359,7 @@ function refused(path, errno = "EACCES") {
350
359
  // src/fs/dac.ts
351
360
  var MAX_DENIAL_KEYS = 1e4;
352
361
  var S_ISGID2 = 1024;
362
+ var DIR = { directory: true };
353
363
  var Dac = class {
354
364
  mode;
355
365
  hook;
@@ -386,6 +396,17 @@ var Dac = class {
386
396
  );
387
397
  }
388
398
  };
399
+ var MAX_CREATED = 1024;
400
+ var Created = class {
401
+ paths = /* @__PURE__ */ new Set();
402
+ add(path) {
403
+ if (this.paths.size >= MAX_CREATED) this.paths.clear();
404
+ this.paths.add(normalizePath(path));
405
+ }
406
+ take(path) {
407
+ return this.paths.delete(normalizePath(path));
408
+ }
409
+ };
389
410
  function deny(g, op, path, errno = "EACCES") {
390
411
  g.dac.denied({ pid: g.pid, uid: g.cred.euid, op, path, errno });
391
412
  }
@@ -394,20 +415,30 @@ async function need(g, op, path, want, options = {}, shown = path) {
394
415
  if (walked.denied !== void 0) deny(g, op, shown);
395
416
  return walked;
396
417
  }
418
+ function entryIn(dir, path) {
419
+ return joinName(dir.path, path.slice(path.lastIndexOf("/") + 1));
420
+ }
397
421
  async function stickyEntry(g, op, path, dir) {
398
422
  const perm = dir.perm;
399
423
  if (!perm || sticky(g.cred, perm, perm)) return;
400
- const entry = joinName(dir.path, path.slice(path.lastIndexOf("/") + 1));
424
+ const entry = entryIn(dir, path);
401
425
  if (sticky(g.cred, perm, (await ancestryOf(g.fs, entry)).at(-1))) return;
402
426
  if (await present(g.fs, entry, false)) deny(g, op, path, "EPERM");
403
427
  }
404
428
  async function inParent(g, op, path, old = false) {
405
- const dir = await need(g, op, parentOf(path), W_OK | X_OK, {}, path);
406
- if (old && dir.denied === void 0) await stickyEntry(g, op, path, dir);
429
+ if (old === "replaced") {
430
+ const dir2 = await need(g, op, parentOf(path), W_OK | X_OK, DIR, path);
431
+ return dir2.denied === void 0 ? stickyEntry(g, op, path, dir2) : void 0;
432
+ }
433
+ if (!old) return void await need(g, op, parentOf(path), W_OK | X_OK, DIR, path);
434
+ const dir = await access(g.fs, g.cred, parentOf(path), W_OK | X_OK, DIR);
435
+ if (dir.denied === void 0) return stickyEntry(g, op, path, dir);
436
+ const lookup2 = dir.denied === dir.path && dir.perm && permits(g.cred, dir.perm, X_OK);
437
+ if (!lookup2 || await present(g.fs, entryIn(dir, path), false)) deny(g, op, path);
407
438
  }
408
439
  async function makeIn(g, op, path) {
409
440
  for (let dir = parentOf(path); ; dir = parentOf(dir)) {
410
- const walked = await access(g.fs, g.cred, dir, W_OK | X_OK);
441
+ const walked = await access(g.fs, g.cred, dir, W_OK | X_OK, DIR);
411
442
  if (walked.denied !== void 0) {
412
443
  if (!await present(g.fs, path, true)) deny(g, op, path);
413
444
  return;
@@ -433,7 +464,7 @@ async function removable(g, op, path) {
433
464
  async function renamable(g, from, to) {
434
465
  if (from === to) return void await need(g, "rename", from, 0, { follow: false });
435
466
  await inParent(g, "rename", from, true);
436
- await inParent(g, "rename", to, true);
467
+ await inParent(g, "rename", to, "replaced");
437
468
  if (g.cred.euid === 0 || parentOf(from) === parentOf(to)) return;
438
469
  const st = await g.fs.lstat(from).catch(() => void 0);
439
470
  if (st?.isDirectory && !permits(g.cred, permOf(st), W_OK)) deny(g, "rename", from);
@@ -459,8 +490,20 @@ async function chmodMode(g, path, mode) {
459
490
  if (!g.dac.enforcing || !perm || g.cred.euid === 0 || inGroup(g.cred, perm.gid)) return mode;
460
491
  return mode & ~S_ISGID2;
461
492
  }
462
- async function retimable(g, op, path) {
463
- await ownerOnly(g, op, path, op !== "lutimes");
493
+ async function retimable(g, op, path, now = false) {
494
+ const follow2 = op !== "lutimes";
495
+ if (!now) return void await ownerOnly(g, op, path, follow2);
496
+ const walked = await need(g, op, path, 0, { follow: follow2 });
497
+ const perm = walked.perm;
498
+ if (!perm || g.cred.euid === 0 || perm.uid === g.cred.euid) return;
499
+ if (permits(g.cred, perm, W_OK, false)) return;
500
+ if (await present(g.fs, walked.path, false)) deny(g, op, path);
501
+ }
502
+ async function fileWritable(g, op, path) {
503
+ await need(g, op, path, W_OK, { directory: false });
504
+ }
505
+ function realIds(cred) {
506
+ return { ...cred, groups: [...cred.groups], euid: cred.ruid, egid: cred.rgid };
464
507
  }
465
508
  async function listable(g, op, path) {
466
509
  await need(g, op, path, R_OK | X_OK, { directory: true });
@@ -472,10 +515,16 @@ async function readable(g, op, path) {
472
515
  await need(g, op, path, R_OK, { directory: false });
473
516
  }
474
517
  async function writable(g, op, path) {
475
- const st = await g.fs.stat(path).catch(() => void 0);
476
- if (st) return void await need(g, op, path, W_OK, { directory: st.isDirectory });
518
+ await openable(g, op, path, W_OK, true);
519
+ }
520
+ async function creatable(g, op, path) {
477
521
  const target = await need(g, op, path, 0);
478
- if (target.denied === void 0) await need(g, op, parentOf(target.path), W_OK | X_OK, {}, path);
522
+ if (target.denied === void 0) await need(g, op, parentOf(target.path), W_OK | X_OK, DIR, path);
523
+ }
524
+ async function openable(g, op, path, want, create) {
525
+ const st = create ? await g.fs.stat(path).catch(() => void 0) : void 0;
526
+ if (create && !st) return creatable(g, op, path);
527
+ await need(g, op, path, want, { directory: st?.isDirectory ?? false });
479
528
  }
480
529
 
481
530
  // src/kernel/pipe.ts
@@ -2981,13 +3030,21 @@ var OpfsFs = class {
2981
3030
  trackOwners() {
2982
3031
  this.owners = true;
2983
3032
  }
3033
+ async hasOwner(path) {
3034
+ return ownOf((await this.meta.get([path]))[0]) !== void 0;
3035
+ }
2984
3036
  async ancestry(path) {
2985
3037
  const entries = await this.meta.get(chain(path));
2986
- return entries.map((entry, i) => ({
2987
- ...ownOf(entry) ?? (i === 0 ? ROOT_OWNER : inherit(entries.slice(0, i))),
2988
- ...entry?.mode !== void 0 ? { mode: entry.mode } : {},
2989
- ...entry?.link !== void 0 ? { link: entry.link } : {}
2990
- }));
3038
+ const out = [];
3039
+ for (const [i, entry] of entries.entries()) {
3040
+ const link2 = entry?.link !== void 0 && !await this.real(entry.path);
3041
+ out.push({
3042
+ ...ownOf(entry) ?? (i === 0 ? ROOT_OWNER : inherit(entries.slice(0, i))),
3043
+ ...entry?.mode !== void 0 ? { mode: entry.mode } : {},
3044
+ ...link2 ? { link: entry.link } : {}
3045
+ });
3046
+ }
3047
+ return out;
2991
3048
  }
2992
3049
  async handedTo(dir) {
2993
3050
  return inherit(await this.meta.get(chain(dir)));
@@ -3747,8 +3804,10 @@ var UnlinkedKeeper = class {
3747
3804
  count = 0;
3748
3805
  links = /* @__PURE__ */ new Map();
3749
3806
  linked = /* @__PURE__ */ new Map();
3807
+ dirOwner;
3750
3808
  constructor(fs, holders, rootOf, options = {}) {
3751
3809
  const { locks, owner } = options;
3810
+ this.dirOwner = options.dirOwner;
3752
3811
  this.fs = fs;
3753
3812
  this.holders = holders;
3754
3813
  this.rootOf = rootOf;
@@ -3859,7 +3918,8 @@ var UnlinkedKeeper = class {
3859
3918
  const dir = dirOf(this.rootOf(path));
3860
3919
  const hidden = `${dir}/${this.instance}.${++this.count}`;
3861
3920
  try {
3862
- await this.fs.mkdir(dir, { recursive: true });
3921
+ const owner = this.dirOwner;
3922
+ await this.fs.mkdir(dir, { recursive: true, ...owner ? { owner } : {} });
3863
3923
  await this.fs.rename(path, hidden);
3864
3924
  await this.linkedName(hidden);
3865
3925
  return hidden;
@@ -5017,6 +5077,7 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
5017
5077
  ...fs.realpath ? { realpath: fs.realpath.bind(fs) } : {},
5018
5078
  ...fs.chown ? { chown: fs.chown.bind(fs) } : {},
5019
5079
  ...fs.ancestry ? { ancestry: fs.ancestry.bind(fs) } : {},
5080
+ ...fs.hasOwner ? { hasOwner: fs.hasOwner.bind(fs) } : {},
5020
5081
  chmod: (path, mode) => fs.chmod(path, mode),
5021
5082
  utimes: (path, atime, mtime) => fs.utimes(path, atime, mtime),
5022
5083
  lutimes: (path, atime, mtime) => lutimesOf(fs, path, atime, mtime),
@@ -6388,6 +6449,10 @@ var MountFs = class {
6388
6449
  if (this.at(real)) throw errnoError("ENOSYS", path);
6389
6450
  return real;
6390
6451
  }
6452
+ async hasOwner(path) {
6453
+ if (this.at(path)) return true;
6454
+ return await this.base.hasOwner?.(path) ?? true;
6455
+ }
6391
6456
  async ancestry(path) {
6392
6457
  const names = chain(normalizePath(path));
6393
6458
  const first = names.findIndex((name2) => this.at(name2));
@@ -6477,6 +6542,9 @@ function revokeSyncFsToken(token) {
6477
6542
  }
6478
6543
 
6479
6544
  // src/realm/sync-fs-wire.ts
6545
+ var HOLD_READ = 4;
6546
+ var HOLD_WRITE = 8;
6547
+ var HOLD_CREATED = 16;
6480
6548
  var SYNC_FS_REQUEST_TIMEOUT_MS = 25e3;
6481
6549
  function syncError(code, label) {
6482
6550
  return Object.assign(new Error(`${code}: ${label}`), { code });
@@ -6600,6 +6668,7 @@ async function run(entry, path, req) {
6600
6668
  return done;
6601
6669
  case "create":
6602
6670
  await createExclusive(fs, path, ownerFor2(entry, req.mode ?? 438));
6671
+ entry.created?.add(path);
6603
6672
  return done;
6604
6673
  case "readlink":
6605
6674
  return json2(await fs.readlink(path));
@@ -6656,11 +6725,47 @@ async function guard(g, path, req, cwd) {
6656
6725
  return linkable(g, path, other(), req.mode === 1);
6657
6726
  case "utimes":
6658
6727
  case "lutimes":
6659
- return retimable(g, op, path);
6728
+ return retimable(g, op, path, req.now === true);
6729
+ case "read":
6730
+ case "pread":
6731
+ return req.handle === void 0 ? readable(g, op, path) : void 0;
6732
+ case "pwrite":
6733
+ case "truncate":
6734
+ return req.handle === void 0 ? fileWritable(g, op, path) : void 0;
6735
+ case "write":
6736
+ return req.handle === void 0 && req.mode !== 1 ? writable(g, op, path) : void 0;
6660
6737
  default:
6661
6738
  return void 0;
6662
6739
  }
6663
6740
  }
6741
+ async function opening(entry, path, mode) {
6742
+ const g = guardOf(entry);
6743
+ const want = (mode & HOLD_READ ? R_OK : 0) | (mode & HOLD_WRITE ? W_OK : 0);
6744
+ const created3 = (mode & HOLD_CREATED) !== 0 && entry.created?.take(path) === true;
6745
+ if (g && want !== 0 && !created3) {
6746
+ await need(g, "open", path, want, { directory: false });
6747
+ }
6748
+ }
6749
+ async function holdOp(entry, path, req) {
6750
+ const mode = req.mode ?? 0;
6751
+ const [on, open] = [(mode & 1) === 1, (mode & 2) === 2];
6752
+ if (on && open) {
6753
+ await opening(entry, path, mode);
6754
+ const real = await entry.fs.realpath?.(path, true).catch(() => path);
6755
+ return json2(entry.hold?.(path, on, open, real) ?? null);
6756
+ }
6757
+ entry.hold?.(path, on, open, void 0, req.handle);
6758
+ return done;
6759
+ }
6760
+ async function accessOp(entry, path, req) {
6761
+ const g = guardOf(entry);
6762
+ if (!g) return done;
6763
+ const open = req.arg2 === "open";
6764
+ const cred = open || req.arg2 === "eaccess" ? g.cred : realIds(g.cred);
6765
+ const options = { follow: req.nofollow !== true, ...open ? { directory: false } : {} };
6766
+ await need({ ...g, cred }, open ? "open" : "access", path, req.mode ?? 0, options);
6767
+ return done;
6768
+ }
6664
6769
  async function guarded(entry, path, req) {
6665
6770
  const g = guardOf(entry);
6666
6771
  if (!g) return void 0;
@@ -6724,16 +6829,6 @@ async function owned(entry, own, req, path, target) {
6724
6829
  }
6725
6830
  return own(target, () => run(entry, path, req));
6726
6831
  }
6727
- async function holdOp(entry, path, req) {
6728
- const mode = req.mode ?? 0;
6729
- const [on, open] = [(mode & 1) === 1, (mode & 2) === 2];
6730
- if (on && open) {
6731
- const real = await entry.fs.realpath?.(path, true).catch(() => path);
6732
- return json2(entry.hold?.(path, on, open, real) ?? null);
6733
- }
6734
- entry.hold?.(path, on, open, void 0, req.handle);
6735
- return done;
6736
- }
6737
6832
  var IDENTITY_OPS = /* @__PURE__ */ new Set([
6738
6833
  "write",
6739
6834
  "pwrite",
@@ -6770,7 +6865,8 @@ async function dispatchOne(entry, req) {
6770
6865
  const path = fs.resolvePath(cwd, req.path);
6771
6866
  if (req.op === "kept") return json2(entry.keptFor?.(req.handle ?? -1) !== void 0);
6772
6867
  if (req.op === "stat" && req.handle !== void 0) return await keptStat(entry, req);
6773
- if (req.op === "hold") return holdOp(entry, path, req);
6868
+ if (req.op === "access") return await accessOp(entry, path, req);
6869
+ if (req.op === "hold") return await holdOp(entry, path, req);
6774
6870
  const refused2 = await guarded(entry, path, req);
6775
6871
  if (refused2) return refused2;
6776
6872
  if (req.op === "statfs") return json2(await entry.statfs?.(path) ?? await storageUsage());
@@ -6966,188 +7062,6 @@ function ownNames(hostname) {
6966
7062
  return [.../* @__PURE__ */ new Set([hostname.toLowerCase(), ...LIBC_HOSTNAMES])];
6967
7063
  }
6968
7064
 
6969
- // src/process/process-fds.ts
6970
- var field2 = (text) => (text || "none").replace(/[\s\\]/g, (c) => `\\${c.charCodeAt(0).toString(8).padStart(3, "0")}`);
6971
- function mountTable(mounts = []) {
6972
- const root = globalThis.navigator?.storage ? "opfs" : "memory";
6973
- const lines2 = [`${root} / ${root} rw 0 0`, "devfs /dev devfs rw 0 0", "proc /proc proc rw 0 0"];
6974
- for (const m of mounts) {
6975
- const options = Object.entries(m.options).map(([k, v]) => v === "" ? k : `${k}=${v}`);
6976
- if (m.state !== "ok") options.push(m.state);
6977
- const access2 = options.includes("ro") ? [] : ["rw"];
6978
- lines2.push(
6979
- `${field2(m.source)} ${field2(m.target)} ${m.type} ${[...access2, ...options].join(",")} 0 0`
6980
- );
6981
- }
6982
- return `${lines2.join("\n")}
6983
- `;
6984
- }
6985
-
6986
- // src/process/procfs.ts
6987
- var HZ = 100;
6988
- var KIB = 1024;
6989
- var FALLBACK_MEMORY = 4 * KIB * KIB * KIB;
6990
- var PID_FILES = ["cmdline", "comm", "stat", "statm", "status"];
6991
- var SYSTEM_FILES = ["loadavg", "meminfo", "stat", "uptime"];
6992
- function commOf(info) {
6993
- const name2 = info.argv[0] ?? "";
6994
- return name2.slice(name2.lastIndexOf("/") + 1).slice(0, 15);
6995
- }
6996
- function ttyNumber(tty) {
6997
- const pts = tty && /^\/dev\/pts\/(\d+)$/.exec(tty);
6998
- if (pts) return 136 << 8 | Number(pts[1]);
6999
- const vt = tty && /^\/dev\/tty(\d+)$/.exec(tty);
7000
- return vt ? 4 << 8 | Number(vt[1]) : 0;
7001
- }
7002
- function pages(bytes) {
7003
- return Math.ceil(bytes / 4096);
7004
- }
7005
- function ticks(ms) {
7006
- return Math.max(0, Math.floor(ms * HZ / 1e3));
7007
- }
7008
- function pidStat(info, boot) {
7009
- const fields = [
7010
- info.pid,
7011
- `(${commOf(info)})`,
7012
- info.state,
7013
- info.ppid,
7014
- info.pgid,
7015
- info.sid,
7016
- ttyNumber(info.tty),
7017
- info.tty ? info.pgid : -1,
7018
- 4194304,
7019
- 0,
7020
- 0,
7021
- 0,
7022
- 0,
7023
- 0,
7024
- 0,
7025
- 0,
7026
- 0,
7027
- 20,
7028
- 0,
7029
- 1,
7030
- 0,
7031
- ticks(info.started - boot),
7032
- info.memory,
7033
- pages(info.memory),
7034
- "18446744073709551615",
7035
- ...Array(12).fill(0),
7036
- 17,
7037
- 0,
7038
- 0,
7039
- 0,
7040
- 0,
7041
- 0,
7042
- 0,
7043
- ...Array(7).fill(0),
7044
- 0
7045
- ];
7046
- return `${fields.join(" ")}
7047
- `;
7048
- }
7049
- var sigMask = (mask = 0) => (mask >>> 1).toString(16).padStart(16, "0");
7050
- function pidStatus(info) {
7051
- const state = info.state === "Z" ? "Z (zombie)" : "S (sleeping)";
7052
- const c = info.cred ?? ROOT;
7053
- const ids = (r, e, s) => `${r} ${e} ${s} ${e}`;
7054
- return [
7055
- `Name: ${commOf(info)}`,
7056
- `Umask: ${(info.umask ?? 18).toString(8).padStart(4, "0")}`,
7057
- `State: ${state}`,
7058
- `Tgid: ${info.pid}`,
7059
- "Ngid: 0",
7060
- `Pid: ${info.pid}`,
7061
- `PPid: ${info.ppid}`,
7062
- "TracerPid: 0",
7063
- `Uid: ${ids(c.ruid, c.euid, c.suid)}`,
7064
- `Gid: ${ids(c.rgid, c.egid, c.sgid)}`,
7065
- "FDSize: 64",
7066
- `VmSize: ${Math.ceil(info.memory / KIB)} kB`,
7067
- `VmRSS: ${Math.ceil(info.memory / KIB)} kB`,
7068
- `Groups: ${c.groups.join(" ")}`,
7069
- `NSpid: ${info.pid}`,
7070
- `NSpgid: ${info.pgid}`,
7071
- `NSsid: ${info.sid}`,
7072
- "Threads: 1",
7073
- "SigQ: 0/0",
7074
- "SigPnd: 0000000000000000",
7075
- "ShdPnd: 0000000000000000",
7076
- "SigBlk: 0000000000000000",
7077
- `SigIgn: ${sigMask(info.sigIgn)}`,
7078
- `SigCgt: ${sigMask(info.sigCgt)}`,
7079
- ""
7080
- ].join("\n");
7081
- }
7082
- function pidFile(name2, info, boot) {
7083
- if (name2 === "cmdline") return info.state === "Z" ? "" : `${info.argv.join("\0")}\0`;
7084
- if (name2 === "comm") return `${commOf(info)}
7085
- `;
7086
- if (name2 === "stat") return pidStat(info, boot);
7087
- if (name2 === "statm") {
7088
- const p = pages(info.memory);
7089
- return `${p} ${p} 0 0 0 ${p} 0
7090
- `;
7091
- }
7092
- return pidStatus(info);
7093
- }
7094
- function memoryInfo(source, used = 0) {
7095
- const total = source.deviceMemory ? source.deviceMemory * KIB * KIB * KIB : FALLBACK_MEMORY;
7096
- const free = Math.max(0, total - used - (source.usedHeap ?? 0));
7097
- const kb = (bytes) => `${Math.floor(bytes / KIB)} kB`;
7098
- return [
7099
- ["MemTotal", kb(total)],
7100
- ["MemFree", kb(free)],
7101
- ["MemAvailable", kb(free)],
7102
- ["Buffers", kb(0)],
7103
- ["Cached", kb(0)],
7104
- ["SwapCached", kb(0)],
7105
- ["Shmem", kb(0)],
7106
- ["SReclaimable", kb(0)],
7107
- ["SwapTotal", kb(0)],
7108
- ["SwapFree", kb(0)]
7109
- ].map(([key, value]) => `${`${key}:`.padEnd(16)}${value.padStart(12)}`).concat("").join("\n");
7110
- }
7111
- function systemFile(name2, listing, now, memory) {
7112
- const up = Math.max(0, now - listing.boot) / 1e3;
7113
- if (name2 === "uptime") return `${up.toFixed(2)} ${up.toFixed(2)}
7114
- `;
7115
- if (name2 === "meminfo") {
7116
- return memoryInfo(
7117
- memory,
7118
- listing.processes.reduce((sum, p) => sum + p.memory, 0)
7119
- );
7120
- }
7121
- const live = listing.processes.filter((p) => p.state !== "Z");
7122
- if (name2 === "loadavg") {
7123
- const last = Math.max(0, ...listing.processes.map((p) => p.pid));
7124
- return `0.00 0.00 0.00 1/${live.length} ${last}
7125
- `;
7126
- }
7127
- const idle = ticks(now - listing.boot);
7128
- return [
7129
- `cpu 0 0 0 ${idle} 0 0 0 0 0 0`,
7130
- `cpu0 0 0 0 ${idle} 0 0 0 0 0 0`,
7131
- "intr 0",
7132
- "ctxt 0",
7133
- `btime ${Math.floor(listing.boot / 1e3)}`,
7134
- `processes ${listing.processes.length}`,
7135
- "procs_running 1",
7136
- "procs_blocked 0",
7137
- ""
7138
- ].join("\n");
7139
- }
7140
- function memorySource(scope = globalThis) {
7141
- const nav = scope.navigator;
7142
- const perf = scope.performance;
7143
- const deviceMemory = nav?.deviceMemory;
7144
- const used = perf?.memory?.usedJSHeapSize;
7145
- return {
7146
- ...typeof deviceMemory === "number" && deviceMemory > 0 ? { deviceMemory } : {},
7147
- ...typeof used === "number" ? { usedHeap: used } : {}
7148
- };
7149
- }
7150
-
7151
7065
  // src/fs/pages.ts
7152
7066
  var PAGE = 64 * 1024;
7153
7067
  var READAHEAD = 1024 * 1024;
@@ -7373,6 +7287,191 @@ var AsyncRangedFile = class {
7373
7287
  }
7374
7288
  };
7375
7289
 
7290
+ // src/realm/live-vfs-fs.ts
7291
+ var EARLY_WHOLE_FILE = 64 * 1024;
7292
+
7293
+ // src/process/process-fds.ts
7294
+ var field2 = (text) => (text || "none").replace(/[\s\\]/g, (c) => `\\${c.charCodeAt(0).toString(8).padStart(3, "0")}`);
7295
+ function mountTable(mounts = []) {
7296
+ const root = globalThis.navigator?.storage ? "opfs" : "memory";
7297
+ const lines2 = [`${root} / ${root} rw 0 0`, "devfs /dev devfs rw 0 0", "proc /proc proc rw 0 0"];
7298
+ for (const m of mounts) {
7299
+ const options = Object.entries(m.options).map(([k, v]) => v === "" ? k : `${k}=${v}`);
7300
+ if (m.state !== "ok") options.push(m.state);
7301
+ const access2 = options.includes("ro") ? [] : ["rw"];
7302
+ lines2.push(
7303
+ `${field2(m.source)} ${field2(m.target)} ${m.type} ${[...access2, ...options].join(",")} 0 0`
7304
+ );
7305
+ }
7306
+ return `${lines2.join("\n")}
7307
+ `;
7308
+ }
7309
+
7310
+ // src/process/procfs.ts
7311
+ var HZ = 100;
7312
+ var KIB = 1024;
7313
+ var FALLBACK_MEMORY = 4 * KIB * KIB * KIB;
7314
+ var PID_FILES = ["cmdline", "comm", "stat", "statm", "status"];
7315
+ var SYSTEM_FILES = ["loadavg", "meminfo", "stat", "uptime"];
7316
+ function commOf(info) {
7317
+ const name2 = info.argv[0] ?? "";
7318
+ return name2.slice(name2.lastIndexOf("/") + 1).slice(0, 15);
7319
+ }
7320
+ function ttyNumber(tty) {
7321
+ const pts = tty && /^\/dev\/pts\/(\d+)$/.exec(tty);
7322
+ if (pts) return 136 << 8 | Number(pts[1]);
7323
+ const vt = tty && /^\/dev\/tty(\d+)$/.exec(tty);
7324
+ return vt ? 4 << 8 | Number(vt[1]) : 0;
7325
+ }
7326
+ function pages(bytes) {
7327
+ return Math.ceil(bytes / 4096);
7328
+ }
7329
+ function ticks(ms) {
7330
+ return Math.max(0, Math.floor(ms * HZ / 1e3));
7331
+ }
7332
+ function pidStat(info, boot) {
7333
+ const fields = [
7334
+ info.pid,
7335
+ `(${commOf(info)})`,
7336
+ info.state,
7337
+ info.ppid,
7338
+ info.pgid,
7339
+ info.sid,
7340
+ ttyNumber(info.tty),
7341
+ info.tty ? info.pgid : -1,
7342
+ 4194304,
7343
+ 0,
7344
+ 0,
7345
+ 0,
7346
+ 0,
7347
+ 0,
7348
+ 0,
7349
+ 0,
7350
+ 0,
7351
+ 20,
7352
+ 0,
7353
+ 1,
7354
+ 0,
7355
+ ticks(info.started - boot),
7356
+ info.memory,
7357
+ pages(info.memory),
7358
+ "18446744073709551615",
7359
+ ...Array(12).fill(0),
7360
+ 17,
7361
+ 0,
7362
+ 0,
7363
+ 0,
7364
+ 0,
7365
+ 0,
7366
+ 0,
7367
+ ...Array(7).fill(0),
7368
+ 0
7369
+ ];
7370
+ return `${fields.join(" ")}
7371
+ `;
7372
+ }
7373
+ var sigMask = (mask = 0) => (mask >>> 1).toString(16).padStart(16, "0");
7374
+ function pidStatus(info) {
7375
+ const state = info.state === "Z" ? "Z (zombie)" : "S (sleeping)";
7376
+ const c = info.cred ?? ROOT;
7377
+ const ids = (r, e, s) => `${r} ${e} ${s} ${e}`;
7378
+ return [
7379
+ `Name: ${commOf(info)}`,
7380
+ `Umask: ${(info.umask ?? 18).toString(8).padStart(4, "0")}`,
7381
+ `State: ${state}`,
7382
+ `Tgid: ${info.pid}`,
7383
+ "Ngid: 0",
7384
+ `Pid: ${info.pid}`,
7385
+ `PPid: ${info.ppid}`,
7386
+ "TracerPid: 0",
7387
+ `Uid: ${ids(c.ruid, c.euid, c.suid)}`,
7388
+ `Gid: ${ids(c.rgid, c.egid, c.sgid)}`,
7389
+ "FDSize: 64",
7390
+ `VmSize: ${Math.ceil(info.memory / KIB)} kB`,
7391
+ `VmRSS: ${Math.ceil(info.memory / KIB)} kB`,
7392
+ `Groups: ${c.groups.join(" ")}`,
7393
+ `NSpid: ${info.pid}`,
7394
+ `NSpgid: ${info.pgid}`,
7395
+ `NSsid: ${info.sid}`,
7396
+ "Threads: 1",
7397
+ "SigQ: 0/0",
7398
+ "SigPnd: 0000000000000000",
7399
+ "ShdPnd: 0000000000000000",
7400
+ "SigBlk: 0000000000000000",
7401
+ `SigIgn: ${sigMask(info.sigIgn)}`,
7402
+ `SigCgt: ${sigMask(info.sigCgt)}`,
7403
+ ""
7404
+ ].join("\n");
7405
+ }
7406
+ function pidFile(name2, info, boot) {
7407
+ if (name2 === "cmdline") return info.state === "Z" ? "" : `${info.argv.join("\0")}\0`;
7408
+ if (name2 === "comm") return `${commOf(info)}
7409
+ `;
7410
+ if (name2 === "stat") return pidStat(info, boot);
7411
+ if (name2 === "statm") {
7412
+ const p = pages(info.memory);
7413
+ return `${p} ${p} 0 0 0 ${p} 0
7414
+ `;
7415
+ }
7416
+ return pidStatus(info);
7417
+ }
7418
+ function memoryInfo(source, used = 0) {
7419
+ const total = source.deviceMemory ? source.deviceMemory * KIB * KIB * KIB : FALLBACK_MEMORY;
7420
+ const free = Math.max(0, total - used - (source.usedHeap ?? 0));
7421
+ const kb = (bytes) => `${Math.floor(bytes / KIB)} kB`;
7422
+ return [
7423
+ ["MemTotal", kb(total)],
7424
+ ["MemFree", kb(free)],
7425
+ ["MemAvailable", kb(free)],
7426
+ ["Buffers", kb(0)],
7427
+ ["Cached", kb(0)],
7428
+ ["SwapCached", kb(0)],
7429
+ ["Shmem", kb(0)],
7430
+ ["SReclaimable", kb(0)],
7431
+ ["SwapTotal", kb(0)],
7432
+ ["SwapFree", kb(0)]
7433
+ ].map(([key, value]) => `${`${key}:`.padEnd(16)}${value.padStart(12)}`).concat("").join("\n");
7434
+ }
7435
+ function systemFile(name2, listing, now, memory) {
7436
+ const up = Math.max(0, now - listing.boot) / 1e3;
7437
+ if (name2 === "uptime") return `${up.toFixed(2)} ${up.toFixed(2)}
7438
+ `;
7439
+ if (name2 === "meminfo") {
7440
+ return memoryInfo(
7441
+ memory,
7442
+ listing.processes.reduce((sum, p) => sum + p.memory, 0)
7443
+ );
7444
+ }
7445
+ const live = listing.processes.filter((p) => p.state !== "Z");
7446
+ if (name2 === "loadavg") {
7447
+ const last = Math.max(0, ...listing.processes.map((p) => p.pid));
7448
+ return `0.00 0.00 0.00 1/${live.length} ${last}
7449
+ `;
7450
+ }
7451
+ const idle = ticks(now - listing.boot);
7452
+ return [
7453
+ `cpu 0 0 0 ${idle} 0 0 0 0 0 0`,
7454
+ `cpu0 0 0 0 ${idle} 0 0 0 0 0 0`,
7455
+ "intr 0",
7456
+ "ctxt 0",
7457
+ `btime ${Math.floor(listing.boot / 1e3)}`,
7458
+ `processes ${listing.processes.length}`,
7459
+ "procs_running 1",
7460
+ "procs_blocked 0",
7461
+ ""
7462
+ ].join("\n");
7463
+ }
7464
+ function memorySource(scope = globalThis) {
7465
+ const nav = scope.navigator;
7466
+ const perf = scope.performance;
7467
+ const deviceMemory = nav?.deviceMemory;
7468
+ const used = perf?.memory?.usedJSHeapSize;
7469
+ return {
7470
+ ...typeof deviceMemory === "number" && deviceMemory > 0 ? { deviceMemory } : {},
7471
+ ...typeof used === "number" ? { usedHeap: used } : {}
7472
+ };
7473
+ }
7474
+
7376
7475
  // src/process/wasi/wasi-files.ts
7377
7476
  function normalize(path) {
7378
7477
  const out = [];
@@ -8561,11 +8660,11 @@ function realmCa(owner, store) {
8561
8660
  opened.set(store, cas);
8562
8661
  let ca = cas.get(owner);
8563
8662
  if (!ca) {
8564
- const opening = RealmCa.open(owner, store);
8565
- ca = opening;
8566
- cas.set(owner, opening);
8567
- opening.catch(() => {
8568
- if (cas.get(owner) === opening) cas.delete(owner);
8663
+ const opening2 = RealmCa.open(owner, store);
8664
+ ca = opening2;
8665
+ cas.set(owner, opening2);
8666
+ opening2.catch(() => {
8667
+ if (cas.get(owner) === opening2) cas.delete(owner);
8569
8668
  });
8570
8669
  }
8571
8670
  return ca;
@@ -10972,6 +11071,7 @@ var WasmProcess = class {
10972
11071
  throw Object.assign(new Error(alias.refused), { code: alias.refused });
10973
11072
  if (alias !== void 0) return this.openProc(req, alias);
10974
11073
  await refuseReadonly(this.options.fs, req.path, req.flags, req);
11074
+ await this.mayOpen(req);
10975
11075
  const fd = this.fds.install(heldFile(), 3);
10976
11076
  try {
10977
11077
  const nodes = this.nodes;
@@ -11017,6 +11117,17 @@ var WasmProcess = class {
11017
11117
  };
11018
11118
  return fs.exclusive ? fs.exclusive(first.path, look) : look();
11019
11119
  }
11120
+ async mayOpen(req) {
11121
+ const { guard: guard2 } = this.options;
11122
+ const inherited2 = req.orphan || req.contents !== void 0 || req.handle !== void 0;
11123
+ if (!guard2 || inherited2 || req.pin !== void 0) return;
11124
+ const g = { fs: guard2.fs, dac: guard2.dac, cred: this.cred, pid: this.pid };
11125
+ if (req.exclusive) return inParent(g, "open", req.path);
11126
+ if (req.created && guard2.created?.take(req.path)) return;
11127
+ const access2 = req.flags & WRITE_ACCESS;
11128
+ const want = (access2 === 1 ? 0 : R_OK) | (access2 !== 0 || req.truncate ? W_OK : 0);
11129
+ await openable(g, "open", req.path, want, req.create === true);
11130
+ }
11020
11131
  newOwner() {
11021
11132
  if (!this.options.owners) return void 0;
11022
11133
  return { uid: this.cred.euid, gid: this.cred.egid, mode: 438 & ~this.umask };
@@ -11701,7 +11812,7 @@ function inherited(opts) {
11701
11812
  function holdingToken(opts, holds, caller) {
11702
11813
  return mintSyncFsToken({
11703
11814
  ...opts.owners ? caller : {},
11704
- ...opts.dac ? { dac: opts.dac, pid: opts.pid } : {},
11815
+ ...opts.dac ? { dac: opts.dac, pid: opts.pid, created: caller.created } : {},
11705
11816
  fs: opts.fs,
11706
11817
  cwd: opts.cwd,
11707
11818
  ...opts.statfs ? { statfs: opts.statfs } : {},
@@ -11714,10 +11825,11 @@ function holdingToken(opts, holds, caller) {
11714
11825
  renamed: (from, to) => holds.renamed(from, to)
11715
11826
  });
11716
11827
  }
11717
- function processOptions(opts, header4, hooks) {
11828
+ function processOptions(opts, header4, hooks, created3) {
11718
11829
  return {
11719
11830
  ...inherited(opts),
11720
11831
  ...opts.owners ? { owners: true } : {},
11832
+ ...opts.dac ? { guard: { dac: opts.dac, fs: opts.fs, created: created3 } } : {},
11721
11833
  ...opts.identity ? { identity: opts.identity } : {},
11722
11834
  ...opts.onSyscall ? { onSyscall: opts.onSyscall } : {},
11723
11835
  ...opts.settling ? { settling: opts.settling } : {},
@@ -11751,20 +11863,27 @@ function spawnWasmProcess(opts) {
11751
11863
  const sab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
11752
11864
  const header4 = new Int32Array(sab, 0, SAB_HEADER_I32);
11753
11865
  const threadSignals = /* @__PURE__ */ new Map();
11866
+ const created3 = new Created();
11754
11867
  const process2 = new WasmProcess(
11755
11868
  opts.pid,
11756
11869
  opts.fds,
11757
- processOptions(opts, header4, {
11758
- raise: (sig) => signal(sig),
11759
- threadKill: (tid, sig) => killThread(threadSignals.get(tid), process2.signalMasks().caught, sig)
11760
- })
11870
+ processOptions(
11871
+ opts,
11872
+ header4,
11873
+ {
11874
+ raise: (sig) => signal(sig),
11875
+ threadKill: (tid, sig) => killThread(threadSignals.get(tid), process2.signalMasks().caught, sig)
11876
+ },
11877
+ created3
11878
+ )
11761
11879
  );
11762
11880
  publishStops(process2, header4);
11763
11881
  const holds = new HeldPaths();
11764
11882
  opts.held?.add(holds);
11765
11883
  const token = holdingToken(opts, holds, {
11766
11884
  cred: () => process2.cred,
11767
- umask: () => process2.umask
11885
+ umask: () => process2.umask,
11886
+ created: created3
11768
11887
  });
11769
11888
  process2.keptPath = (path) => holds.keptAt(path)?.hidden;
11770
11889
  process2.keptHandle = (handle2) => holds.keptFor(handle2)?.hidden;
@@ -12877,16 +12996,16 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
12877
12996
  });
12878
12997
  async function writable2(client, path, flags) {
12879
12998
  const { ifMatch: _, ...asked } = flags;
12880
- const opening = () => client.call("open", { path: rel(path), ...asked });
12999
+ const opening2 = () => client.call("open", { path: rel(path), ...asked });
12881
13000
  try {
12882
- return { opened: await opening(), restore: void 0 };
13001
+ return { opened: await opening2(), restore: void 0 };
12883
13002
  } catch (err) {
12884
13003
  if (!flags.write || codeOf4(err) !== "EACCES") throw err;
12885
13004
  const was = await client.call("stat", { path: rel(path) });
12886
13005
  const mode = was.mode ?? 0;
12887
13006
  if (mode & 128) throw err;
12888
13007
  const restore = { path: rel(path), mode, was };
12889
- const opened2 = await client.call("setattr", { path: rel(path), mode: mode | 128 }).then(opening).catch(async (again) => {
13008
+ const opened2 = await client.call("setattr", { path: rel(path), mode: mode | 128 }).then(opening2).catch(async (again) => {
12890
13009
  await settle(client, restore).catch(() => {
12891
13010
  });
12892
13011
  throw again;
@@ -13894,6 +14013,16 @@ var NOT_FOUND = 127;
13894
14013
  var DECIDE_MS = 100;
13895
14014
  var INIT_PID = 1;
13896
14015
  var SHARED_DIRS = ["/tmp", "/home"];
14016
+ var LAYOUT = [
14017
+ ["/", 493],
14018
+ ["/root", 448],
14019
+ ["/home", 493],
14020
+ ["/etc", 493],
14021
+ [PASSWD, 420],
14022
+ [GROUP, 420],
14023
+ ["/usr", 493],
14024
+ ["/usr/local", 493]
14025
+ ];
13897
14026
  var encoder3 = new TextEncoder();
13898
14027
  var USERS_MODES = ["off", "report", "enforce"];
13899
14028
  function usersMode(mode = "off") {
@@ -14087,7 +14216,8 @@ var Launcher = class {
14087
14216
  {
14088
14217
  locks: webLocks(),
14089
14218
  busy: (path) => [...this.openFiles].some((nodes) => nodes.holds(path)),
14090
- owner: options.unlinkOwner
14219
+ owner: options.unlinkOwner,
14220
+ ...this.owners ? { dirOwner: { uid: 0, gid: 0, mode: 448 } } : {}
14091
14221
  }
14092
14222
  );
14093
14223
  this.fs = this.unlinked.wrap(keepingOpen(fs, this.nodes));
@@ -14247,15 +14377,33 @@ var Launcher = class {
14247
14377
  return void 0;
14248
14378
  }
14249
14379
  const inner = await this.interpreted(interp, [interp, ...passed], cwd, scope, depth + 1);
14250
- return inner && { ...inner, target: withScriptEnv(inner.target, command) };
14380
+ const via = [interp, ...inner?.via ?? []];
14381
+ return inner && { ...inner, target: withScriptEnv(inner.target, command), via };
14251
14382
  }
14252
14383
  const target = withScriptEnv(found, command);
14253
- return { target, args: [...target.prefix ?? [], ...passed] };
14384
+ return { target, args: [...target.prefix ?? [], ...passed], via: [interp] };
14254
14385
  }
14255
14386
  async plan(file, argv, cwd, scope) {
14256
14387
  const direct = await this.resolve(file, argv[0] ?? file, cwd, scope);
14257
14388
  if (direct) return { target: direct, args: [...direct.prefix ?? [], ...argv.slice(1)] };
14258
- return this.interpreted(file, argv, cwd, scope);
14389
+ const script = await this.interpreted(file, argv, cwd, scope);
14390
+ return script && { ...script, script: true };
14391
+ }
14392
+ async mayExec(cred, req, planned) {
14393
+ const { dac } = this;
14394
+ if (!dac) return;
14395
+ const g = { fs: this.fs, cred, pid: req.pid, dac };
14396
+ const byPath = (file) => file.includes("/") && !COMMAND2.test(file);
14397
+ if (byPath(req.file)) {
14398
+ const path = this.fs.resolvePath(req.cwd, req.file);
14399
+ await need(g, "exec", path, planned?.script ? R_OK | X_OK : X_OK);
14400
+ }
14401
+ const via = planned?.via ?? [];
14402
+ for (const [i, file] of via.entries()) {
14403
+ if (!byPath(file)) continue;
14404
+ const want = i < via.length - 1 ? R_OK | X_OK : X_OK;
14405
+ await need(g, "exec", this.fs.resolvePath(req.cwd, file), want);
14406
+ }
14259
14407
  }
14260
14408
  async unrunnable(file, cwd, scope) {
14261
14409
  const path = this.fs.resolvePath(cwd, file);
@@ -14475,6 +14623,12 @@ var Launcher = class {
14475
14623
  return async (req, fds) => {
14476
14624
  const scope = await this.scopeOf(this.credOf(ppid).euid);
14477
14625
  const planned = await this.plan(req.file, req.argv, req.cwd, scope);
14626
+ try {
14627
+ await this.mayExec(this.credOf(ppid), { ...req, pid: ppid }, planned);
14628
+ } catch (err) {
14629
+ await fds.closeAll();
14630
+ throw err.code === "EACCES" ? new SpawnError("EACCES") : err;
14631
+ }
14478
14632
  if (!planned) {
14479
14633
  await fds.closeAll();
14480
14634
  throw new SpawnError(await this.unrunnable(req.file, req.cwd, scope));
@@ -14688,6 +14842,24 @@ var Launcher = class {
14688
14842
  await this.provisionPnpmHome();
14689
14843
  return entry;
14690
14844
  }
14845
+ async stampLayout() {
14846
+ const layout = [
14847
+ ...LAYOUT,
14848
+ [this.modulesDir, 493],
14849
+ [this.pnpmHome, 493],
14850
+ [`/${UNLINKED_DIR}`, 448]
14851
+ ];
14852
+ for (const [path, mode] of layout) {
14853
+ const real = await this.realLayoutPath(path);
14854
+ const fresh = real !== void 0 && !await this.base.hasOwner?.(real);
14855
+ if (fresh) await this.base.chown?.(real, { uid: 0, gid: 0, mode, follow: false });
14856
+ }
14857
+ }
14858
+ async realLayoutPath(path) {
14859
+ if (!await this.base.exists(path)) return void 0;
14860
+ if (path === "/") return path;
14861
+ return joinName(await realDir(this.base, parentOf(path)), baseName(path));
14862
+ }
14691
14863
  async provisionPnpmHome() {
14692
14864
  await this.base.mkdir(this.pnpmHome, { recursive: true }).catch(() => void 0);
14693
14865
  }
@@ -14758,8 +14930,10 @@ var Launcher = class {
14758
14930
  }
14759
14931
  async prepare() {
14760
14932
  for (const dir of SHARED_DIRS) await this.base.mkdir(dir, { recursive: true });
14933
+ if (this.owners) await this.base.chmod("/tmp", 1023);
14761
14934
  await this.provisionPnpmHome();
14762
14935
  await this.users.ensure();
14936
+ if (this.owners) await this.stampLayout();
14763
14937
  await writeCaFile(this.base, this.ca).catch(() => void 0);
14764
14938
  this.fstab = this.mountFstab();
14765
14939
  }
@@ -14805,7 +14979,9 @@ var Launcher = class {
14805
14979
  const cwd = await physicalPathAsync(this.base, start2.startsWith("/") ? start2 : `/${start2}`);
14806
14980
  const [file = ""] = argv;
14807
14981
  await this.base.mkdir(cwd, { recursive: true });
14808
- return { cwd, file, planned: await this.plan(file, argv, cwd, scope) };
14982
+ const planned = await this.plan(file, argv, cwd, scope);
14983
+ await this.mayExec(credOf(account), { file, cwd, pid: 0 }, planned);
14984
+ return { cwd, file, planned };
14809
14985
  }
14810
14986
  async openTerminal(argv, options) {
14811
14987
  const account = await this.account(options.user);