@ai-ecoverse/slicc-kernel 1.72.0 → 1.73.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.
@@ -361,7 +361,17 @@ async function chownAs(fs, cred, path, change) {
361
361
  }
362
362
 
363
363
  // src/fs/protect.ts
364
- var PROTECT_WRITER = "slicc-kernel: protect.writer needs the process tag (#405), which this kernel does not have yet";
364
+ function protectInit(protection) {
365
+ const writers = [...writerTags(protection)];
366
+ return { paths: [...protection.paths], ...writers.length > 0 ? { writers } : {} };
367
+ }
368
+ function writerTags(protection) {
369
+ const writers = protection?.writers ?? [];
370
+ if (!Array.isArray(writers) || writers.some((tag) => typeof tag !== "string" || tag === "")) {
371
+ throw new TypeError("protect.writers must be a list of process tags");
372
+ }
373
+ return new Set(writers);
374
+ }
365
375
  function protectedPrefixes(paths) {
366
376
  return [...new Set(paths.map((p) => normalizePath(p)))];
367
377
  }
@@ -873,6 +883,9 @@ var OpfsFs = class {
873
883
  protect(prefixes2) {
874
884
  this.guarded = prefixes2;
875
885
  }
886
+ unguarded() {
887
+ return Object.create(this, { guarded: { value: [] } });
888
+ }
876
889
  refuse(path, holding = false) {
877
890
  if (isProtected(this.guarded, path) || holding && holdsProtected(this.guarded, path)) {
878
891
  throw fsError("EROFS", path);
@@ -1042,15 +1055,15 @@ var OpfsFs = class {
1042
1055
  }
1043
1056
  dirTimes = /* @__PURE__ */ new Map();
1044
1057
  dirDirty = /* @__PURE__ */ new Set();
1045
- dirFlush;
1058
+ flushing = {};
1046
1059
  touch(...dirs) {
1047
1060
  const now = Date.now();
1048
1061
  for (const dir of dirs) {
1049
1062
  this.dirTimes.set(dir, now);
1050
1063
  this.dirDirty.add(dir);
1051
1064
  }
1052
- this.dirFlush ??= Promise.resolve().then(() => this.flushDirTimes()).catch(() => void 0).finally(() => {
1053
- this.dirFlush = void 0;
1065
+ this.flushing.dir ??= Promise.resolve().then(() => this.flushDirTimes()).catch(() => void 0).finally(() => {
1066
+ this.flushing.dir = void 0;
1054
1067
  });
1055
1068
  }
1056
1069
  async flushDirTimes() {
@@ -3821,6 +3834,12 @@ var CLIENT_CHECKS = {
3821
3834
  readlink: (g, at) => searchable(g, "readlink", at(0), false),
3822
3835
  exists: (g, at) => searchable(g, "exists", at(0), true)
3823
3836
  };
3837
+ function clientFs(l, tag, self, req) {
3838
+ if (!FS_METHODS.includes(req.method)) {
3839
+ throw new Error(`unknown file system call ${req.method}`);
3840
+ }
3841
+ return fsCall(l.fsFor(tag), req.method, req.args, ownerFor(l, self), guardFor(l, self, tag));
3842
+ }
3824
3843
  async function fsCall(fs, method, args, owner, g) {
3825
3844
  const path = (i) => fs.resolvePath("/", String(args[i]));
3826
3845
  if (g) {
@@ -4108,10 +4127,7 @@ function serveClient(port, host) {
4108
4127
  case "ps":
4109
4128
  return { result: processes(l, self) };
4110
4129
  case "fs":
4111
- if (!FS_METHODS.includes(req.method)) {
4112
- throw new Error(`unknown file system call ${req.method}`);
4113
- }
4114
- return fsCall(l.processFs, req.method, req.args, ownerFor(l, self), guard2(l));
4130
+ return clientFs(l, host.tag, self, req);
4115
4131
  case "watch":
4116
4132
  case "unwatch":
4117
4133
  return watchOp(l, req, watches, send, guard2(l));
@@ -15880,6 +15896,10 @@ var Launcher = class {
15880
15896
  fs;
15881
15897
  processFs;
15882
15898
  prefixes;
15899
+ writerTags;
15900
+ unguardedRoot;
15901
+ writers;
15902
+ unlinkOwnerOption;
15883
15903
  base;
15884
15904
  createWorker;
15885
15905
  modulesDir;
@@ -15997,7 +16017,7 @@ var Launcher = class {
15997
16017
  this.nodes = new VfsNodes(fs, (path) => this.released(path));
15998
16018
  this.nodes.keepsOnDisk = true;
15999
16019
  this.openFiles.add(this.nodes);
16000
- const holders = [this.held, [this.nodes]];
16020
+ const holders = [this.held, this.openFiles];
16001
16021
  this.unlinked = new UnlinkedKeeper(
16002
16022
  fs,
16003
16023
  { [Symbol.iterator]: () => holders.flatMap((set2) => [...set2]).values() },
@@ -16010,7 +16030,12 @@ var Launcher = class {
16010
16030
  }
16011
16031
  );
16012
16032
  this.fs = this.unlinked.wrap(keepingOpen(fs, this.nodes));
16013
- if (options.protect?.writer) throw new Error(PROTECT_WRITER);
16033
+ this.writerTags = writerTags(options.protect);
16034
+ this.unlinkOwnerOption = options.unlinkOwner;
16035
+ if (this.writerTags.size > 0 && options.fs.protect && !options.fs.unguarded) {
16036
+ throw new TypeError("protect.writers needs a root file system with an unguarded view");
16037
+ }
16038
+ this.unguardedRoot = options.fs.unguarded?.() ?? options.fs;
16014
16039
  this.prefixes = protectedPrefixes(options.protect?.paths ?? []);
16015
16040
  this.processFs = this.prefixes.length > 0 ? new ProtectedFs(this.prefixes).view(this.fs) : this.fs;
16016
16041
  options.fs.protect?.(this.prefixes);
@@ -16334,6 +16359,7 @@ var Launcher = class {
16334
16359
  throw fsError("ESRCH", `no process group ${req.pgid}`);
16335
16360
  }
16336
16361
  const pid = this.nextPid++;
16362
+ const stack = this.stackFor(req.tag);
16337
16363
  const terminal = req.fds.stdioTerminal();
16338
16364
  const decided = req.decided ?? Promise.resolve(true);
16339
16365
  const count = (byParent) => {
@@ -16364,8 +16390,8 @@ var Launcher = class {
16364
16390
  env: req.env,
16365
16391
  cwd: req.cwd,
16366
16392
  fds: req.fds,
16367
- fs: this.processFs,
16368
- ...this.prefixes.length > 0 ? { readonlyAt: (path) => isProtected(this.prefixes, path) } : {},
16393
+ fs: stack.fs,
16394
+ ...this.prefixes.length > 0 && !stack.writer ? { readonlyAt: (path) => isProtected(this.prefixes, path) } : {},
16369
16395
  createWorker: this.createWorker,
16370
16396
  onError: req.report,
16371
16397
  spawner: this.spawner(pid, req.report),
@@ -16380,9 +16406,9 @@ var Launcher = class {
16380
16406
  }),
16381
16407
  sudo: (call, signal) => this.sudo(pid, call, signal),
16382
16408
  openFiles: this.openFiles,
16383
- nodes: this.nodes,
16409
+ nodes: stack.nodes,
16384
16410
  held: this.held,
16385
- unlinkGate: this.unlinked.gate,
16411
+ unlinkGate: stack.unlinked.gate,
16386
16412
  statfs: (path) => this.mounts.statfs(path),
16387
16413
  mounts: () => this.mounts.list(),
16388
16414
  mount: (call, signal) => this.processMount(pid, call, signal),
@@ -16683,6 +16709,7 @@ var Launcher = class {
16683
16709
  released(path) {
16684
16710
  const at = normalizePath(path);
16685
16711
  this.unlinked.closed(at);
16712
+ this.writers?.unlinked.closed(at);
16686
16713
  const open = [...this.openFiles].some((nodes) => nodes.writes(at)) || heldUnder(this.held, at);
16687
16714
  if (!open) void this.mounts.commit(at).catch(() => void 0);
16688
16715
  }
@@ -17001,13 +17028,49 @@ var Launcher = class {
17001
17028
  ...account.umask !== void 0 ? { umask: account.umask } : {}
17002
17029
  };
17003
17030
  }
17031
+ fsFor(tag) {
17032
+ return this.stackFor(tag).fs;
17033
+ }
17034
+ isWriter(tag) {
17035
+ return tag !== void 0 && this.prefixes.length > 0 && this.writerTags.has(tag);
17036
+ }
17037
+ stackFor(tag) {
17038
+ if (!this.isWriter(tag)) {
17039
+ return { fs: this.processFs, nodes: this.nodes, unlinked: this.unlinked, writer: false };
17040
+ }
17041
+ this.writers ??= this.writerStack();
17042
+ return this.writers;
17043
+ }
17044
+ writerStack() {
17045
+ const fs = withCommandDirs(
17046
+ this.watchers.wrap(this.mounts.wrap(this.unguardedRoot)),
17047
+ async () => new Set((await this.commands()).keys()),
17048
+ (dir) => this.userCommandDir(dir),
17049
+ this.owners
17050
+ );
17051
+ const nodes = new VfsNodes(fs, (path) => this.released(path));
17052
+ nodes.keepsOnDisk = true;
17053
+ this.openFiles.add(nodes);
17054
+ const unlinked = new UnlinkedKeeper(
17055
+ fs,
17056
+ { [Symbol.iterator]: () => [this.held, this.openFiles].flatMap((set2) => [...set2]).values() },
17057
+ (path) => this.mountRoot(path),
17058
+ {
17059
+ locks: webLocks(),
17060
+ busy: (path) => [...this.openFiles].some((open) => open.holds(path)),
17061
+ owner: this.unlinkOwnerOption,
17062
+ ...this.owners ? { dirOwner: { uid: 0, gid: 0, mode: 448 } } : {}
17063
+ }
17064
+ );
17065
+ return { fs: unlinked.wrap(keepingOpen(fs, nodes)), nodes, unlinked, writer: true };
17066
+ }
17004
17067
  async starting(argv, dir, account, tag) {
17005
17068
  this.resetCatalogs();
17006
17069
  const scope = account.uid === 0 ? "" : pnpmHomeOf(account, this.pnpmHome);
17007
17070
  const start2 = dir ?? "/";
17008
17071
  const cwd = await physicalPathAsync(this.base, start2.startsWith("/") ? start2 : `/${start2}`);
17009
17072
  const [file = ""] = argv;
17010
- if (!await this.base.exists(cwd)) await this.processFs.mkdir(cwd, { recursive: true });
17073
+ if (!await this.base.exists(cwd)) await this.stackFor(tag).fs.mkdir(cwd, { recursive: true });
17011
17074
  const planned = await this.plan(file, argv, cwd, scope);
17012
17075
  await this.mayExec(credOf(account), { file, cwd, pid: 0, tag }, planned);
17013
17076
  return { cwd, file, planned };
@@ -17270,7 +17333,7 @@ function hostOptions(req, port, sealed) {
17270
17333
  ...req.hostname !== void 0 ? { hostname: req.hostname } : {},
17271
17334
  ...req.users !== void 0 ? { users: req.users } : {},
17272
17335
  ...req.denials ? { onDenial: (denial) => port.postMessage({ denial }) } : {},
17273
- ...req.protect ? { protect: { paths: [...req.protect.paths] } } : {},
17336
+ ...req.protect ? { protect: protectInit(req.protect) } : {},
17274
17337
  ...req.sealed ? { sealed } : {}
17275
17338
  };
17276
17339
  }