@ai-ecoverse/slicc-kernel 1.56.2 → 1.57.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.
@@ -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);
@@ -7945,6 +8159,10 @@ function normalize(path) {
7945
8159
  }
7946
8160
  return `/${out.join("/")}`;
7947
8161
  }
8162
+ var RING = 8;
8163
+ var RING_SIZE = 64;
8164
+ var SLOT = 3;
8165
+ var SHARED_IDS_BYTES = (RING + RING_SIZE * SLOT) * 4;
7948
8166
 
7949
8167
  // src/process/wasi/wasi-procfs.ts
7950
8168
  var DIR_MODE = 16749;
@@ -9518,12 +9736,12 @@ var VfsNodes = class {
9518
9736
  }
9519
9737
  return walk();
9520
9738
  }
9521
- createExclusive(path) {
9739
+ createExclusive(path, owner) {
9522
9740
  return this.onEntry(path, async () => {
9523
9741
  const node = this.byPath.get(path);
9524
9742
  const held = node ? await node.serial(async () => node.holds()) : false;
9525
9743
  if (held || await present(this.fs, path)) throw fsError("EEXIST", path);
9526
- await this.fs.writeFile(path, new Uint8Array(0));
9744
+ await this.fs.writeFile(path, new Uint8Array(0), owner);
9527
9745
  });
9528
9746
  }
9529
9747
  open(path, pin, writable = false) {
@@ -9740,9 +9958,9 @@ function keepingOpen(fs, nodes) {
9740
9958
  nodes.renamed(a, b);
9741
9959
  });
9742
9960
  },
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)),
9961
+ writeFile: (path, content, owner) => nodes.onEntry(fs.resolvePath("/", path), () => fs.writeFile(path, content, owner)),
9962
+ createExclusive: async (path, owner) => nodes.createExclusive(await nodes.entryKey(fs.resolvePath("/", path)), owner),
9963
+ symlink: (target, path, owner) => nodes.onEntry(fs.resolvePath("/", path), () => fs.symlink(target, path, owner)),
9746
9964
  mkdir: (path, options) => nodes.onEntry(fs.resolvePath("/", path), () => fs.mkdir(path, options))
9747
9965
  };
9748
9966
  }
@@ -10013,9 +10231,9 @@ var WasmProcess = class {
10013
10231
  return "deliver";
10014
10232
  }
10015
10233
  async spawn(req) {
10016
- const { file, argv, env, cwd, stdio, inherit, exec } = req;
10234
+ const { file, argv, env, cwd, stdio, inherit: inherit2, exec } = req;
10017
10235
  const spawned = { file, argv, env, cwd, ...exec ? { exec } : {} };
10018
- const pid = await this.children.spawn(spawned, stdio, inherit);
10236
+ const pid = await this.children.spawn(spawned, stdio, inherit2);
10019
10237
  if (!exec) return pid;
10020
10238
  if (this.killed) void Promise.allSettled([this.options.kill?.(pid, SIG.KILL)]);
10021
10239
  else this.adoptExec(pid);
@@ -10180,12 +10398,14 @@ var WasmProcess = class {
10180
10398
  }
10181
10399
  const open = { ...req, path };
10182
10400
  if (req.exclusive) {
10183
- await nodes.createExclusive(path);
10401
+ await nodes.createExclusive(path, this.newOwner());
10184
10402
  this.fds.installAt(fd, this.openVfsFile(open));
10185
10403
  } else if (req.create) {
10186
10404
  await nodes.onEntry(path, async () => {
10405
+ const fresh = this.options.owners && !await present(this.options.fs, path, true);
10187
10406
  this.fds.installAt(fd, this.openVfsFile(open));
10188
10407
  await this.fds.get(fd).file.stat?.();
10408
+ if (fresh) await this.options.fs.chown?.(path, { ...this.newOwner(), kind: "new" });
10189
10409
  });
10190
10410
  } else {
10191
10411
  this.fds.installAt(fd, this.openVfsFile(open));
@@ -10199,6 +10419,10 @@ var WasmProcess = class {
10199
10419
  }
10200
10420
  return fd;
10201
10421
  }
10422
+ newOwner() {
10423
+ if (!this.options.owners) return void 0;
10424
+ return { uid: this.cred.euid, gid: this.cred.egid, mode: 438 & ~this.umask };
10425
+ }
10202
10426
  async openProc(req, path) {
10203
10427
  return this.fds.install(await this.procFile(req, path), 3);
10204
10428
  }
@@ -10845,6 +11069,7 @@ function processInit(opts, sab, umask, cred, lane) {
10845
11069
  sab,
10846
11070
  umask,
10847
11071
  cred,
11072
+ ...opts.owners ? { owners: true } : {},
10848
11073
  ...opts.ppid !== void 0 ? { ppid: opts.ppid } : {}
10849
11074
  };
10850
11075
  if (opts.program.abi === "js") {
@@ -10868,8 +11093,9 @@ function inherited(opts) {
10868
11093
  ...opts.cred ? { cred: opts.cred } : {}
10869
11094
  };
10870
11095
  }
10871
- function holdingToken(opts, holds) {
11096
+ function holdingToken(opts, holds, caller) {
10872
11097
  return mintSyncFsToken({
11098
+ ...opts.owners ? caller : {},
10873
11099
  fs: opts.fs,
10874
11100
  cwd: opts.cwd,
10875
11101
  ...opts.statfs ? { statfs: opts.statfs } : {},
@@ -10885,6 +11111,7 @@ function holdingToken(opts, holds) {
10885
11111
  function processOptions(opts, header3, hooks) {
10886
11112
  return {
10887
11113
  ...inherited(opts),
11114
+ ...opts.owners ? { owners: true } : {},
10888
11115
  ...opts.identity ? { identity: opts.identity } : {},
10889
11116
  ...opts.onSyscall ? { onSyscall: opts.onSyscall } : {},
10890
11117
  ...opts.settling ? { settling: opts.settling } : {},
@@ -10929,7 +11156,10 @@ function spawnWasmProcess(opts) {
10929
11156
  publishStops(process, header3);
10930
11157
  const holds = new HeldPaths();
10931
11158
  opts.held?.add(holds);
10932
- const token = holdingToken(opts, holds);
11159
+ const token = holdingToken(opts, holds, {
11160
+ cred: () => process.cred,
11161
+ umask: () => process.umask
11162
+ });
10933
11163
  process.keptPath = (path) => holds.keptAt(path)?.hidden;
10934
11164
  process.keptHandle = (handle2) => holds.keptFor(handle2)?.hidden;
10935
11165
  const worker = opts.createWorker();
@@ -11290,26 +11520,26 @@ function replayingHops(inner, heldMs = HELD_MS) {
11290
11520
  void entry.final.cancel().catch(() => void 0);
11291
11521
  };
11292
11522
  const keyOf2 = (owner, method, url) => `${owner} ${method} ${url}`;
11293
- const hold = (key, chain) => {
11523
+ const hold = (key, chain2) => {
11294
11524
  drop(key);
11295
- const mine = [...held.keys()].filter((k) => held.get(k)?.owner === chain.owner);
11525
+ const mine = [...held.keys()].filter((k) => held.get(k)?.owner === chain2.owner);
11296
11526
  if (mine.length >= HELD_PER_OWNER) drop(mine[0]);
11297
11527
  if (held.size >= HELD_MAX) drop(held.keys().next().value);
11298
11528
  const timer = setTimeout(() => drop(key), heldMs);
11299
11529
  timer.unref?.();
11300
- held.set(key, { ...chain, timer });
11530
+ held.set(key, { ...chain2, timer });
11301
11531
  };
11302
- const replay = (chain) => {
11303
- const [hop, ...rest2] = chain.hops;
11532
+ const replay = (chain2) => {
11533
+ const [hop, ...rest2] = chain2.hops;
11304
11534
  const location = hop && header2(hop.headers, "location");
11305
- if (!hop || location === void 0) return chain.final;
11535
+ if (!hop || location === void 0) return chain2.final;
11306
11536
  const target = new URL(location, hop.url);
11307
11537
  target.hash = "";
11308
- const method = followedWith(hop.status, chain.method);
11538
+ const method = followedWith(hop.status, chain2.method);
11309
11539
  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,
11540
+ const print = away ? unauthorized(chain2.fingerprint) : chain2.fingerprint;
11541
+ hold(keyOf2(chain2.owner, method, target.href), {
11542
+ ...chain2,
11313
11543
  method,
11314
11544
  fingerprint: print,
11315
11545
  hops: rest2
@@ -12898,6 +13128,11 @@ var DECIDE_MS = 100;
12898
13128
  var INIT_PID = 1;
12899
13129
  var SHARED_DIRS = ["/tmp", "/home"];
12900
13130
  var encoder3 = new TextEncoder();
13131
+ var USERS_MODES = ["off", "report", "enforce"];
13132
+ function usersMode(mode = "off") {
13133
+ if (USERS_MODES.includes(mode)) return mode;
13134
+ throw new Error(`users must be 'off', 'report' or 'enforce', not ${String(mode)}`);
13135
+ }
12901
13136
  function baseName(path) {
12902
13137
  return path.slice(path.lastIndexOf("/") + 1);
12903
13138
  }
@@ -13025,10 +13260,13 @@ var Launcher = class {
13025
13260
  processMounts;
13026
13261
  booting = new AbortController();
13027
13262
  fstab = Promise.resolve([]);
13263
+ usersMode;
13028
13264
  nextPid = 1e3;
13029
13265
  boot = Date.now();
13030
13266
  terminals = 0;
13031
13267
  constructor(options) {
13268
+ this.usersMode = usersMode(options.users);
13269
+ if (this.owners) options.fs.trackOwners?.();
13032
13270
  this.mounts = new MountTable({
13033
13271
  open: (type, spec) => this.driver(type, spec),
13034
13272
  busy: (target) => [...this.openFiles].some((nodes) => nodes.holds(target)) || heldUnder(this.held, target),
@@ -13082,7 +13320,7 @@ var Launcher = class {
13082
13320
  }
13083
13321
  );
13084
13322
  this.fs = this.unlinked.wrap(keepingOpen(fs, this.nodes));
13085
- this.users = new UserDb(this.fs);
13323
+ this.users = new UserDb(this.fs, this.owners);
13086
13324
  void this.unlinked.sweep("/");
13087
13325
  this.watchers.watch(
13088
13326
  [this.modulesDir, this.pnpmHome],
@@ -13094,6 +13332,9 @@ var Launcher = class {
13094
13332
  this.resetCatalogs();
13095
13333
  });
13096
13334
  }
13335
+ get owners() {
13336
+ return this.usersMode !== "off";
13337
+ }
13097
13338
  setRoutes(table) {
13098
13339
  this.routes.set(table);
13099
13340
  }
@@ -13352,6 +13593,7 @@ var Launcher = class {
13352
13593
  ...req.ignored ? { ignored: req.ignored } : {},
13353
13594
  ...req.umask !== void 0 ? { umask: req.umask } : {},
13354
13595
  cred: req.cred,
13596
+ ...this.owners ? { owners: true } : {},
13355
13597
  program: req.program,
13356
13598
  argv0: req.argv0,
13357
13599
  args: req.args,
@@ -13930,6 +14172,12 @@ function usersOp(l, req) {
13930
14172
  if (req.op === "users-remove") return l.users.remove(req.name, req.options);
13931
14173
  return l.users.list();
13932
14174
  }
14175
+ function hostOptions(req) {
14176
+ return {
14177
+ ...req.hostname !== void 0 ? { hostname: req.hostname } : {},
14178
+ ...req.users !== void 0 ? { users: req.users } : {}
14179
+ };
14180
+ }
13933
14181
  function serveKernel(port, deps) {
13934
14182
  let launcher;
13935
14183
  let remote;
@@ -14004,7 +14252,7 @@ function serveKernel(port, deps) {
14004
14252
  ...deps.createDriverWorker ? { createDriverWorker: deps.createDriverWorker } : {},
14005
14253
  ...req.modules ? { modules: req.modules } : {},
14006
14254
  ...req.env ? { env: req.env } : {},
14007
- ...req.hostname !== void 0 ? { hostname: req.hostname } : {},
14255
+ ...hostOptions(req),
14008
14256
  ...net,
14009
14257
  ...media ? { media } : {},
14010
14258
  onMountPending: (medium) => port.postMessage({ medium }),