@ai-ecoverse/slicc-kernel 1.56.2 → 1.57.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.
@@ -216,11 +216,69 @@ function inodeOf(path) {
216
216
  return 4294967296 * (2097151 & h2) + (h1 >>> 0) || 1;
217
217
  }
218
218
 
219
+ // src/fs/owners.ts
220
+ var ROOT_OWNER = Object.freeze({ uid: 0, gid: 0 });
221
+ var S_ISUID = 2048;
222
+ var S_ISGID = 1024;
223
+ var S_IXGRP = 8;
224
+ function ownOf(entry) {
225
+ if (entry?.uid === void 0 || entry.gid === void 0) return void 0;
226
+ return { uid: entry.uid, gid: entry.gid };
227
+ }
228
+ function handedBy(entry) {
229
+ return entry?.heir ? { uid: entry.heir[0], gid: entry.heir[1] } : ownOf(entry);
230
+ }
231
+ function inherit(entries) {
232
+ for (let i = entries.length - 1; i >= 0; i--) {
233
+ const found = handedBy(entries[i]);
234
+ if (found) return found;
235
+ }
236
+ return { ...ROOT_OWNER };
237
+ }
238
+ function chain(path) {
239
+ const parts = path.split("/").filter(Boolean);
240
+ return ["/", ...parts.map((_, i) => `/${parts.slice(0, i + 1).join("/")}`)];
241
+ }
242
+ function sameOwner(a, b) {
243
+ return a.uid === b.uid && a.gid === b.gid;
244
+ }
245
+ function ownerOf(cred, mode) {
246
+ return { uid: cred.euid, gid: cred.egid, ...mode !== void 0 ? { mode } : {} };
247
+ }
248
+ function inGroup(cred, gid) {
249
+ return cred.egid === gid || cred.groups.includes(gid);
250
+ }
251
+ function mayChown(cred, current, uid, gid) {
252
+ if (cred.euid === 0) return true;
253
+ const owner = cred.euid === current.uid;
254
+ if (uid !== void 0 && !(owner && uid === current.uid)) return false;
255
+ if (gid !== void 0 && !(owner && (gid === current.gid || inGroup(cred, gid)))) return false;
256
+ return true;
257
+ }
258
+ function clearedSetId(mode) {
259
+ const bits2 = S_ISUID | (mode & S_IXGRP ? S_ISGID : 0);
260
+ return mode & 4095 & ~bits2;
261
+ }
262
+ async function chownAs(fs, cred, path, change) {
263
+ const { uid, gid, follow: follow2 } = change;
264
+ const st = follow2 ? await fs.stat(path) : await fs.lstat(path);
265
+ const current = { uid: st.uid ?? 0, gid: st.gid ?? 0 };
266
+ if (!fs.chown || !mayChown(cred, current, uid, gid)) throw fsError("EPERM", path);
267
+ const clear = st.isFile && (uid !== void 0 || gid !== void 0) ? clearedSetId(st.mode) : void 0;
268
+ await fs.chown(path, {
269
+ ...uid !== void 0 ? { uid } : {},
270
+ ...gid !== void 0 ? { gid } : {},
271
+ follow: follow2,
272
+ ...clear !== void 0 && clear !== (st.mode & 4095) ? { mode: clear } : {}
273
+ });
274
+ }
275
+
219
276
  // src/fs/opfs.ts
220
277
  var S_IFDIR = 16384;
221
278
  var S_IFREG = 32768;
222
279
  var S_IFLNK = 40960;
223
280
  var PERM_MASK = 4095;
281
+ var S_ISGID2 = 1024;
224
282
  var READ_ATTEMPTS = 3;
225
283
  var MAX_LINKS = 40;
226
284
  var CHECK_MS = 1e3;
@@ -322,6 +380,7 @@ var OpfsFs = class {
322
380
  checked = /* @__PURE__ */ new WeakMap();
323
381
  channel;
324
382
  ranged;
383
+ owners = false;
325
384
  constructor(root, meta = new MemoryMeta(), channel, options = {}) {
326
385
  this.root = root;
327
386
  this.meta = meta;
@@ -341,6 +400,73 @@ var OpfsFs = class {
341
400
  resolvePath(base, path) {
342
401
  return resolvePath(base, path);
343
402
  }
403
+ trackOwners() {
404
+ this.owners = true;
405
+ }
406
+ async handedTo(dir) {
407
+ return inherit(await this.meta.get(chain(dir)));
408
+ }
409
+ async ownerAt(path, entry) {
410
+ const own = ownOf(entry);
411
+ if (own) return own;
412
+ return path === "/" ? { ...ROOT_OWNER } : this.handedTo(parentOf2(path));
413
+ }
414
+ async owned(stat, path, entry) {
415
+ return this.owners ? { ...stat, ...await this.ownerAt(path, entry) } : stat;
416
+ }
417
+ async newFields(path, owner, directory) {
418
+ if (!this.owners || !owner) return void 0;
419
+ const entries = await this.meta.get(chain(path));
420
+ const self = entries.pop();
421
+ const parent = entries.at(-1);
422
+ const setgid = ((parent?.mode ?? 0) & S_ISGID2) !== 0;
423
+ const parentGid = (ownOf(parent) ?? inherit(entries.slice(0, -1))).gid;
424
+ const want = { uid: owner.uid, gid: setgid ? parentGid : owner.gid };
425
+ let mode = owner.mode === void 0 ? void 0 : owner.mode & PERM_MASK;
426
+ if (directory && setgid) mode = (mode ?? 493) | S_ISGID2;
427
+ const fields = {};
428
+ if (mode !== void 0 && mode !== (this.mode(path, directory, void 0) & PERM_MASK)) {
429
+ fields.mode = mode;
430
+ }
431
+ if (self) fields.heir = void 0;
432
+ if (ownOf(self) || !sameOwner(want, inherit(entries))) Object.assign(fields, want);
433
+ return Object.keys(fields).length > 0 ? fields : void 0;
434
+ }
435
+ async stampNew(path, owner, directory) {
436
+ const fields = await this.newFields(path, owner, directory);
437
+ if (fields) await this.meta.update(path, (entry) => ({ ...entry, path, ...fields }));
438
+ }
439
+ async chown(path, change) {
440
+ if (!this.owners) return;
441
+ if (change.kind === "new") {
442
+ const at = resolvePath((await this.locate(parentOf2(path), true)).path, baseOf(path));
443
+ const { uid = 0, gid = 0, mode } = change;
444
+ return this.stampNew(at, { uid, gid, ...mode !== void 0 ? { mode } : {} }, false);
445
+ }
446
+ const found = await this.locate(path, change.follow !== false);
447
+ const [stored] = found.link ? [found.link] : await this.meta.get([found.path]);
448
+ const current = await this.ownerAt(found.path, stored);
449
+ const next2 = { uid: change.uid ?? current.uid, gid: change.gid ?? current.gid };
450
+ const handing = found.handle?.kind === "directory" && !sameOwner(next2, current);
451
+ const heir = change.kind === "adopt" ? void 0 : stored?.heir ?? (handing ? [current.uid, current.gid] : void 0);
452
+ const ctimeMs = Date.now();
453
+ await this.meta.update(found.path, (entry) => {
454
+ const { heir: _, ...rest2 } = entry ?? found.link ?? { path: found.path };
455
+ return {
456
+ ...rest2,
457
+ path: found.path,
458
+ ...next2,
459
+ ...heir ? { heir } : {},
460
+ ...change.mode !== void 0 ? { mode: change.mode & PERM_MASK } : {},
461
+ ctimeMs
462
+ };
463
+ });
464
+ }
465
+ async movedOwner(source) {
466
+ if (!this.owners) return void 0;
467
+ const [stored] = source.link ? [source.link] : await this.meta.get([source.path]);
468
+ return ownOf(stored) ? void 0 : this.ownerAt(source.path, stored);
469
+ }
344
470
  async walk(parts, from, start2) {
345
471
  let dir = start2;
346
472
  for (let at = from; at < parts.length; at++) {
@@ -533,9 +659,15 @@ var OpfsFs = class {
533
659
  };
534
660
  }
535
661
  async describe(found) {
536
- if (found.link) return this.linkStat(found.link);
537
- const [meta] = await this.meta.get([found.path]);
538
- return this.statOf(found.path, found.handle, meta);
662
+ if (found.link) return this.owned(this.linkStat(found.link), found.path, found.link);
663
+ if (!this.owners) {
664
+ const [meta2] = await this.meta.get([found.path]);
665
+ return this.statOf(found.path, found.handle, meta2);
666
+ }
667
+ const entries = await this.meta.get(chain(found.path));
668
+ const meta = entries.pop();
669
+ const stat = await this.statOf(found.path, found.handle, meta);
670
+ return { ...stat, ...ownOf(meta) ?? inherit(entries) };
539
671
  }
540
672
  async stat(path) {
541
673
  return this.describe(await this.locate(path, true));
@@ -625,14 +757,15 @@ var OpfsFs = class {
625
757
  (parent) => parent.getFileHandle(name2, { create: true })
626
758
  );
627
759
  if (!existed) this.touch(parentOf2(target));
628
- return [handle, existed];
760
+ return [handle, existed, target];
629
761
  } catch (err) {
630
762
  const code = err?.name === "TypeMismatchError" ? "EISDIR" : null;
631
763
  throw code ? fsError(code, path) : translate(err, path);
632
764
  }
633
765
  }
634
- async writeFile(path, content) {
635
- const [file, existed] = await this.fileFor(path);
766
+ async writeFile(path, content, owner) {
767
+ const [file, existed, target] = await this.fileFor(path);
768
+ if (!existed) await this.stampNew(target, owner, false);
636
769
  if (!existed && content.length === 0) return;
637
770
  const writable = await file.createWritable();
638
771
  await writable.write(content);
@@ -657,11 +790,13 @@ var OpfsFs = class {
657
790
  const [dir, children2] = await this.listing(path);
658
791
  const base = dir === "/" ? "" : dir;
659
792
  const metas = await this.meta.get(children2.map(([name2]) => `${base}/${name2}`));
793
+ const handed2 = this.owners ? await this.handedTo(dir) : void 0;
794
+ const owned = (stat, entry) => handed2 ? { ...stat, ...ownOf(entry) ?? handed2 } : stat;
660
795
  return Promise.all(
661
796
  children2.map(async ([name2, child], i) => {
662
- if (!isHandle(child)) return [name2, this.linkStat(child)];
797
+ if (!isHandle(child)) return [name2, owned(this.linkStat(child), child)];
663
798
  const stat = await this.statOf(`${base}/${name2}`, child, metas[i]).catch(() => null);
664
- return [name2, stat];
799
+ return [name2, stat && owned(stat, metas[i])];
665
800
  })
666
801
  );
667
802
  }
@@ -679,11 +814,22 @@ var OpfsFs = class {
679
814
  }
680
815
  const [entry] = await this.meta.get([path]);
681
816
  if (entry?.link !== void 0) throw fsError("EEXIST", path);
682
- if (options.recursive) await this.mkdir(parentOf2(path), options);
817
+ const { owner } = options;
818
+ if (options.recursive) {
819
+ const mode = owner?.parents;
820
+ const above = owner && {
821
+ uid: owner.uid,
822
+ gid: owner.gid,
823
+ ...mode ? { mode, parents: mode } : {}
824
+ };
825
+ await this.mkdir(parentOf2(path), { recursive: true, ...above ? { owner: above } : {} });
826
+ }
683
827
  const parent = await this.locate(parentOf2(path), true);
684
828
  if (parent.handle?.kind !== "directory") throw fsError("ENOTDIR", path);
685
829
  await parent.handle.getDirectoryHandle(baseOf(path), { create: true });
686
- this.touch(parent.path, `${parent.path === "/" ? "" : parent.path}/${baseOf(path)}`);
830
+ const made = `${parent.path === "/" ? "" : parent.path}/${baseOf(path)}`;
831
+ await this.stampNew(made, owner, true);
832
+ this.touch(parent.path, made);
687
833
  }
688
834
  async rm(path, options = {}) {
689
835
  let found;
@@ -730,9 +876,11 @@ var OpfsFs = class {
730
876
  throw fsError("ENOTEMPTY", to);
731
877
  }
732
878
  const now = Date.now();
879
+ const moving = await this.movedOwner(source);
733
880
  const carry = (entry) => ({
734
881
  ino: inodeOf(source.path),
735
882
  ...entry,
883
+ ...moving,
736
884
  path: destination,
737
885
  ctimeMs: now
738
886
  });
@@ -806,7 +954,7 @@ var OpfsFs = class {
806
954
  const target = await dir.getDirectoryHandle(name2, { create: true });
807
955
  for await (const child of handle.values()) await this.copy(child, target, child.name);
808
956
  }
809
- async symlink(target, path) {
957
+ async symlink(target, path, owner) {
810
958
  const parent = await this.locate(parentOf2(path), true);
811
959
  if (parent.handle?.kind !== "directory") throw fsError("ENOTDIR", path);
812
960
  const at = resolvePath(parent.path, baseOf(path));
@@ -817,13 +965,15 @@ var OpfsFs = class {
817
965
  throw fsError("EEXIST", path);
818
966
  }
819
967
  const now = Date.now();
968
+ const fields = await this.newFields(at, owner && { uid: owner.uid, gid: owner.gid }, false);
820
969
  await this.meta.update(at, () => ({
821
970
  path: at,
822
971
  link: target,
823
972
  dir: parent.path,
824
973
  ino: inodeOf(at),
825
974
  mtimeMs: now,
826
- ctimeMs: now
975
+ ctimeMs: now,
976
+ ...fields
827
977
  }));
828
978
  this.touch(parent.path);
829
979
  }
@@ -2467,12 +2617,30 @@ function userEnv(account, system) {
2467
2617
  PATH: `${pnpm}/bin:/usr/local/bin:/usr/bin:/bin`
2468
2618
  };
2469
2619
  }
2620
+ function homeGroupOf(groups, name2) {
2621
+ if (name2 === void 0) return void 0;
2622
+ const found = groups.find((g) => g.name === name2);
2623
+ if (!found) throw fsError("ENOENT", `no group ${name2}`);
2624
+ return found;
2625
+ }
2470
2626
  var UserDb = class {
2471
2627
  fs;
2628
+ owners;
2472
2629
  umasks = /* @__PURE__ */ new Map();
2473
2630
  writing = Promise.resolve();
2474
- constructor(fs) {
2631
+ constructor(fs, owners = false) {
2475
2632
  this.fs = fs;
2633
+ this.owners = owners;
2634
+ }
2635
+ async stampHome(row, shared) {
2636
+ const home = this.fs.resolvePath("/", row.home);
2637
+ if (!this.owners || !this.fs.chown || !HOME_DIR.test(home)) return;
2638
+ if (await this.fs.realpath?.(home, false).catch(() => void 0) !== home) return;
2639
+ if (!(await this.fs.lstat(home)).isDirectory) return;
2640
+ const gid = shared?.gid ?? row.gid;
2641
+ await this.fs.chown(home, { uid: row.uid, gid, follow: false, kind: "adopt" });
2642
+ const mode = shared ? 1512 : row.name.startsWith("scoop-") ? 488 : 448;
2643
+ await this.fs.chmod(home, mode);
2476
2644
  }
2477
2645
  async read(path, fallback) {
2478
2646
  try {
@@ -2572,8 +2740,12 @@ var UserDb = class {
2572
2740
  const users = parsed(passwd);
2573
2741
  const groups = parsed(group);
2574
2742
  const found = users.find((r) => r.name === name2);
2743
+ const shared = homeGroupOf(groups, options.homeGroup);
2575
2744
  if (found) {
2576
- if (found.uid >= FIRST_UID && found.uid <= LAST_UID) await this.provision(found);
2745
+ if (found.uid >= FIRST_UID && found.uid <= LAST_UID) {
2746
+ await this.provision(found);
2747
+ await this.stampHome(found, shared);
2748
+ }
2577
2749
  remember();
2578
2750
  return this.account(found, groups);
2579
2751
  }
@@ -2593,6 +2765,7 @@ var UserDb = class {
2593
2765
  await this.fs.mkdir(home, { recursive: true });
2594
2766
  try {
2595
2767
  await this.provision(row);
2768
+ await this.stampHome(row, shared);
2596
2769
  await this.fs.mkdir("/etc", { recursive: true });
2597
2770
  await this.commit(formatPasswd(passwd), formatGroup(group));
2598
2771
  } catch (err) {
@@ -2661,7 +2834,7 @@ async function realpath(fs, path) {
2661
2834
  }
2662
2835
  return resolved || "/";
2663
2836
  }
2664
- async function fsCall(fs, method, args) {
2837
+ async function fsCall(fs, method, args, owner) {
2665
2838
  const path = (i) => fs.resolvePath("/", String(args[i]));
2666
2839
  switch (method) {
2667
2840
  case "readFile": {
@@ -2669,7 +2842,7 @@ async function fsCall(fs, method, args) {
2669
2842
  return { result: bytes, transfer: [bytes.buffer] };
2670
2843
  }
2671
2844
  case "writeFile":
2672
- await fs.writeFile(path(0), args[1]);
2845
+ await fs.writeFile(path(0), args[1], owner);
2673
2846
  return { result: true };
2674
2847
  case "stat":
2675
2848
  return { result: await fs.stat(path(0)) };
@@ -2678,7 +2851,7 @@ async function fsCall(fs, method, args) {
2678
2851
  case "readdir":
2679
2852
  return { result: await fs.readdir(path(0)) };
2680
2853
  case "mkdir":
2681
- await fs.mkdir(path(0), { recursive: true });
2854
+ await fs.mkdir(path(0), owner ? { recursive: true, owner } : { recursive: true });
2682
2855
  return { result: true };
2683
2856
  case "rm":
2684
2857
  await fs.rm(path(0), { recursive: true, force: args[1] === true });
@@ -2689,7 +2862,7 @@ async function fsCall(fs, method, args) {
2689
2862
  case "realpath":
2690
2863
  return { result: await realpath(fs, String(args[0])) };
2691
2864
  case "symlink":
2692
- await fs.symlink(String(args[0]), path(1));
2865
+ await fs.symlink(String(args[0]), path(1), owner);
2693
2866
  return { result: true };
2694
2867
  case "readlink":
2695
2868
  return { result: await fs.readlink(path(0)) };
@@ -2711,6 +2884,9 @@ function dialFor(l, req, open) {
2711
2884
  );
2712
2885
  return { result: port2, transfer: [port2] };
2713
2886
  }
2887
+ function ownerFor(l, self) {
2888
+ return l.owners && self ? { uid: self.uid, gid: self.gid } : void 0;
2889
+ }
2714
2890
  async function accountOf(l, user) {
2715
2891
  const found = await l.users.lookup(user);
2716
2892
  if (!found) throw fsError("ENOENT", `no user ${String(user)}`);
@@ -2838,6 +3014,7 @@ function serveClient(port, host) {
2838
3014
  hello: {
2839
3015
  protocol: PROTOCOL,
2840
3016
  traits: transport.traits,
3017
+ ...launcher.usersMode ? { users: launcher.usersMode } : {},
2841
3018
  ...host.lock ? { lock: host.lock } : {}
2842
3019
  }
2843
3020
  });
@@ -2903,7 +3080,7 @@ function serveClient(port, host) {
2903
3080
  if (!FS_METHODS.includes(req.method)) {
2904
3081
  throw new Error(`unknown file system call ${req.method}`);
2905
3082
  }
2906
- return fsCall(l.fs, req.method, req.args);
3083
+ return fsCall(l.fs, req.method, req.args, ownerFor(l, self));
2907
3084
  case "watch":
2908
3085
  case "unwatch":
2909
3086
  return watchOp(l, req, watches, send);
@@ -3926,10 +4103,10 @@ var RealmProxy = class {
3926
4103
  }
3927
4104
  async bridge(ctx, socket, hangup) {
3928
4105
  const stop = this.stop.signal;
3929
- let chain = Promise.resolve();
4106
+ let chain2 = Promise.resolve();
3930
4107
  const toGuest = (bytes) => {
3931
- chain = chain.then(() => ctx.sink.write(bytes, stop));
3932
- return chain;
4108
+ chain2 = chain2.then(() => ctx.sink.write(bytes, stop));
4109
+ return chain2;
3933
4110
  };
3934
4111
  const done4 = new AbortController();
3935
4112
  const reading = AbortSignal.any([stop, done4.signal, hangup]);
@@ -5636,7 +5813,7 @@ function withCommandDirs(fs, names, userDir = async () => void 0) {
5636
5813
  throw err;
5637
5814
  }
5638
5815
  },
5639
- writeFile: (path, content) => fs.writeFile(path, content),
5816
+ writeFile: (path, content, owner) => fs.writeFile(path, content, owner),
5640
5817
  async exists(path) {
5641
5818
  return await fs.exists(path) || await linked(path) !== void 0;
5642
5819
  },
@@ -5674,9 +5851,10 @@ function withCommandDirs(fs, names, userDir = async () => void 0) {
5674
5851
  mkdir: (path, options) => fs.mkdir(path, options),
5675
5852
  rm: (path, options) => fs.rm(path, options),
5676
5853
  rename: (from, to) => fs.rename(from, to),
5677
- symlink: (target, path) => fs.symlink(target, path),
5854
+ symlink: (target, path, owner) => fs.symlink(target, path, owner),
5678
5855
  readlink: (path) => fs.readlink(path),
5679
5856
  ...fs.realpath ? { realpath: fs.realpath.bind(fs) } : {},
5857
+ ...fs.chown ? { chown: fs.chown.bind(fs) } : {},
5680
5858
  chmod: (path, mode) => fs.chmod(path, mode),
5681
5859
  utimes: (path, atime, mtime) => fs.utimes(path, atime, mtime),
5682
5860
  lutimes: (path, atime, mtime) => lutimesOf(fs, path, atime, mtime),
@@ -5993,6 +6171,7 @@ var FsWatchers = class {
5993
6171
  rename: after(fs.rename.bind(fs), (from, to) => [from, to]),
5994
6172
  symlink: after(fs.symlink.bind(fs), (_target, path) => [path]),
5995
6173
  chmod: after(fs.chmod.bind(fs), (path) => [path]),
6174
+ ...fs.chown ? { chown: after(fs.chown.bind(fs), (path) => [path]) } : {},
5996
6175
  utimes: after(fs.utimes.bind(fs), (path) => [path]),
5997
6176
  lutimes: after(
5998
6177
  (path, atime, mtime) => lutimesOf(fs, path, atime, mtime),
@@ -6064,13 +6243,13 @@ var ChildTable = class {
6064
6243
  if (!this.forker) throw new SpawnError("ENOSYS");
6065
6244
  return this.track(state, this.parentFds.fork(), /* @__PURE__ */ new Map(), this.forker);
6066
6245
  }
6067
- async spawn(req, stdio, inherit = []) {
6246
+ async spawn(req, stdio, inherit2 = []) {
6068
6247
  if (!this.spawner) throw new SpawnError("ENOSYS");
6069
6248
  const fds = new FdTable();
6070
6249
  const captured = /* @__PURE__ */ new Map();
6071
6250
  try {
6072
6251
  for (const [n, slot] of stdio.entries()) fds.installAt(n, this.openSlot(slot, n, captured));
6073
- for (const slot of inherit) {
6252
+ for (const slot of inherit2) {
6074
6253
  if (slot.fd <= 2) continue;
6075
6254
  if ("device" in slot) {
6076
6255
  fds.installAt(slot.fd, deviceFile(slot.device, slot.access));
@@ -6151,8 +6330,8 @@ var ChildTable = class {
6151
6330
  candidates(pid, flags) {
6152
6331
  const all = [...this.children];
6153
6332
  if (pid > 0) return all.filter(([p]) => p === pid);
6154
- const inGroup = flags.inGroup;
6155
- return pid === -1 || !inGroup ? all : all.filter(([p]) => inGroup(p));
6333
+ const inGroup2 = flags.inGroup;
6334
+ return pid === -1 || !inGroup2 ? all : all.filter(([p]) => inGroup2(p));
6156
6335
  }
6157
6336
  async nextChange(candidates, flags, interrupt) {
6158
6337
  let wake;
@@ -6873,10 +7052,10 @@ var MountFs = class {
6873
7052
  }
6874
7053
  return concat4(chunks, total);
6875
7054
  }
6876
- async writeFile(path, content) {
7055
+ async writeFile(path, content, owner) {
6877
7056
  const real = await this.follow(path, 0, true);
6878
7057
  const found = this.at(real);
6879
- if (!found) return this.base.writeFile(real, content);
7058
+ if (!found) return this.base.writeFile(real, content, owner);
6880
7059
  const { mount, rel: rel2 } = found;
6881
7060
  this.writable(mount, real);
6882
7061
  const bytes = typeof content === "string" ? new TextEncoder().encode(content) : content;
@@ -7143,9 +7322,9 @@ var MountFs = class {
7143
7322
  this.table.forget(a.mount, normalizePath(to));
7144
7323
  }
7145
7324
  }
7146
- async symlink(target, path) {
7325
+ async symlink(target, path, owner) {
7147
7326
  const found = this.at(path);
7148
- if (!found) return this.base.symlink(target, path);
7327
+ if (!found) return this.base.symlink(target, path, owner);
7149
7328
  this.writable(found.mount, normalizePath(path));
7150
7329
  if (!found.mount.caps.symlinks)
7151
7330
  throw errnoError("EPERM", `${path}: this file system has no symlinks`);
@@ -7163,6 +7342,15 @@ var MountFs = class {
7163
7342
  if (!found) return this.base.readlink(path);
7164
7343
  return await found.mount.conn.call({ op: "readlink", path: found.rel });
7165
7344
  }
7345
+ async chown(path, change) {
7346
+ const real = change.follow === false || change.kind === "new" ? path : await this.follow(path);
7347
+ if (this.at(real)) {
7348
+ if (change.kind === "new") return;
7349
+ throw errnoError("EPERM", `${path}: this file system keeps no owners`);
7350
+ }
7351
+ if (!this.base.chown) throw errnoError("EPERM", path);
7352
+ return this.base.chown(real, change);
7353
+ }
7166
7354
  async chmod(path, mode) {
7167
7355
  const found = this.at(path);
7168
7356
  if (!found) return this.base.chmod(path, mode);
@@ -7237,7 +7425,9 @@ function statJson(s) {
7237
7425
  ...s.readonly ? { readonly: true } : {},
7238
7426
  ...s.maxFile ? { maxFile: s.maxFile } : {},
7239
7427
  ...s.ranged ? { ranged: true } : {},
7240
- ...s.version !== void 0 ? { version: s.version } : {}
7428
+ ...s.version !== void 0 ? { version: s.version } : {},
7429
+ ...s.uid !== void 0 ? { uid: s.uid } : {},
7430
+ ...s.gid !== void 0 ? { gid: s.gid } : {}
7241
7431
  };
7242
7432
  }
7243
7433
  async function readdirStat(fs, dir) {
@@ -7252,13 +7442,34 @@ var done = { ok: true, kind: "void" };
7252
7442
  function json2(value) {
7253
7443
  return { ok: true, kind: "json", json: value };
7254
7444
  }
7445
+ function ownerFor2(entry, mode) {
7446
+ const cred = entry.cred?.();
7447
+ if (!cred) return void 0;
7448
+ return ownerOf(cred, mode === void 0 ? void 0 : mode & ~(entry.umask?.() ?? 18));
7449
+ }
7450
+ function mkdirOptions(entry, mode = 511) {
7451
+ const owner = ownerFor2(entry, mode);
7452
+ if (!owner) return { recursive: true };
7453
+ const parents = 511 & ~(entry.umask?.() ?? 18) | 192;
7454
+ return { recursive: true, owner: { ...owner, parents } };
7455
+ }
7456
+ async function chown(entry, path, req) {
7457
+ const cred = entry.cred?.();
7458
+ if (!cred) return done;
7459
+ await chownAs(entry.fs, cred, path, {
7460
+ ...req.uid !== void 0 ? { uid: req.uid } : {},
7461
+ ...req.gid !== void 0 ? { gid: req.gid } : {},
7462
+ follow: req.op === "chown"
7463
+ });
7464
+ return done;
7465
+ }
7255
7466
  async function run(entry, path, req) {
7256
7467
  const { fs, cwd } = entry;
7257
7468
  switch (req.op) {
7258
7469
  case "read":
7259
7470
  return { ok: true, kind: "bytes", bytes: await fs.readFileBuffer(path) };
7260
7471
  case "write":
7261
- await fs.writeFile(path, req.body ?? new Uint8Array(0));
7472
+ await fs.writeFile(path, req.body ?? new Uint8Array(0), ownerFor2(entry, 438));
7262
7473
  return done;
7263
7474
  case "exists":
7264
7475
  return json2(await fs.exists(path));
@@ -7271,7 +7482,7 @@ async function run(entry, path, req) {
7271
7482
  case "readdir-stat":
7272
7483
  return json2(await readdirStat(fs, path));
7273
7484
  case "mkdir":
7274
- await fs.mkdir(path, { recursive: true });
7485
+ await fs.mkdir(path, mkdirOptions(entry, req.mode));
7275
7486
  return done;
7276
7487
  case "rm":
7277
7488
  await fs.rm(path, { recursive: true });
@@ -7292,10 +7503,10 @@ async function run(entry, path, req) {
7292
7503
  await fs.rm(path, { recursive: true });
7293
7504
  return done;
7294
7505
  case "symlink":
7295
- await fs.symlink(req.arg2 ?? "", path);
7506
+ await fs.symlink(req.arg2 ?? "", path, ownerFor2(entry));
7296
7507
  return done;
7297
7508
  case "create":
7298
- await createExclusive(fs, path);
7509
+ await createExclusive(fs, path, ownerFor2(entry, req.mode ?? 438));
7299
7510
  return done;
7300
7511
  case "readlink":
7301
7512
  return json2(await fs.readlink(path));
@@ -7308,6 +7519,9 @@ async function run(entry, path, req) {
7308
7519
  case "lutimes":
7309
7520
  await lutimesOf(fs, path, new Date(req.atimeMs ?? 0), new Date(req.mtimeMs ?? 0));
7310
7521
  return done;
7522
+ case "chown":
7523
+ case "lchown":
7524
+ return chown(entry, path, req);
7311
7525
  case "pread":
7312
7526
  case "pwrite":
7313
7527
  case "truncate":
@@ -7316,14 +7530,14 @@ async function run(entry, path, req) {
7316
7530
  return { ok: false, errno: "EINVAL", message: `sync-fs: unknown op '${req.op}'` };
7317
7531
  }
7318
7532
  }
7319
- async function createExclusive(fs, path) {
7320
- if (fs.createExclusive) return fs.createExclusive(path);
7533
+ async function createExclusive(fs, path, owner) {
7534
+ if (fs.createExclusive) return fs.createExclusive(path, owner);
7321
7535
  if (await fs.lstat(path).then(
7322
7536
  () => true,
7323
7537
  () => false
7324
7538
  ))
7325
7539
  throw syncError("EEXIST", path);
7326
- await fs.writeFile(path, new Uint8Array(0));
7540
+ await fs.writeFile(path, new Uint8Array(0), owner);
7327
7541
  }
7328
7542
  async function ranged(fs, path, req) {
7329
7543
  const ops = rangedOps(fs);
@@ -9518,12 +9732,12 @@ var VfsNodes = class {
9518
9732
  }
9519
9733
  return walk();
9520
9734
  }
9521
- createExclusive(path) {
9735
+ createExclusive(path, owner) {
9522
9736
  return this.onEntry(path, async () => {
9523
9737
  const node = this.byPath.get(path);
9524
9738
  const held = node ? await node.serial(async () => node.holds()) : false;
9525
9739
  if (held || await present(this.fs, path)) throw fsError("EEXIST", path);
9526
- await this.fs.writeFile(path, new Uint8Array(0));
9740
+ await this.fs.writeFile(path, new Uint8Array(0), owner);
9527
9741
  });
9528
9742
  }
9529
9743
  open(path, pin, writable = false) {
@@ -9740,9 +9954,9 @@ function keepingOpen(fs, nodes) {
9740
9954
  nodes.renamed(a, b);
9741
9955
  });
9742
9956
  },
9743
- writeFile: (path, content) => nodes.onEntry(fs.resolvePath("/", path), () => fs.writeFile(path, content)),
9744
- createExclusive: async (path) => nodes.createExclusive(await nodes.entryKey(fs.resolvePath("/", path))),
9745
- symlink: (target, path) => nodes.onEntry(fs.resolvePath("/", path), () => fs.symlink(target, path)),
9957
+ writeFile: (path, content, owner) => nodes.onEntry(fs.resolvePath("/", path), () => fs.writeFile(path, content, owner)),
9958
+ createExclusive: async (path, owner) => nodes.createExclusive(await nodes.entryKey(fs.resolvePath("/", path)), owner),
9959
+ symlink: (target, path, owner) => nodes.onEntry(fs.resolvePath("/", path), () => fs.symlink(target, path, owner)),
9746
9960
  mkdir: (path, options) => nodes.onEntry(fs.resolvePath("/", path), () => fs.mkdir(path, options))
9747
9961
  };
9748
9962
  }
@@ -10013,9 +10227,9 @@ var WasmProcess = class {
10013
10227
  return "deliver";
10014
10228
  }
10015
10229
  async spawn(req) {
10016
- const { file, argv, env, cwd, stdio, inherit, exec } = req;
10230
+ const { file, argv, env, cwd, stdio, inherit: inherit2, exec } = req;
10017
10231
  const spawned = { file, argv, env, cwd, ...exec ? { exec } : {} };
10018
- const pid = await this.children.spawn(spawned, stdio, inherit);
10232
+ const pid = await this.children.spawn(spawned, stdio, inherit2);
10019
10233
  if (!exec) return pid;
10020
10234
  if (this.killed) void Promise.allSettled([this.options.kill?.(pid, SIG.KILL)]);
10021
10235
  else this.adoptExec(pid);
@@ -10180,12 +10394,14 @@ var WasmProcess = class {
10180
10394
  }
10181
10395
  const open = { ...req, path };
10182
10396
  if (req.exclusive) {
10183
- await nodes.createExclusive(path);
10397
+ await nodes.createExclusive(path, this.newOwner());
10184
10398
  this.fds.installAt(fd, this.openVfsFile(open));
10185
10399
  } else if (req.create) {
10186
10400
  await nodes.onEntry(path, async () => {
10401
+ const fresh = this.options.owners && !await present(this.options.fs, path, true);
10187
10402
  this.fds.installAt(fd, this.openVfsFile(open));
10188
10403
  await this.fds.get(fd).file.stat?.();
10404
+ if (fresh) await this.options.fs.chown?.(path, { ...this.newOwner(), kind: "new" });
10189
10405
  });
10190
10406
  } else {
10191
10407
  this.fds.installAt(fd, this.openVfsFile(open));
@@ -10199,6 +10415,10 @@ var WasmProcess = class {
10199
10415
  }
10200
10416
  return fd;
10201
10417
  }
10418
+ newOwner() {
10419
+ if (!this.options.owners) return void 0;
10420
+ return { uid: this.cred.euid, gid: this.cred.egid, mode: 438 & ~this.umask };
10421
+ }
10202
10422
  async openProc(req, path) {
10203
10423
  return this.fds.install(await this.procFile(req, path), 3);
10204
10424
  }
@@ -10845,6 +11065,7 @@ function processInit(opts, sab, umask, cred, lane) {
10845
11065
  sab,
10846
11066
  umask,
10847
11067
  cred,
11068
+ ...opts.owners ? { owners: true } : {},
10848
11069
  ...opts.ppid !== void 0 ? { ppid: opts.ppid } : {}
10849
11070
  };
10850
11071
  if (opts.program.abi === "js") {
@@ -10868,8 +11089,9 @@ function inherited(opts) {
10868
11089
  ...opts.cred ? { cred: opts.cred } : {}
10869
11090
  };
10870
11091
  }
10871
- function holdingToken(opts, holds) {
11092
+ function holdingToken(opts, holds, caller) {
10872
11093
  return mintSyncFsToken({
11094
+ ...opts.owners ? caller : {},
10873
11095
  fs: opts.fs,
10874
11096
  cwd: opts.cwd,
10875
11097
  ...opts.statfs ? { statfs: opts.statfs } : {},
@@ -10885,6 +11107,7 @@ function holdingToken(opts, holds) {
10885
11107
  function processOptions(opts, header3, hooks) {
10886
11108
  return {
10887
11109
  ...inherited(opts),
11110
+ ...opts.owners ? { owners: true } : {},
10888
11111
  ...opts.identity ? { identity: opts.identity } : {},
10889
11112
  ...opts.onSyscall ? { onSyscall: opts.onSyscall } : {},
10890
11113
  ...opts.settling ? { settling: opts.settling } : {},
@@ -10929,7 +11152,10 @@ function spawnWasmProcess(opts) {
10929
11152
  publishStops(process, header3);
10930
11153
  const holds = new HeldPaths();
10931
11154
  opts.held?.add(holds);
10932
- const token = holdingToken(opts, holds);
11155
+ const token = holdingToken(opts, holds, {
11156
+ cred: () => process.cred,
11157
+ umask: () => process.umask
11158
+ });
10933
11159
  process.keptPath = (path) => holds.keptAt(path)?.hidden;
10934
11160
  process.keptHandle = (handle2) => holds.keptFor(handle2)?.hidden;
10935
11161
  const worker = opts.createWorker();
@@ -11290,26 +11516,26 @@ function replayingHops(inner, heldMs = HELD_MS) {
11290
11516
  void entry.final.cancel().catch(() => void 0);
11291
11517
  };
11292
11518
  const keyOf2 = (owner, method, url) => `${owner} ${method} ${url}`;
11293
- const hold = (key, chain) => {
11519
+ const hold = (key, chain2) => {
11294
11520
  drop(key);
11295
- const mine = [...held.keys()].filter((k) => held.get(k)?.owner === chain.owner);
11521
+ const mine = [...held.keys()].filter((k) => held.get(k)?.owner === chain2.owner);
11296
11522
  if (mine.length >= HELD_PER_OWNER) drop(mine[0]);
11297
11523
  if (held.size >= HELD_MAX) drop(held.keys().next().value);
11298
11524
  const timer = setTimeout(() => drop(key), heldMs);
11299
11525
  timer.unref?.();
11300
- held.set(key, { ...chain, timer });
11526
+ held.set(key, { ...chain2, timer });
11301
11527
  };
11302
- const replay = (chain) => {
11303
- const [hop, ...rest2] = chain.hops;
11528
+ const replay = (chain2) => {
11529
+ const [hop, ...rest2] = chain2.hops;
11304
11530
  const location = hop && header2(hop.headers, "location");
11305
- if (!hop || location === void 0) return chain.final;
11531
+ if (!hop || location === void 0) return chain2.final;
11306
11532
  const target = new URL(location, hop.url);
11307
11533
  target.hash = "";
11308
- const method = followedWith(hop.status, chain.method);
11534
+ const method = followedWith(hop.status, chain2.method);
11309
11535
  const away = target.origin !== new URL(hop.url).origin;
11310
- const print = away ? unauthorized(chain.fingerprint) : chain.fingerprint;
11311
- hold(keyOf2(chain.owner, method, target.href), {
11312
- ...chain,
11536
+ const print = away ? unauthorized(chain2.fingerprint) : chain2.fingerprint;
11537
+ hold(keyOf2(chain2.owner, method, target.href), {
11538
+ ...chain2,
11313
11539
  method,
11314
11540
  fingerprint: print,
11315
11541
  hops: rest2
@@ -12898,6 +13124,11 @@ var DECIDE_MS = 100;
12898
13124
  var INIT_PID = 1;
12899
13125
  var SHARED_DIRS = ["/tmp", "/home"];
12900
13126
  var encoder3 = new TextEncoder();
13127
+ var USERS_MODES = ["off", "report", "enforce"];
13128
+ function usersMode(mode = "off") {
13129
+ if (USERS_MODES.includes(mode)) return mode;
13130
+ throw new Error(`users must be 'off', 'report' or 'enforce', not ${String(mode)}`);
13131
+ }
12901
13132
  function baseName(path) {
12902
13133
  return path.slice(path.lastIndexOf("/") + 1);
12903
13134
  }
@@ -13025,10 +13256,13 @@ var Launcher = class {
13025
13256
  processMounts;
13026
13257
  booting = new AbortController();
13027
13258
  fstab = Promise.resolve([]);
13259
+ usersMode;
13028
13260
  nextPid = 1e3;
13029
13261
  boot = Date.now();
13030
13262
  terminals = 0;
13031
13263
  constructor(options) {
13264
+ this.usersMode = usersMode(options.users);
13265
+ if (this.owners) options.fs.trackOwners?.();
13032
13266
  this.mounts = new MountTable({
13033
13267
  open: (type, spec) => this.driver(type, spec),
13034
13268
  busy: (target) => [...this.openFiles].some((nodes) => nodes.holds(target)) || heldUnder(this.held, target),
@@ -13082,7 +13316,7 @@ var Launcher = class {
13082
13316
  }
13083
13317
  );
13084
13318
  this.fs = this.unlinked.wrap(keepingOpen(fs, this.nodes));
13085
- this.users = new UserDb(this.fs);
13319
+ this.users = new UserDb(this.fs, this.owners);
13086
13320
  void this.unlinked.sweep("/");
13087
13321
  this.watchers.watch(
13088
13322
  [this.modulesDir, this.pnpmHome],
@@ -13094,6 +13328,9 @@ var Launcher = class {
13094
13328
  this.resetCatalogs();
13095
13329
  });
13096
13330
  }
13331
+ get owners() {
13332
+ return this.usersMode !== "off";
13333
+ }
13097
13334
  setRoutes(table) {
13098
13335
  this.routes.set(table);
13099
13336
  }
@@ -13352,6 +13589,7 @@ var Launcher = class {
13352
13589
  ...req.ignored ? { ignored: req.ignored } : {},
13353
13590
  ...req.umask !== void 0 ? { umask: req.umask } : {},
13354
13591
  cred: req.cred,
13592
+ ...this.owners ? { owners: true } : {},
13355
13593
  program: req.program,
13356
13594
  argv0: req.argv0,
13357
13595
  args: req.args,
@@ -13930,6 +14168,12 @@ function usersOp(l, req) {
13930
14168
  if (req.op === "users-remove") return l.users.remove(req.name, req.options);
13931
14169
  return l.users.list();
13932
14170
  }
14171
+ function hostOptions(req) {
14172
+ return {
14173
+ ...req.hostname !== void 0 ? { hostname: req.hostname } : {},
14174
+ ...req.users !== void 0 ? { users: req.users } : {}
14175
+ };
14176
+ }
13933
14177
  function serveKernel(port, deps) {
13934
14178
  let launcher;
13935
14179
  let remote;
@@ -14004,7 +14248,7 @@ function serveKernel(port, deps) {
14004
14248
  ...deps.createDriverWorker ? { createDriverWorker: deps.createDriverWorker } : {},
14005
14249
  ...req.modules ? { modules: req.modules } : {},
14006
14250
  ...req.env ? { env: req.env } : {},
14007
- ...req.hostname !== void 0 ? { hostname: req.hostname } : {},
14251
+ ...hostOptions(req),
14008
14252
  ...net,
14009
14253
  ...media ? { media } : {},
14010
14254
  onMountPending: (medium) => port.postMessage({ medium }),