@ai-ecoverse/slicc-kernel 1.56.1 → 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++) {
@@ -438,12 +564,34 @@ var OpfsFs = class {
438
564
  const link2 = await this.linkAlong(current);
439
565
  if (!link2) throw err;
440
566
  if (link2.path === current && !follow2) return { path: current, link: link2 };
441
- const rest2 = current.slice(link2.path.length);
442
- current = resolvePath(parentOf2(link2.path), `${link2.link}${rest2}`);
567
+ const target = `${link2.link}${current.slice(link2.path.length)}`;
568
+ current = target.split("/").includes("..") ? await this.physical(parentOf2(link2.path), target, hops + 1) : resolvePath(parentOf2(link2.path), target);
443
569
  }
444
570
  }
445
571
  throw fsError("ELOOP", path);
446
572
  }
573
+ async physical(base, path, used) {
574
+ let done4 = [];
575
+ let todo = (path.startsWith("/") ? path : `${base}/${path}`).split("/").filter((p) => p !== "" && p !== ".");
576
+ for (let hops = used; todo.length > 0; ) {
577
+ const part = todo.shift();
578
+ if (part === "..") done4.pop();
579
+ else if (!todo.includes("..")) done4.push(part);
580
+ else {
581
+ const at = `/${[...done4, part].join("/")}`;
582
+ const st = await this.lstat(at);
583
+ if (!st.isSymbolicLink && !st.isDirectory) throw fsError("ENOTDIR", at);
584
+ if (!st.isSymbolicLink) done4.push(part);
585
+ else if (++hops > MAX_LINKS) throw fsError("ELOOP", path);
586
+ else {
587
+ const link2 = await this.readlink(at);
588
+ if (link2.startsWith("/")) done4 = [];
589
+ todo = [...link2.split("/").filter((p) => p !== "" && p !== "."), ...todo];
590
+ }
591
+ }
592
+ }
593
+ return `/${done4.join("/")}`;
594
+ }
447
595
  mode(path, directory, meta) {
448
596
  const fallback = directory || EXECUTABLE.test(path) ? 493 : 420;
449
597
  return (directory ? S_IFDIR : S_IFREG) | (meta?.mode ?? fallback);
@@ -511,9 +659,15 @@ var OpfsFs = class {
511
659
  };
512
660
  }
513
661
  async describe(found) {
514
- if (found.link) return this.linkStat(found.link);
515
- const [meta] = await this.meta.get([found.path]);
516
- 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) };
517
671
  }
518
672
  async stat(path) {
519
673
  return this.describe(await this.locate(path, true));
@@ -603,14 +757,15 @@ var OpfsFs = class {
603
757
  (parent) => parent.getFileHandle(name2, { create: true })
604
758
  );
605
759
  if (!existed) this.touch(parentOf2(target));
606
- return [handle, existed];
760
+ return [handle, existed, target];
607
761
  } catch (err) {
608
762
  const code = err?.name === "TypeMismatchError" ? "EISDIR" : null;
609
763
  throw code ? fsError(code, path) : translate(err, path);
610
764
  }
611
765
  }
612
- async writeFile(path, content) {
613
- 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);
614
769
  if (!existed && content.length === 0) return;
615
770
  const writable = await file.createWritable();
616
771
  await writable.write(content);
@@ -635,11 +790,13 @@ var OpfsFs = class {
635
790
  const [dir, children2] = await this.listing(path);
636
791
  const base = dir === "/" ? "" : dir;
637
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;
638
795
  return Promise.all(
639
796
  children2.map(async ([name2, child], i) => {
640
- if (!isHandle(child)) return [name2, this.linkStat(child)];
797
+ if (!isHandle(child)) return [name2, owned(this.linkStat(child), child)];
641
798
  const stat = await this.statOf(`${base}/${name2}`, child, metas[i]).catch(() => null);
642
- return [name2, stat];
799
+ return [name2, stat && owned(stat, metas[i])];
643
800
  })
644
801
  );
645
802
  }
@@ -657,11 +814,22 @@ var OpfsFs = class {
657
814
  }
658
815
  const [entry] = await this.meta.get([path]);
659
816
  if (entry?.link !== void 0) throw fsError("EEXIST", path);
660
- 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
+ }
661
827
  const parent = await this.locate(parentOf2(path), true);
662
828
  if (parent.handle?.kind !== "directory") throw fsError("ENOTDIR", path);
663
829
  await parent.handle.getDirectoryHandle(baseOf(path), { create: true });
664
- 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);
665
833
  }
666
834
  async rm(path, options = {}) {
667
835
  let found;
@@ -708,9 +876,11 @@ var OpfsFs = class {
708
876
  throw fsError("ENOTEMPTY", to);
709
877
  }
710
878
  const now = Date.now();
879
+ const moving = await this.movedOwner(source);
711
880
  const carry = (entry) => ({
712
881
  ino: inodeOf(source.path),
713
882
  ...entry,
883
+ ...moving,
714
884
  path: destination,
715
885
  ctimeMs: now
716
886
  });
@@ -784,7 +954,7 @@ var OpfsFs = class {
784
954
  const target = await dir.getDirectoryHandle(name2, { create: true });
785
955
  for await (const child of handle.values()) await this.copy(child, target, child.name);
786
956
  }
787
- async symlink(target, path) {
957
+ async symlink(target, path, owner) {
788
958
  const parent = await this.locate(parentOf2(path), true);
789
959
  if (parent.handle?.kind !== "directory") throw fsError("ENOTDIR", path);
790
960
  const at = resolvePath(parent.path, baseOf(path));
@@ -795,13 +965,15 @@ var OpfsFs = class {
795
965
  throw fsError("EEXIST", path);
796
966
  }
797
967
  const now = Date.now();
968
+ const fields = await this.newFields(at, owner && { uid: owner.uid, gid: owner.gid }, false);
798
969
  await this.meta.update(at, () => ({
799
970
  path: at,
800
971
  link: target,
801
972
  dir: parent.path,
802
973
  ino: inodeOf(at),
803
974
  mtimeMs: now,
804
- ctimeMs: now
975
+ ctimeMs: now,
976
+ ...fields
805
977
  }));
806
978
  this.touch(parent.path);
807
979
  }
@@ -2445,12 +2617,30 @@ function userEnv(account, system) {
2445
2617
  PATH: `${pnpm}/bin:/usr/local/bin:/usr/bin:/bin`
2446
2618
  };
2447
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
+ }
2448
2626
  var UserDb = class {
2449
2627
  fs;
2628
+ owners;
2450
2629
  umasks = /* @__PURE__ */ new Map();
2451
2630
  writing = Promise.resolve();
2452
- constructor(fs) {
2631
+ constructor(fs, owners = false) {
2453
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);
2454
2644
  }
2455
2645
  async read(path, fallback) {
2456
2646
  try {
@@ -2550,8 +2740,12 @@ var UserDb = class {
2550
2740
  const users = parsed(passwd);
2551
2741
  const groups = parsed(group);
2552
2742
  const found = users.find((r) => r.name === name2);
2743
+ const shared = homeGroupOf(groups, options.homeGroup);
2553
2744
  if (found) {
2554
- 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
+ }
2555
2749
  remember();
2556
2750
  return this.account(found, groups);
2557
2751
  }
@@ -2571,6 +2765,7 @@ var UserDb = class {
2571
2765
  await this.fs.mkdir(home, { recursive: true });
2572
2766
  try {
2573
2767
  await this.provision(row);
2768
+ await this.stampHome(row, shared);
2574
2769
  await this.fs.mkdir("/etc", { recursive: true });
2575
2770
  await this.commit(formatPasswd(passwd), formatGroup(group));
2576
2771
  } catch (err) {
@@ -2639,7 +2834,7 @@ async function realpath(fs, path) {
2639
2834
  }
2640
2835
  return resolved || "/";
2641
2836
  }
2642
- async function fsCall(fs, method, args) {
2837
+ async function fsCall(fs, method, args, owner) {
2643
2838
  const path = (i) => fs.resolvePath("/", String(args[i]));
2644
2839
  switch (method) {
2645
2840
  case "readFile": {
@@ -2647,7 +2842,7 @@ async function fsCall(fs, method, args) {
2647
2842
  return { result: bytes, transfer: [bytes.buffer] };
2648
2843
  }
2649
2844
  case "writeFile":
2650
- await fs.writeFile(path(0), args[1]);
2845
+ await fs.writeFile(path(0), args[1], owner);
2651
2846
  return { result: true };
2652
2847
  case "stat":
2653
2848
  return { result: await fs.stat(path(0)) };
@@ -2656,7 +2851,7 @@ async function fsCall(fs, method, args) {
2656
2851
  case "readdir":
2657
2852
  return { result: await fs.readdir(path(0)) };
2658
2853
  case "mkdir":
2659
- await fs.mkdir(path(0), { recursive: true });
2854
+ await fs.mkdir(path(0), owner ? { recursive: true, owner } : { recursive: true });
2660
2855
  return { result: true };
2661
2856
  case "rm":
2662
2857
  await fs.rm(path(0), { recursive: true, force: args[1] === true });
@@ -2667,7 +2862,7 @@ async function fsCall(fs, method, args) {
2667
2862
  case "realpath":
2668
2863
  return { result: await realpath(fs, String(args[0])) };
2669
2864
  case "symlink":
2670
- await fs.symlink(String(args[0]), path(1));
2865
+ await fs.symlink(String(args[0]), path(1), owner);
2671
2866
  return { result: true };
2672
2867
  case "readlink":
2673
2868
  return { result: await fs.readlink(path(0)) };
@@ -2689,6 +2884,9 @@ function dialFor(l, req, open) {
2689
2884
  );
2690
2885
  return { result: port2, transfer: [port2] };
2691
2886
  }
2887
+ function ownerFor(l, self) {
2888
+ return l.owners && self ? { uid: self.uid, gid: self.gid } : void 0;
2889
+ }
2692
2890
  async function accountOf(l, user) {
2693
2891
  const found = await l.users.lookup(user);
2694
2892
  if (!found) throw fsError("ENOENT", `no user ${String(user)}`);
@@ -2816,6 +3014,7 @@ function serveClient(port, host) {
2816
3014
  hello: {
2817
3015
  protocol: PROTOCOL,
2818
3016
  traits: transport.traits,
3017
+ ...launcher.usersMode ? { users: launcher.usersMode } : {},
2819
3018
  ...host.lock ? { lock: host.lock } : {}
2820
3019
  }
2821
3020
  });
@@ -2881,7 +3080,7 @@ function serveClient(port, host) {
2881
3080
  if (!FS_METHODS.includes(req.method)) {
2882
3081
  throw new Error(`unknown file system call ${req.method}`);
2883
3082
  }
2884
- return fsCall(l.fs, req.method, req.args);
3083
+ return fsCall(l.fs, req.method, req.args, ownerFor(l, self));
2885
3084
  case "watch":
2886
3085
  case "unwatch":
2887
3086
  return watchOp(l, req, watches, send);
@@ -3904,10 +4103,10 @@ var RealmProxy = class {
3904
4103
  }
3905
4104
  async bridge(ctx, socket, hangup) {
3906
4105
  const stop = this.stop.signal;
3907
- let chain = Promise.resolve();
4106
+ let chain2 = Promise.resolve();
3908
4107
  const toGuest = (bytes) => {
3909
- chain = chain.then(() => ctx.sink.write(bytes, stop));
3910
- return chain;
4108
+ chain2 = chain2.then(() => ctx.sink.write(bytes, stop));
4109
+ return chain2;
3911
4110
  };
3912
4111
  const done4 = new AbortController();
3913
4112
  const reading = AbortSignal.any([stop, done4.signal, hangup]);
@@ -5614,7 +5813,7 @@ function withCommandDirs(fs, names, userDir = async () => void 0) {
5614
5813
  throw err;
5615
5814
  }
5616
5815
  },
5617
- writeFile: (path, content) => fs.writeFile(path, content),
5816
+ writeFile: (path, content, owner) => fs.writeFile(path, content, owner),
5618
5817
  async exists(path) {
5619
5818
  return await fs.exists(path) || await linked(path) !== void 0;
5620
5819
  },
@@ -5652,9 +5851,10 @@ function withCommandDirs(fs, names, userDir = async () => void 0) {
5652
5851
  mkdir: (path, options) => fs.mkdir(path, options),
5653
5852
  rm: (path, options) => fs.rm(path, options),
5654
5853
  rename: (from, to) => fs.rename(from, to),
5655
- symlink: (target, path) => fs.symlink(target, path),
5854
+ symlink: (target, path, owner) => fs.symlink(target, path, owner),
5656
5855
  readlink: (path) => fs.readlink(path),
5657
5856
  ...fs.realpath ? { realpath: fs.realpath.bind(fs) } : {},
5857
+ ...fs.chown ? { chown: fs.chown.bind(fs) } : {},
5658
5858
  chmod: (path, mode) => fs.chmod(path, mode),
5659
5859
  utimes: (path, atime, mtime) => fs.utimes(path, atime, mtime),
5660
5860
  lutimes: (path, atime, mtime) => lutimesOf(fs, path, atime, mtime),
@@ -5971,6 +6171,7 @@ var FsWatchers = class {
5971
6171
  rename: after(fs.rename.bind(fs), (from, to) => [from, to]),
5972
6172
  symlink: after(fs.symlink.bind(fs), (_target, path) => [path]),
5973
6173
  chmod: after(fs.chmod.bind(fs), (path) => [path]),
6174
+ ...fs.chown ? { chown: after(fs.chown.bind(fs), (path) => [path]) } : {},
5974
6175
  utimes: after(fs.utimes.bind(fs), (path) => [path]),
5975
6176
  lutimes: after(
5976
6177
  (path, atime, mtime) => lutimesOf(fs, path, atime, mtime),
@@ -6042,13 +6243,13 @@ var ChildTable = class {
6042
6243
  if (!this.forker) throw new SpawnError("ENOSYS");
6043
6244
  return this.track(state, this.parentFds.fork(), /* @__PURE__ */ new Map(), this.forker);
6044
6245
  }
6045
- async spawn(req, stdio, inherit = []) {
6246
+ async spawn(req, stdio, inherit2 = []) {
6046
6247
  if (!this.spawner) throw new SpawnError("ENOSYS");
6047
6248
  const fds = new FdTable();
6048
6249
  const captured = /* @__PURE__ */ new Map();
6049
6250
  try {
6050
6251
  for (const [n, slot] of stdio.entries()) fds.installAt(n, this.openSlot(slot, n, captured));
6051
- for (const slot of inherit) {
6252
+ for (const slot of inherit2) {
6052
6253
  if (slot.fd <= 2) continue;
6053
6254
  if ("device" in slot) {
6054
6255
  fds.installAt(slot.fd, deviceFile(slot.device, slot.access));
@@ -6129,8 +6330,8 @@ var ChildTable = class {
6129
6330
  candidates(pid, flags) {
6130
6331
  const all = [...this.children];
6131
6332
  if (pid > 0) return all.filter(([p]) => p === pid);
6132
- const inGroup = flags.inGroup;
6133
- 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));
6134
6335
  }
6135
6336
  async nextChange(candidates, flags, interrupt) {
6136
6337
  let wake;
@@ -6851,10 +7052,10 @@ var MountFs = class {
6851
7052
  }
6852
7053
  return concat4(chunks, total);
6853
7054
  }
6854
- async writeFile(path, content) {
7055
+ async writeFile(path, content, owner) {
6855
7056
  const real = await this.follow(path, 0, true);
6856
7057
  const found = this.at(real);
6857
- if (!found) return this.base.writeFile(real, content);
7058
+ if (!found) return this.base.writeFile(real, content, owner);
6858
7059
  const { mount, rel: rel2 } = found;
6859
7060
  this.writable(mount, real);
6860
7061
  const bytes = typeof content === "string" ? new TextEncoder().encode(content) : content;
@@ -7121,9 +7322,9 @@ var MountFs = class {
7121
7322
  this.table.forget(a.mount, normalizePath(to));
7122
7323
  }
7123
7324
  }
7124
- async symlink(target, path) {
7325
+ async symlink(target, path, owner) {
7125
7326
  const found = this.at(path);
7126
- if (!found) return this.base.symlink(target, path);
7327
+ if (!found) return this.base.symlink(target, path, owner);
7127
7328
  this.writable(found.mount, normalizePath(path));
7128
7329
  if (!found.mount.caps.symlinks)
7129
7330
  throw errnoError("EPERM", `${path}: this file system has no symlinks`);
@@ -7141,6 +7342,15 @@ var MountFs = class {
7141
7342
  if (!found) return this.base.readlink(path);
7142
7343
  return await found.mount.conn.call({ op: "readlink", path: found.rel });
7143
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
+ }
7144
7354
  async chmod(path, mode) {
7145
7355
  const found = this.at(path);
7146
7356
  if (!found) return this.base.chmod(path, mode);
@@ -7215,7 +7425,9 @@ function statJson(s) {
7215
7425
  ...s.readonly ? { readonly: true } : {},
7216
7426
  ...s.maxFile ? { maxFile: s.maxFile } : {},
7217
7427
  ...s.ranged ? { ranged: true } : {},
7218
- ...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 } : {}
7219
7431
  };
7220
7432
  }
7221
7433
  async function readdirStat(fs, dir) {
@@ -7230,13 +7442,34 @@ var done = { ok: true, kind: "void" };
7230
7442
  function json2(value) {
7231
7443
  return { ok: true, kind: "json", json: value };
7232
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
+ }
7233
7466
  async function run(entry, path, req) {
7234
7467
  const { fs, cwd } = entry;
7235
7468
  switch (req.op) {
7236
7469
  case "read":
7237
7470
  return { ok: true, kind: "bytes", bytes: await fs.readFileBuffer(path) };
7238
7471
  case "write":
7239
- await fs.writeFile(path, req.body ?? new Uint8Array(0));
7472
+ await fs.writeFile(path, req.body ?? new Uint8Array(0), ownerFor2(entry, 438));
7240
7473
  return done;
7241
7474
  case "exists":
7242
7475
  return json2(await fs.exists(path));
@@ -7249,7 +7482,7 @@ async function run(entry, path, req) {
7249
7482
  case "readdir-stat":
7250
7483
  return json2(await readdirStat(fs, path));
7251
7484
  case "mkdir":
7252
- await fs.mkdir(path, { recursive: true });
7485
+ await fs.mkdir(path, mkdirOptions(entry, req.mode));
7253
7486
  return done;
7254
7487
  case "rm":
7255
7488
  await fs.rm(path, { recursive: true });
@@ -7270,10 +7503,10 @@ async function run(entry, path, req) {
7270
7503
  await fs.rm(path, { recursive: true });
7271
7504
  return done;
7272
7505
  case "symlink":
7273
- await fs.symlink(req.arg2 ?? "", path);
7506
+ await fs.symlink(req.arg2 ?? "", path, ownerFor2(entry));
7274
7507
  return done;
7275
7508
  case "create":
7276
- await createExclusive(fs, path);
7509
+ await createExclusive(fs, path, ownerFor2(entry, req.mode ?? 438));
7277
7510
  return done;
7278
7511
  case "readlink":
7279
7512
  return json2(await fs.readlink(path));
@@ -7286,6 +7519,9 @@ async function run(entry, path, req) {
7286
7519
  case "lutimes":
7287
7520
  await lutimesOf(fs, path, new Date(req.atimeMs ?? 0), new Date(req.mtimeMs ?? 0));
7288
7521
  return done;
7522
+ case "chown":
7523
+ case "lchown":
7524
+ return chown(entry, path, req);
7289
7525
  case "pread":
7290
7526
  case "pwrite":
7291
7527
  case "truncate":
@@ -7294,14 +7530,14 @@ async function run(entry, path, req) {
7294
7530
  return { ok: false, errno: "EINVAL", message: `sync-fs: unknown op '${req.op}'` };
7295
7531
  }
7296
7532
  }
7297
- async function createExclusive(fs, path) {
7298
- if (fs.createExclusive) return fs.createExclusive(path);
7533
+ async function createExclusive(fs, path, owner) {
7534
+ if (fs.createExclusive) return fs.createExclusive(path, owner);
7299
7535
  if (await fs.lstat(path).then(
7300
7536
  () => true,
7301
7537
  () => false
7302
7538
  ))
7303
7539
  throw syncError("EEXIST", path);
7304
- await fs.writeFile(path, new Uint8Array(0));
7540
+ await fs.writeFile(path, new Uint8Array(0), owner);
7305
7541
  }
7306
7542
  async function ranged(fs, path, req) {
7307
7543
  const ops = rangedOps(fs);
@@ -9496,12 +9732,12 @@ var VfsNodes = class {
9496
9732
  }
9497
9733
  return walk();
9498
9734
  }
9499
- createExclusive(path) {
9735
+ createExclusive(path, owner) {
9500
9736
  return this.onEntry(path, async () => {
9501
9737
  const node = this.byPath.get(path);
9502
9738
  const held = node ? await node.serial(async () => node.holds()) : false;
9503
9739
  if (held || await present(this.fs, path)) throw fsError("EEXIST", path);
9504
- await this.fs.writeFile(path, new Uint8Array(0));
9740
+ await this.fs.writeFile(path, new Uint8Array(0), owner);
9505
9741
  });
9506
9742
  }
9507
9743
  open(path, pin, writable = false) {
@@ -9631,6 +9867,11 @@ var MAX_LINKS5 = 40;
9631
9867
  function parentOf6(path) {
9632
9868
  return path.slice(0, path.lastIndexOf("/")) || "/";
9633
9869
  }
9870
+ async function mustBeDir(fs, path) {
9871
+ if (!fs.lstat) return;
9872
+ const st = await fs.lstat(path);
9873
+ if (st.isDirectory !== true) throw fsError("ENOTDIR", path);
9874
+ }
9634
9875
  async function linkOf(fs, path) {
9635
9876
  return fs.readlink?.(path).catch(() => void 0);
9636
9877
  }
@@ -9647,6 +9888,7 @@ async function realDir(fs, dir, budget = { hops: 0 }) {
9647
9888
  const next2 = at === "/" ? `/${part}` : `${at}/${part}`;
9648
9889
  const link2 = await linkOf(fs, next2);
9649
9890
  if (link2 === void 0) {
9891
+ if (queue.find((p) => p !== ".") === "..") await mustBeDir(fs, next2);
9650
9892
  at = next2;
9651
9893
  continue;
9652
9894
  }
@@ -9712,9 +9954,9 @@ function keepingOpen(fs, nodes) {
9712
9954
  nodes.renamed(a, b);
9713
9955
  });
9714
9956
  },
9715
- writeFile: (path, content) => nodes.onEntry(fs.resolvePath("/", path), () => fs.writeFile(path, content)),
9716
- createExclusive: async (path) => nodes.createExclusive(await nodes.entryKey(fs.resolvePath("/", path))),
9717
- 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)),
9718
9960
  mkdir: (path, options) => nodes.onEntry(fs.resolvePath("/", path), () => fs.mkdir(path, options))
9719
9961
  };
9720
9962
  }
@@ -9985,9 +10227,9 @@ var WasmProcess = class {
9985
10227
  return "deliver";
9986
10228
  }
9987
10229
  async spawn(req) {
9988
- const { file, argv, env, cwd, stdio, inherit, exec } = req;
10230
+ const { file, argv, env, cwd, stdio, inherit: inherit2, exec } = req;
9989
10231
  const spawned = { file, argv, env, cwd, ...exec ? { exec } : {} };
9990
- const pid = await this.children.spawn(spawned, stdio, inherit);
10232
+ const pid = await this.children.spawn(spawned, stdio, inherit2);
9991
10233
  if (!exec) return pid;
9992
10234
  if (this.killed) void Promise.allSettled([this.options.kill?.(pid, SIG.KILL)]);
9993
10235
  else this.adoptExec(pid);
@@ -10152,12 +10394,14 @@ var WasmProcess = class {
10152
10394
  }
10153
10395
  const open = { ...req, path };
10154
10396
  if (req.exclusive) {
10155
- await nodes.createExclusive(path);
10397
+ await nodes.createExclusive(path, this.newOwner());
10156
10398
  this.fds.installAt(fd, this.openVfsFile(open));
10157
10399
  } else if (req.create) {
10158
10400
  await nodes.onEntry(path, async () => {
10401
+ const fresh = this.options.owners && !await present(this.options.fs, path, true);
10159
10402
  this.fds.installAt(fd, this.openVfsFile(open));
10160
10403
  await this.fds.get(fd).file.stat?.();
10404
+ if (fresh) await this.options.fs.chown?.(path, { ...this.newOwner(), kind: "new" });
10161
10405
  });
10162
10406
  } else {
10163
10407
  this.fds.installAt(fd, this.openVfsFile(open));
@@ -10171,6 +10415,10 @@ var WasmProcess = class {
10171
10415
  }
10172
10416
  return fd;
10173
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
+ }
10174
10422
  async openProc(req, path) {
10175
10423
  return this.fds.install(await this.procFile(req, path), 3);
10176
10424
  }
@@ -10817,6 +11065,7 @@ function processInit(opts, sab, umask, cred, lane) {
10817
11065
  sab,
10818
11066
  umask,
10819
11067
  cred,
11068
+ ...opts.owners ? { owners: true } : {},
10820
11069
  ...opts.ppid !== void 0 ? { ppid: opts.ppid } : {}
10821
11070
  };
10822
11071
  if (opts.program.abi === "js") {
@@ -10840,8 +11089,9 @@ function inherited(opts) {
10840
11089
  ...opts.cred ? { cred: opts.cred } : {}
10841
11090
  };
10842
11091
  }
10843
- function holdingToken(opts, holds) {
11092
+ function holdingToken(opts, holds, caller) {
10844
11093
  return mintSyncFsToken({
11094
+ ...opts.owners ? caller : {},
10845
11095
  fs: opts.fs,
10846
11096
  cwd: opts.cwd,
10847
11097
  ...opts.statfs ? { statfs: opts.statfs } : {},
@@ -10857,6 +11107,7 @@ function holdingToken(opts, holds) {
10857
11107
  function processOptions(opts, header3, hooks) {
10858
11108
  return {
10859
11109
  ...inherited(opts),
11110
+ ...opts.owners ? { owners: true } : {},
10860
11111
  ...opts.identity ? { identity: opts.identity } : {},
10861
11112
  ...opts.onSyscall ? { onSyscall: opts.onSyscall } : {},
10862
11113
  ...opts.settling ? { settling: opts.settling } : {},
@@ -10901,7 +11152,10 @@ function spawnWasmProcess(opts) {
10901
11152
  publishStops(process, header3);
10902
11153
  const holds = new HeldPaths();
10903
11154
  opts.held?.add(holds);
10904
- const token = holdingToken(opts, holds);
11155
+ const token = holdingToken(opts, holds, {
11156
+ cred: () => process.cred,
11157
+ umask: () => process.umask
11158
+ });
10905
11159
  process.keptPath = (path) => holds.keptAt(path)?.hidden;
10906
11160
  process.keptHandle = (handle2) => holds.keptFor(handle2)?.hidden;
10907
11161
  const worker = opts.createWorker();
@@ -11262,26 +11516,26 @@ function replayingHops(inner, heldMs = HELD_MS) {
11262
11516
  void entry.final.cancel().catch(() => void 0);
11263
11517
  };
11264
11518
  const keyOf2 = (owner, method, url) => `${owner} ${method} ${url}`;
11265
- const hold = (key, chain) => {
11519
+ const hold = (key, chain2) => {
11266
11520
  drop(key);
11267
- 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);
11268
11522
  if (mine.length >= HELD_PER_OWNER) drop(mine[0]);
11269
11523
  if (held.size >= HELD_MAX) drop(held.keys().next().value);
11270
11524
  const timer = setTimeout(() => drop(key), heldMs);
11271
11525
  timer.unref?.();
11272
- held.set(key, { ...chain, timer });
11526
+ held.set(key, { ...chain2, timer });
11273
11527
  };
11274
- const replay = (chain) => {
11275
- const [hop, ...rest2] = chain.hops;
11528
+ const replay = (chain2) => {
11529
+ const [hop, ...rest2] = chain2.hops;
11276
11530
  const location = hop && header2(hop.headers, "location");
11277
- if (!hop || location === void 0) return chain.final;
11531
+ if (!hop || location === void 0) return chain2.final;
11278
11532
  const target = new URL(location, hop.url);
11279
11533
  target.hash = "";
11280
- const method = followedWith(hop.status, chain.method);
11534
+ const method = followedWith(hop.status, chain2.method);
11281
11535
  const away = target.origin !== new URL(hop.url).origin;
11282
- const print = away ? unauthorized(chain.fingerprint) : chain.fingerprint;
11283
- hold(keyOf2(chain.owner, method, target.href), {
11284
- ...chain,
11536
+ const print = away ? unauthorized(chain2.fingerprint) : chain2.fingerprint;
11537
+ hold(keyOf2(chain2.owner, method, target.href), {
11538
+ ...chain2,
11285
11539
  method,
11286
11540
  fingerprint: print,
11287
11541
  hops: rest2
@@ -12870,6 +13124,11 @@ var DECIDE_MS = 100;
12870
13124
  var INIT_PID = 1;
12871
13125
  var SHARED_DIRS = ["/tmp", "/home"];
12872
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
+ }
12873
13132
  function baseName(path) {
12874
13133
  return path.slice(path.lastIndexOf("/") + 1);
12875
13134
  }
@@ -12997,10 +13256,13 @@ var Launcher = class {
12997
13256
  processMounts;
12998
13257
  booting = new AbortController();
12999
13258
  fstab = Promise.resolve([]);
13259
+ usersMode;
13000
13260
  nextPid = 1e3;
13001
13261
  boot = Date.now();
13002
13262
  terminals = 0;
13003
13263
  constructor(options) {
13264
+ this.usersMode = usersMode(options.users);
13265
+ if (this.owners) options.fs.trackOwners?.();
13004
13266
  this.mounts = new MountTable({
13005
13267
  open: (type, spec) => this.driver(type, spec),
13006
13268
  busy: (target) => [...this.openFiles].some((nodes) => nodes.holds(target)) || heldUnder(this.held, target),
@@ -13054,7 +13316,7 @@ var Launcher = class {
13054
13316
  }
13055
13317
  );
13056
13318
  this.fs = this.unlinked.wrap(keepingOpen(fs, this.nodes));
13057
- this.users = new UserDb(this.fs);
13319
+ this.users = new UserDb(this.fs, this.owners);
13058
13320
  void this.unlinked.sweep("/");
13059
13321
  this.watchers.watch(
13060
13322
  [this.modulesDir, this.pnpmHome],
@@ -13066,6 +13328,9 @@ var Launcher = class {
13066
13328
  this.resetCatalogs();
13067
13329
  });
13068
13330
  }
13331
+ get owners() {
13332
+ return this.usersMode !== "off";
13333
+ }
13069
13334
  setRoutes(table) {
13070
13335
  this.routes.set(table);
13071
13336
  }
@@ -13324,6 +13589,7 @@ var Launcher = class {
13324
13589
  ...req.ignored ? { ignored: req.ignored } : {},
13325
13590
  ...req.umask !== void 0 ? { umask: req.umask } : {},
13326
13591
  cred: req.cred,
13592
+ ...this.owners ? { owners: true } : {},
13327
13593
  program: req.program,
13328
13594
  argv0: req.argv0,
13329
13595
  args: req.args,
@@ -13902,6 +14168,12 @@ function usersOp(l, req) {
13902
14168
  if (req.op === "users-remove") return l.users.remove(req.name, req.options);
13903
14169
  return l.users.list();
13904
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
+ }
13905
14177
  function serveKernel(port, deps) {
13906
14178
  let launcher;
13907
14179
  let remote;
@@ -13976,7 +14248,7 @@ function serveKernel(port, deps) {
13976
14248
  ...deps.createDriverWorker ? { createDriverWorker: deps.createDriverWorker } : {},
13977
14249
  ...req.modules ? { modules: req.modules } : {},
13978
14250
  ...req.env ? { env: req.env } : {},
13979
- ...req.hostname !== void 0 ? { hostname: req.hostname } : {},
14251
+ ...hostOptions(req),
13980
14252
  ...net,
13981
14253
  ...media ? { media } : {},
13982
14254
  onMountPending: (medium) => port.postMessage({ medium }),