@ai-ecoverse/slicc-kernel 1.72.0 → 1.73.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/node.js CHANGED
@@ -2246,6 +2246,9 @@ var UserDb = class {
2246
2246
  const pnpm = `${home}/.local/share/pnpm`;
2247
2247
  if (await this.realDir(pnpm)) await this.fs.chmod(pnpm, scoop ? 488 : 448);
2248
2248
  }
2249
+ umaskFor(options) {
2250
+ return (options.umask ?? this.umasks.get(options.name) ?? 18) & 511;
2251
+ }
2249
2252
  async realDir(path) {
2250
2253
  const parts2 = path.split("/").filter(Boolean);
2251
2254
  for (let n = 1; n <= parts2.length; n++) {
@@ -2269,9 +2272,11 @@ var UserDb = class {
2269
2272
  group: parseGroup(await this.read(GROUP, DEFAULT_GROUP))
2270
2273
  };
2271
2274
  }
2272
- async provision(row) {
2273
- const owner = this.owners ? { uid: row.uid, gid: row.gid } : void 0;
2274
- const made2 = owner ? { recursive: true, owner } : { recursive: true };
2275
+ async provision(row, umask, shared) {
2276
+ const dirs = 511 & ~umask | (shared ? 1024 : 0);
2277
+ const gid = shared?.gid ?? row.gid;
2278
+ const owner = this.owners ? { uid: row.uid, gid } : void 0;
2279
+ const made2 = owner ? { recursive: true, owner: { ...owner, mode: dirs, parents: dirs } } : { recursive: true };
2275
2280
  const pnpm = `${row.home}/.local/share/pnpm`;
2276
2281
  if (!await this.fs.exists(pnpm)) {
2277
2282
  await this.fs.mkdir(pnpm, made2);
@@ -2280,7 +2285,7 @@ var UserDb = class {
2280
2285
  const config = `${row.home}/.config/pnpm/config.yaml`;
2281
2286
  if (!await this.fs.exists(config)) {
2282
2287
  await this.fs.mkdir(`${row.home}/.config/pnpm`, made2);
2283
- await this.fs.writeFile(config, PNPM_CONFIG, owner);
2288
+ await this.fs.writeFile(config, PNPM_CONFIG, owner && { ...owner, mode: 438 & ~umask });
2284
2289
  }
2285
2290
  }
2286
2291
  async commit(passwd, group, then) {
@@ -2371,7 +2376,7 @@ var UserDb = class {
2371
2376
  throw fsError("EINVAL", `a user is no member of its own group ${name2}`);
2372
2377
  }
2373
2378
  if (found.uid >= FIRST_UID && found.uid <= LAST_UID) {
2374
- await this.provision(found);
2379
+ await this.provision(found, this.umaskFor(options), shared);
2375
2380
  await this.stampHome(found, shared, homeMode);
2376
2381
  }
2377
2382
  if (joined(name2, more)) await this.fs.writeFile(GROUP, formatGroup(group));
@@ -2389,7 +2394,7 @@ var UserDb = class {
2389
2394
  const fresh2 = !await this.fs.exists(home);
2390
2395
  await this.fs.mkdir(home, { recursive: true });
2391
2396
  try {
2392
- await this.provision(row);
2397
+ await this.provision(row, this.umaskFor(options), shared);
2393
2398
  await this.stampHome(row, shared, homeMode);
2394
2399
  await this.fs.mkdir("/etc", { recursive: true });
2395
2400
  await this.commit(formatPasswd(passwd), formatGroup(group));
@@ -2525,6 +2530,12 @@ var CLIENT_CHECKS = {
2525
2530
  readlink: (g, at) => searchable(g, "readlink", at(0), false),
2526
2531
  exists: (g, at) => searchable(g, "exists", at(0), true)
2527
2532
  };
2533
+ function clientFs(l, tag, self, req) {
2534
+ if (!FS_METHODS.includes(req.method)) {
2535
+ throw new Error(`unknown file system call ${req.method}`);
2536
+ }
2537
+ return fsCall(l.fsFor(tag), req.method, req.args, ownerFor(l, self), guardFor(l, self, tag));
2538
+ }
2528
2539
  async function fsCall(fs, method, args, owner, g) {
2529
2540
  const path = (i) => fs.resolvePath("/", String(args[i]));
2530
2541
  if (g) {
@@ -2812,10 +2823,7 @@ function serveClient(port, host) {
2812
2823
  case "ps":
2813
2824
  return { result: processes(l, self) };
2814
2825
  case "fs":
2815
- if (!FS_METHODS.includes(req.method)) {
2816
- throw new Error(`unknown file system call ${req.method}`);
2817
- }
2818
- return fsCall(l.processFs, req.method, req.args, ownerFor(l, self), guard2(l));
2826
+ return clientFs(l, host.tag, self, req);
2819
2827
  case "watch":
2820
2828
  case "unwatch":
2821
2829
  return watchOp(l, req, watches, send, guard2(l));
@@ -3003,7 +3011,13 @@ var MemoryMeta = class {
3003
3011
  };
3004
3012
 
3005
3013
  // src/fs/protect.ts
3006
- var PROTECT_WRITER = "slicc-kernel: protect.writer needs the process tag (#405), which this kernel does not have yet";
3014
+ function writerTags(protection) {
3015
+ const writers = protection?.writers ?? [];
3016
+ if (!Array.isArray(writers) || writers.some((tag) => typeof tag !== "string" || tag === "")) {
3017
+ throw new TypeError("protect.writers must be a list of process tags");
3018
+ }
3019
+ return new Set(writers);
3020
+ }
3007
3021
  function protectedPrefixes(paths) {
3008
3022
  return [...new Set(paths.map((p) => normalizePath(p)))];
3009
3023
  }
@@ -3511,6 +3525,9 @@ var OpfsFs = class {
3511
3525
  protect(prefixes2) {
3512
3526
  this.guarded = prefixes2;
3513
3527
  }
3528
+ unguarded() {
3529
+ return Object.create(this, { guarded: { value: [] } });
3530
+ }
3514
3531
  refuse(path, holding = false) {
3515
3532
  if (isProtected(this.guarded, path) || holding && holdsProtected(this.guarded, path)) {
3516
3533
  throw fsError("EROFS", path);
@@ -3680,15 +3697,15 @@ var OpfsFs = class {
3680
3697
  }
3681
3698
  dirTimes = /* @__PURE__ */ new Map();
3682
3699
  dirDirty = /* @__PURE__ */ new Set();
3683
- dirFlush;
3700
+ flushing = {};
3684
3701
  touch(...dirs) {
3685
3702
  const now = Date.now();
3686
3703
  for (const dir of dirs) {
3687
3704
  this.dirTimes.set(dir, now);
3688
3705
  this.dirDirty.add(dir);
3689
3706
  }
3690
- this.dirFlush ??= Promise.resolve().then(() => this.flushDirTimes()).catch(() => void 0).finally(() => {
3691
- this.dirFlush = void 0;
3707
+ this.flushing.dir ??= Promise.resolve().then(() => this.flushDirTimes()).catch(() => void 0).finally(() => {
3708
+ this.flushing.dir = void 0;
3692
3709
  });
3693
3710
  }
3694
3711
  async flushDirTimes() {
@@ -3932,7 +3949,7 @@ var OpfsFs = class {
3932
3949
  const above = owner && {
3933
3950
  uid: owner.uid,
3934
3951
  gid: owner.gid,
3935
- ...mode ? { mode, parents: mode } : {}
3952
+ ...mode !== void 0 ? { mode, parents: mode } : {}
3936
3953
  };
3937
3954
  await this.mkdir(parentOf4(path), { recursive: true, ...above ? { owner: above } : {} });
3938
3955
  }
@@ -6236,6 +6253,11 @@ var HeldPaths = class extends Map {
6236
6253
  keptFor(handle) {
6237
6254
  return this.keptHandles.get(handle);
6238
6255
  }
6256
+ keeps(hidden) {
6257
+ for (const file of this.uses.keys()) if (file.hidden === hidden) return true;
6258
+ for (const file of this.unlinked.values()) if (file.hidden === hidden) return true;
6259
+ return false;
6260
+ }
6239
6261
  keep(path, file) {
6240
6262
  const fresh2 = this.handlesAt(path).filter((id2) => !this.keptHandles.has(id2));
6241
6263
  for (const id2 of fresh2) this.keptHandles.set(id2, file);
@@ -7337,6 +7359,7 @@ function guardOf(entry) {
7337
7359
  return { fs: entry.fs, cred, pid: entry.pid ?? 0, dac: entry.dac };
7338
7360
  }
7339
7361
  var FOLLOWING = /* @__PURE__ */ new Set(["exists", "stat", "chown", "statfs"]);
7362
+ var LOOKS = /* @__PURE__ */ new Set(["exists", "stat", "lstat"]);
7340
7363
  var NOT_FOLLOWING = /* @__PURE__ */ new Set(["lstat", "readlink", "lchown"]);
7341
7364
  async function guard(g, path, req, cwd) {
7342
7365
  const { op } = req;
@@ -7406,6 +7429,7 @@ async function accessOp(entry, path, req) {
7406
7429
  async function guarded(entry, path, req) {
7407
7430
  const g = guardOf(entry);
7408
7431
  if (!g) return void 0;
7432
+ if (LOOKS.has(req.op) && await entry.holdsKept?.(path)) return void 0;
7409
7433
  try {
7410
7434
  await guard(g, path, req, entry.cwd);
7411
7435
  return void 0;
@@ -12297,6 +12321,13 @@ var WasmProcess = class {
12297
12321
  }
12298
12322
  }
12299
12323
  keptPath;
12324
+ async holds(path) {
12325
+ for (const fd of this.fds.numbers()) {
12326
+ const { file } = this.fds.get(fd);
12327
+ if ((await file.stat?.().catch(() => void 0))?.path === path) return true;
12328
+ }
12329
+ return false;
12330
+ }
12300
12331
  keptHandle;
12301
12332
  async keptOr(path, handle) {
12302
12333
  const held = handle === void 0 ? void 0 : this.keptHandle?.(handle);
@@ -12940,7 +12971,8 @@ function spawnWasmProcess(opts) {
12940
12971
  const token = holdingToken(opts, holds, {
12941
12972
  cred: () => process2.cred,
12942
12973
  umask: () => process2.umask,
12943
- created: created3
12974
+ created: created3,
12975
+ holdsKept: async (path) => path.includes(`/${UNLINKED_DIR}/`) && (holds.keeps(path) || await process2.holds(path))
12944
12976
  });
12945
12977
  process2.keptPath = (path) => holds.keptAt(path)?.hidden;
12946
12978
  process2.keptHandle = (handle2) => holds.keptFor(handle2)?.hidden;
@@ -15405,9 +15437,14 @@ var Sudo = class {
15405
15437
  if (walked.denied !== void 0 || walked.missing) return false;
15406
15438
  return (await this.o.fs.stat(cwd).catch(() => void 0))?.isDirectory === true;
15407
15439
  }
15408
- async untrusted(path, cwd) {
15409
- const files = await this.o.backing?.(path, cwd) ?? [];
15410
- for (const file of [path, ...files]) if (!await this.trusted(file)) return file;
15440
+ async untrusted(path, cwd, word) {
15441
+ const files = this.o.backing ? await this.o.backing(path, cwd) : [];
15442
+ if (!files) return failed2("ENOENT", `sudo: ${word}: command not found`);
15443
+ for (const file of [path, ...files]) {
15444
+ if (!await this.trusted(file)) {
15445
+ return failed2("EPERM", `sudo: ${file} must be root's, and writable by root only`);
15446
+ }
15447
+ }
15411
15448
  return void 0;
15412
15449
  }
15413
15450
  async locate(word, cwd) {
@@ -15448,10 +15485,8 @@ var Sudo = class {
15448
15485
  }
15449
15486
  const path = await this.locate(word, cwd);
15450
15487
  if (!path) return failed2("ENOENT", `sudo: ${word}: command not found`);
15451
- const untrusted = await this.untrusted(path, cwd);
15452
- if (untrusted) {
15453
- return failed2("EPERM", `sudo: ${untrusted} must be root's, and writable by root only`);
15454
- }
15488
+ const untrusted = await this.untrusted(path, cwd, word);
15489
+ if (untrusted) return untrusted;
15455
15490
  const argv = [path, ...args];
15456
15491
  const matching = rules.filter(
15457
15492
  (rule) => applies(rule) && (rule.target === "ALL" || rule.target === target.name) && (rule.cmnd === "*" || rule.cmnd.length === argv.length && rule.cmnd.every((w, i) => w === argv[i]))
@@ -15474,10 +15509,8 @@ var Sudo = class {
15474
15509
  if (await this.canonical(call.cwd) !== cwd || !await this.reachable(cred, cwd)) {
15475
15510
  return failed2("EACCES", `sudo: ${call.cwd} changed while the request waited`);
15476
15511
  }
15477
- const changed = await this.untrusted(path, cwd);
15478
- if (changed) {
15479
- return failed2("EPERM", `sudo: ${changed} must be root's, and writable by root only`);
15480
- }
15512
+ const changed = await this.untrusted(path, cwd, word);
15513
+ if (changed) return changed;
15481
15514
  if (answer === "always" && !await this.remember(self, target, argv)) {
15482
15515
  return failed2("EIO", "sudo: the rule could not be recorded");
15483
15516
  }
@@ -15751,6 +15784,10 @@ var Launcher = class {
15751
15784
  fs;
15752
15785
  processFs;
15753
15786
  prefixes;
15787
+ writerTags;
15788
+ unguardedRoot;
15789
+ writers;
15790
+ unlinkOwnerOption;
15754
15791
  base;
15755
15792
  createWorker;
15756
15793
  modulesDir;
@@ -15868,7 +15905,7 @@ var Launcher = class {
15868
15905
  this.nodes = new VfsNodes(fs, (path) => this.released(path));
15869
15906
  this.nodes.keepsOnDisk = true;
15870
15907
  this.openFiles.add(this.nodes);
15871
- const holders = [this.held, [this.nodes]];
15908
+ const holders = [this.held, this.openFiles];
15872
15909
  this.unlinked = new UnlinkedKeeper(
15873
15910
  fs,
15874
15911
  { [Symbol.iterator]: () => holders.flatMap((set2) => [...set2]).values() },
@@ -15881,7 +15918,12 @@ var Launcher = class {
15881
15918
  }
15882
15919
  );
15883
15920
  this.fs = this.unlinked.wrap(keepingOpen(fs, this.nodes));
15884
- if (options.protect?.writer) throw new Error(PROTECT_WRITER);
15921
+ this.writerTags = writerTags(options.protect);
15922
+ this.unlinkOwnerOption = options.unlinkOwner;
15923
+ if (this.writerTags.size > 0 && options.fs.protect && !options.fs.unguarded) {
15924
+ throw new TypeError("protect.writers needs a root file system with an unguarded view");
15925
+ }
15926
+ this.unguardedRoot = options.fs.unguarded?.() ?? options.fs;
15885
15927
  this.prefixes = protectedPrefixes(options.protect?.paths ?? []);
15886
15928
  this.processFs = this.prefixes.length > 0 ? new ProtectedFs(this.prefixes).view(this.fs) : this.fs;
15887
15929
  options.fs.protect?.(this.prefixes);
@@ -16098,7 +16140,7 @@ var Launcher = class {
16098
16140
  }
16099
16141
  async backing(file, cwd) {
16100
16142
  const planned = await this.plan(file, [file], cwd, "");
16101
- if (!planned) return [];
16143
+ if (!planned) return void 0;
16102
16144
  const { glue, wasm, imports } = planned.target;
16103
16145
  const files = [glue, wasm, ...imports ? [imports] : []];
16104
16146
  const scripts = [];
@@ -16205,6 +16247,7 @@ var Launcher = class {
16205
16247
  throw fsError("ESRCH", `no process group ${req.pgid}`);
16206
16248
  }
16207
16249
  const pid = this.nextPid++;
16250
+ const stack = this.stackFor(req.tag);
16208
16251
  const terminal = req.fds.stdioTerminal();
16209
16252
  const decided = req.decided ?? Promise.resolve(true);
16210
16253
  const count = (byParent) => {
@@ -16235,8 +16278,8 @@ var Launcher = class {
16235
16278
  env: req.env,
16236
16279
  cwd: req.cwd,
16237
16280
  fds: req.fds,
16238
- fs: this.processFs,
16239
- ...this.prefixes.length > 0 ? { readonlyAt: (path) => isProtected(this.prefixes, path) } : {},
16281
+ fs: stack.fs,
16282
+ ...this.prefixes.length > 0 && !stack.writer ? { readonlyAt: (path) => isProtected(this.prefixes, path) } : {},
16240
16283
  createWorker: this.createWorker,
16241
16284
  onError: req.report,
16242
16285
  spawner: this.spawner(pid, req.report),
@@ -16251,9 +16294,9 @@ var Launcher = class {
16251
16294
  }),
16252
16295
  sudo: (call, signal) => this.sudo(pid, call, signal),
16253
16296
  openFiles: this.openFiles,
16254
- nodes: this.nodes,
16297
+ nodes: stack.nodes,
16255
16298
  held: this.held,
16256
- unlinkGate: this.unlinked.gate,
16299
+ unlinkGate: stack.unlinked.gate,
16257
16300
  statfs: (path) => this.mounts.statfs(path),
16258
16301
  mounts: () => this.mounts.list(),
16259
16302
  mount: (call, signal) => this.processMount(pid, call, signal),
@@ -16554,6 +16597,7 @@ var Launcher = class {
16554
16597
  released(path) {
16555
16598
  const at = normalizePath(path);
16556
16599
  this.unlinked.closed(at);
16600
+ this.writers?.unlinked.closed(at);
16557
16601
  const open = [...this.openFiles].some((nodes) => nodes.writes(at)) || heldUnder(this.held, at);
16558
16602
  if (!open) void this.mounts.commit(at).catch(() => void 0);
16559
16603
  }
@@ -16704,6 +16748,27 @@ var Launcher = class {
16704
16748
  await this.provisionPnpmHome();
16705
16749
  return entry;
16706
16750
  }
16751
+ async ownTmp() {
16752
+ const real = await this.realLayoutPath("/tmp");
16753
+ const st = real === void 0 ? void 0 : await this.base.lstat(real).catch(() => void 0);
16754
+ if (real !== void 0 && st?.isDirectory && this.base.hasOwner && !await this.base.hasOwner(real)) {
16755
+ let removed = 0;
16756
+ for (const name2 of await this.base.readdir(real).catch(() => [])) {
16757
+ const path = joinName(real, name2);
16758
+ if (await this.base.hasOwner(path) || this.inUse(path)) continue;
16759
+ const failed3 = await this.base.rm(path, { recursive: true, force: true }).catch(String);
16760
+ if (failed3 === void 0) removed++;
16761
+ }
16762
+ if (removed > 0) {
16763
+ console.warn(`slicc-kernel users: removed ${removed} entries from /tmp that had no owner`);
16764
+ }
16765
+ await this.base.chown?.(real, { uid: 0, gid: 0, mode: 1023, follow: false });
16766
+ }
16767
+ if (st?.isDirectory) await this.base.chmod(real, 1023);
16768
+ }
16769
+ inUse(path) {
16770
+ return [...this.openFiles].some((nodes) => nodes.holds(path)) || heldUnder(this.held, path);
16771
+ }
16707
16772
  async stampLayout() {
16708
16773
  const layout = [
16709
16774
  ...LAYOUT,
@@ -16823,7 +16888,7 @@ var Launcher = class {
16823
16888
  async prepare() {
16824
16889
  await this.resolvePrefixes();
16825
16890
  for (const dir of SHARED_DIRS) await this.base.mkdir(dir, { recursive: true });
16826
- if (this.owners) await this.base.chmod("/tmp", 1023);
16891
+ if (this.owners) await this.ownTmp();
16827
16892
  await this.provisionPnpmHome();
16828
16893
  await this.users.ensure();
16829
16894
  if (this.owners) await this.stampLayout();
@@ -16872,13 +16937,49 @@ var Launcher = class {
16872
16937
  ...account.umask !== void 0 ? { umask: account.umask } : {}
16873
16938
  };
16874
16939
  }
16940
+ fsFor(tag) {
16941
+ return this.stackFor(tag).fs;
16942
+ }
16943
+ isWriter(tag) {
16944
+ return tag !== void 0 && this.prefixes.length > 0 && this.writerTags.has(tag);
16945
+ }
16946
+ stackFor(tag) {
16947
+ if (!this.isWriter(tag)) {
16948
+ return { fs: this.processFs, nodes: this.nodes, unlinked: this.unlinked, writer: false };
16949
+ }
16950
+ this.writers ??= this.writerStack();
16951
+ return this.writers;
16952
+ }
16953
+ writerStack() {
16954
+ const fs = withCommandDirs(
16955
+ this.watchers.wrap(this.mounts.wrap(this.unguardedRoot)),
16956
+ async () => new Set((await this.commands()).keys()),
16957
+ (dir) => this.userCommandDir(dir),
16958
+ this.owners
16959
+ );
16960
+ const nodes = new VfsNodes(fs, (path) => this.released(path));
16961
+ nodes.keepsOnDisk = true;
16962
+ this.openFiles.add(nodes);
16963
+ const unlinked = new UnlinkedKeeper(
16964
+ fs,
16965
+ { [Symbol.iterator]: () => [this.held, this.openFiles].flatMap((set2) => [...set2]).values() },
16966
+ (path) => this.mountRoot(path),
16967
+ {
16968
+ locks: webLocks(),
16969
+ busy: (path) => [...this.openFiles].some((open) => open.holds(path)),
16970
+ owner: this.unlinkOwnerOption,
16971
+ ...this.owners ? { dirOwner: { uid: 0, gid: 0, mode: 448 } } : {}
16972
+ }
16973
+ );
16974
+ return { fs: unlinked.wrap(keepingOpen(fs, nodes)), nodes, unlinked, writer: true };
16975
+ }
16875
16976
  async starting(argv, dir, account, tag) {
16876
16977
  this.resetCatalogs();
16877
16978
  const scope = account.uid === 0 ? "" : pnpmHomeOf(account, this.pnpmHome);
16878
16979
  const start2 = dir ?? "/";
16879
16980
  const cwd = await physicalPathAsync(this.base, start2.startsWith("/") ? start2 : `/${start2}`);
16880
16981
  const [file = ""] = argv;
16881
- if (!await this.base.exists(cwd)) await this.processFs.mkdir(cwd, { recursive: true });
16982
+ if (!await this.base.exists(cwd)) await this.stackFor(tag).fs.mkdir(cwd, { recursive: true });
16882
16983
  const planned = await this.plan(file, argv, cwd, scope);
16883
16984
  await this.mayExec(credOf(account), { file, cwd, pid: 0, tag }, planned);
16884
16985
  return { cwd, file, planned };