@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.
package/dist/node.js CHANGED
@@ -1753,12 +1753,30 @@ function userEnv(account, system) {
1753
1753
  PATH: `${pnpm}/bin:/usr/local/bin:/usr/bin:/bin`
1754
1754
  };
1755
1755
  }
1756
+ function homeGroupOf(groups, name2) {
1757
+ if (name2 === void 0) return void 0;
1758
+ const found = groups.find((g) => g.name === name2);
1759
+ if (!found) throw fsError("ENOENT", `no group ${name2}`);
1760
+ return found;
1761
+ }
1756
1762
  var UserDb = class {
1757
1763
  fs;
1764
+ owners;
1758
1765
  umasks = /* @__PURE__ */ new Map();
1759
1766
  writing = Promise.resolve();
1760
- constructor(fs) {
1767
+ constructor(fs, owners = false) {
1761
1768
  this.fs = fs;
1769
+ this.owners = owners;
1770
+ }
1771
+ async stampHome(row, shared) {
1772
+ const home = this.fs.resolvePath("/", row.home);
1773
+ if (!this.owners || !this.fs.chown || !HOME_DIR.test(home)) return;
1774
+ if (await this.fs.realpath?.(home, false).catch(() => void 0) !== home) return;
1775
+ if (!(await this.fs.lstat(home)).isDirectory) return;
1776
+ const gid = shared?.gid ?? row.gid;
1777
+ await this.fs.chown(home, { uid: row.uid, gid, follow: false, kind: "adopt" });
1778
+ const mode = shared ? 1512 : row.name.startsWith("scoop-") ? 488 : 448;
1779
+ await this.fs.chmod(home, mode);
1762
1780
  }
1763
1781
  async read(path, fallback) {
1764
1782
  try {
@@ -1858,8 +1876,12 @@ var UserDb = class {
1858
1876
  const users = parsed(passwd);
1859
1877
  const groups = parsed(group);
1860
1878
  const found = users.find((r) => r.name === name2);
1879
+ const shared = homeGroupOf(groups, options.homeGroup);
1861
1880
  if (found) {
1862
- if (found.uid >= FIRST_UID && found.uid <= LAST_UID) await this.provision(found);
1881
+ if (found.uid >= FIRST_UID && found.uid <= LAST_UID) {
1882
+ await this.provision(found);
1883
+ await this.stampHome(found, shared);
1884
+ }
1863
1885
  remember();
1864
1886
  return this.account(found, groups);
1865
1887
  }
@@ -1879,6 +1901,7 @@ var UserDb = class {
1879
1901
  await this.fs.mkdir(home, { recursive: true });
1880
1902
  try {
1881
1903
  await this.provision(row);
1904
+ await this.stampHome(row, shared);
1882
1905
  await this.fs.mkdir("/etc", { recursive: true });
1883
1906
  await this.commit(formatPasswd(passwd), formatGroup(group));
1884
1907
  } catch (err) {
@@ -1974,7 +1997,7 @@ async function realpath(fs, path) {
1974
1997
  }
1975
1998
  return resolved || "/";
1976
1999
  }
1977
- async function fsCall(fs, method, args) {
2000
+ async function fsCall(fs, method, args, owner) {
1978
2001
  const path = (i) => fs.resolvePath("/", String(args[i]));
1979
2002
  switch (method) {
1980
2003
  case "readFile": {
@@ -1982,7 +2005,7 @@ async function fsCall(fs, method, args) {
1982
2005
  return { result: bytes, transfer: [bytes.buffer] };
1983
2006
  }
1984
2007
  case "writeFile":
1985
- await fs.writeFile(path(0), args[1]);
2008
+ await fs.writeFile(path(0), args[1], owner);
1986
2009
  return { result: true };
1987
2010
  case "stat":
1988
2011
  return { result: await fs.stat(path(0)) };
@@ -1991,7 +2014,7 @@ async function fsCall(fs, method, args) {
1991
2014
  case "readdir":
1992
2015
  return { result: await fs.readdir(path(0)) };
1993
2016
  case "mkdir":
1994
- await fs.mkdir(path(0), { recursive: true });
2017
+ await fs.mkdir(path(0), owner ? { recursive: true, owner } : { recursive: true });
1995
2018
  return { result: true };
1996
2019
  case "rm":
1997
2020
  await fs.rm(path(0), { recursive: true, force: args[1] === true });
@@ -2002,7 +2025,7 @@ async function fsCall(fs, method, args) {
2002
2025
  case "realpath":
2003
2026
  return { result: await realpath(fs, String(args[0])) };
2004
2027
  case "symlink":
2005
- await fs.symlink(String(args[0]), path(1));
2028
+ await fs.symlink(String(args[0]), path(1), owner);
2006
2029
  return { result: true };
2007
2030
  case "readlink":
2008
2031
  return { result: await fs.readlink(path(0)) };
@@ -2024,6 +2047,9 @@ function dialFor(l, req, open) {
2024
2047
  );
2025
2048
  return { result: port2, transfer: [port2] };
2026
2049
  }
2050
+ function ownerFor(l, self) {
2051
+ return l.owners && self ? { uid: self.uid, gid: self.gid } : void 0;
2052
+ }
2027
2053
  async function accountOf(l, user) {
2028
2054
  const found = await l.users.lookup(user);
2029
2055
  if (!found) throw fsError("ENOENT", `no user ${String(user)}`);
@@ -2151,6 +2177,7 @@ function serveClient(port, host) {
2151
2177
  hello: {
2152
2178
  protocol: PROTOCOL,
2153
2179
  traits: transport.traits,
2180
+ ...launcher.usersMode ? { users: launcher.usersMode } : {},
2154
2181
  ...host.lock ? { lock: host.lock } : {}
2155
2182
  }
2156
2183
  });
@@ -2216,7 +2243,7 @@ function serveClient(port, host) {
2216
2243
  if (!FS_METHODS.includes(req.method)) {
2217
2244
  throw new Error(`unknown file system call ${req.method}`);
2218
2245
  }
2219
- return fsCall(l.fs, req.method, req.args);
2246
+ return fsCall(l.fs, req.method, req.args, ownerFor(l, self));
2220
2247
  case "watch":
2221
2248
  case "unwatch":
2222
2249
  return watchOp(l, req, watches, send);
@@ -2324,11 +2351,69 @@ var MemoryMeta = class {
2324
2351
  }
2325
2352
  };
2326
2353
 
2354
+ // src/fs/owners.ts
2355
+ var ROOT_OWNER = Object.freeze({ uid: 0, gid: 0 });
2356
+ var S_ISUID = 2048;
2357
+ var S_ISGID = 1024;
2358
+ var S_IXGRP = 8;
2359
+ function ownOf(entry) {
2360
+ if (entry?.uid === void 0 || entry.gid === void 0) return void 0;
2361
+ return { uid: entry.uid, gid: entry.gid };
2362
+ }
2363
+ function handedBy(entry) {
2364
+ return entry?.heir ? { uid: entry.heir[0], gid: entry.heir[1] } : ownOf(entry);
2365
+ }
2366
+ function inherit(entries) {
2367
+ for (let i = entries.length - 1; i >= 0; i--) {
2368
+ const found = handedBy(entries[i]);
2369
+ if (found) return found;
2370
+ }
2371
+ return { ...ROOT_OWNER };
2372
+ }
2373
+ function chain(path) {
2374
+ const parts = path.split("/").filter(Boolean);
2375
+ return ["/", ...parts.map((_, i) => `/${parts.slice(0, i + 1).join("/")}`)];
2376
+ }
2377
+ function sameOwner(a, b) {
2378
+ return a.uid === b.uid && a.gid === b.gid;
2379
+ }
2380
+ function ownerOf(cred, mode) {
2381
+ return { uid: cred.euid, gid: cred.egid, ...mode !== void 0 ? { mode } : {} };
2382
+ }
2383
+ function inGroup(cred, gid) {
2384
+ return cred.egid === gid || cred.groups.includes(gid);
2385
+ }
2386
+ function mayChown(cred, current, uid, gid) {
2387
+ if (cred.euid === 0) return true;
2388
+ const owner = cred.euid === current.uid;
2389
+ if (uid !== void 0 && !(owner && uid === current.uid)) return false;
2390
+ if (gid !== void 0 && !(owner && (gid === current.gid || inGroup(cred, gid)))) return false;
2391
+ return true;
2392
+ }
2393
+ function clearedSetId(mode) {
2394
+ const bits2 = S_ISUID | (mode & S_IXGRP ? S_ISGID : 0);
2395
+ return mode & 4095 & ~bits2;
2396
+ }
2397
+ async function chownAs(fs, cred, path, change) {
2398
+ const { uid, gid, follow: follow2 } = change;
2399
+ const st = follow2 ? await fs.stat(path) : await fs.lstat(path);
2400
+ const current = { uid: st.uid ?? 0, gid: st.gid ?? 0 };
2401
+ if (!fs.chown || !mayChown(cred, current, uid, gid)) throw fsError("EPERM", path);
2402
+ const clear = st.isFile && (uid !== void 0 || gid !== void 0) ? clearedSetId(st.mode) : void 0;
2403
+ await fs.chown(path, {
2404
+ ...uid !== void 0 ? { uid } : {},
2405
+ ...gid !== void 0 ? { gid } : {},
2406
+ follow: follow2,
2407
+ ...clear !== void 0 && clear !== (st.mode & 4095) ? { mode: clear } : {}
2408
+ });
2409
+ }
2410
+
2327
2411
  // src/fs/opfs.ts
2328
2412
  var S_IFDIR = 16384;
2329
2413
  var S_IFREG = 32768;
2330
2414
  var S_IFLNK = 40960;
2331
2415
  var PERM_MASK = 4095;
2416
+ var S_ISGID2 = 1024;
2332
2417
  var READ_ATTEMPTS = 3;
2333
2418
  var MAX_LINKS2 = 40;
2334
2419
  var CHECK_MS = 1e3;
@@ -2426,6 +2511,7 @@ var OpfsFs = class {
2426
2511
  checked = /* @__PURE__ */ new WeakMap();
2427
2512
  channel;
2428
2513
  ranged;
2514
+ owners = false;
2429
2515
  constructor(root, meta = new MemoryMeta(), channel, options = {}) {
2430
2516
  this.root = root;
2431
2517
  this.meta = meta;
@@ -2445,6 +2531,73 @@ var OpfsFs = class {
2445
2531
  resolvePath(base, path) {
2446
2532
  return resolvePath(base, path);
2447
2533
  }
2534
+ trackOwners() {
2535
+ this.owners = true;
2536
+ }
2537
+ async handedTo(dir) {
2538
+ return inherit(await this.meta.get(chain(dir)));
2539
+ }
2540
+ async ownerAt(path, entry) {
2541
+ const own = ownOf(entry);
2542
+ if (own) return own;
2543
+ return path === "/" ? { ...ROOT_OWNER } : this.handedTo(parentOf2(path));
2544
+ }
2545
+ async owned(stat, path, entry) {
2546
+ return this.owners ? { ...stat, ...await this.ownerAt(path, entry) } : stat;
2547
+ }
2548
+ async newFields(path, owner, directory) {
2549
+ if (!this.owners || !owner) return void 0;
2550
+ const entries = await this.meta.get(chain(path));
2551
+ const self = entries.pop();
2552
+ const parent = entries.at(-1);
2553
+ const setgid = ((parent?.mode ?? 0) & S_ISGID2) !== 0;
2554
+ const parentGid = (ownOf(parent) ?? inherit(entries.slice(0, -1))).gid;
2555
+ const want = { uid: owner.uid, gid: setgid ? parentGid : owner.gid };
2556
+ let mode = owner.mode === void 0 ? void 0 : owner.mode & PERM_MASK;
2557
+ if (directory && setgid) mode = (mode ?? 493) | S_ISGID2;
2558
+ const fields = {};
2559
+ if (mode !== void 0 && mode !== (this.mode(path, directory, void 0) & PERM_MASK)) {
2560
+ fields.mode = mode;
2561
+ }
2562
+ if (self) fields.heir = void 0;
2563
+ if (ownOf(self) || !sameOwner(want, inherit(entries))) Object.assign(fields, want);
2564
+ return Object.keys(fields).length > 0 ? fields : void 0;
2565
+ }
2566
+ async stampNew(path, owner, directory) {
2567
+ const fields = await this.newFields(path, owner, directory);
2568
+ if (fields) await this.meta.update(path, (entry) => ({ ...entry, path, ...fields }));
2569
+ }
2570
+ async chown(path, change) {
2571
+ if (!this.owners) return;
2572
+ if (change.kind === "new") {
2573
+ const at = resolvePath((await this.locate(parentOf2(path), true)).path, baseOf(path));
2574
+ const { uid = 0, gid = 0, mode } = change;
2575
+ return this.stampNew(at, { uid, gid, ...mode !== void 0 ? { mode } : {} }, false);
2576
+ }
2577
+ const found = await this.locate(path, change.follow !== false);
2578
+ const [stored] = found.link ? [found.link] : await this.meta.get([found.path]);
2579
+ const current = await this.ownerAt(found.path, stored);
2580
+ const next2 = { uid: change.uid ?? current.uid, gid: change.gid ?? current.gid };
2581
+ const handing = found.handle?.kind === "directory" && !sameOwner(next2, current);
2582
+ const heir = change.kind === "adopt" ? void 0 : stored?.heir ?? (handing ? [current.uid, current.gid] : void 0);
2583
+ const ctimeMs = Date.now();
2584
+ await this.meta.update(found.path, (entry) => {
2585
+ const { heir: _, ...rest2 } = entry ?? found.link ?? { path: found.path };
2586
+ return {
2587
+ ...rest2,
2588
+ path: found.path,
2589
+ ...next2,
2590
+ ...heir ? { heir } : {},
2591
+ ...change.mode !== void 0 ? { mode: change.mode & PERM_MASK } : {},
2592
+ ctimeMs
2593
+ };
2594
+ });
2595
+ }
2596
+ async movedOwner(source) {
2597
+ if (!this.owners) return void 0;
2598
+ const [stored] = source.link ? [source.link] : await this.meta.get([source.path]);
2599
+ return ownOf(stored) ? void 0 : this.ownerAt(source.path, stored);
2600
+ }
2448
2601
  async walk(parts, from, start2) {
2449
2602
  let dir = start2;
2450
2603
  for (let at = from; at < parts.length; at++) {
@@ -2637,9 +2790,15 @@ var OpfsFs = class {
2637
2790
  };
2638
2791
  }
2639
2792
  async describe(found) {
2640
- if (found.link) return this.linkStat(found.link);
2641
- const [meta] = await this.meta.get([found.path]);
2642
- return this.statOf(found.path, found.handle, meta);
2793
+ if (found.link) return this.owned(this.linkStat(found.link), found.path, found.link);
2794
+ if (!this.owners) {
2795
+ const [meta2] = await this.meta.get([found.path]);
2796
+ return this.statOf(found.path, found.handle, meta2);
2797
+ }
2798
+ const entries = await this.meta.get(chain(found.path));
2799
+ const meta = entries.pop();
2800
+ const stat = await this.statOf(found.path, found.handle, meta);
2801
+ return { ...stat, ...ownOf(meta) ?? inherit(entries) };
2643
2802
  }
2644
2803
  async stat(path) {
2645
2804
  return this.describe(await this.locate(path, true));
@@ -2729,14 +2888,15 @@ var OpfsFs = class {
2729
2888
  (parent) => parent.getFileHandle(name2, { create: true })
2730
2889
  );
2731
2890
  if (!existed) this.touch(parentOf2(target));
2732
- return [handle, existed];
2891
+ return [handle, existed, target];
2733
2892
  } catch (err) {
2734
2893
  const code = err?.name === "TypeMismatchError" ? "EISDIR" : null;
2735
2894
  throw code ? fsError(code, path) : translate(err, path);
2736
2895
  }
2737
2896
  }
2738
- async writeFile(path, content) {
2739
- const [file, existed] = await this.fileFor(path);
2897
+ async writeFile(path, content, owner) {
2898
+ const [file, existed, target] = await this.fileFor(path);
2899
+ if (!existed) await this.stampNew(target, owner, false);
2740
2900
  if (!existed && content.length === 0) return;
2741
2901
  const writable = await file.createWritable();
2742
2902
  await writable.write(content);
@@ -2761,11 +2921,13 @@ var OpfsFs = class {
2761
2921
  const [dir, children2] = await this.listing(path);
2762
2922
  const base = dir === "/" ? "" : dir;
2763
2923
  const metas = await this.meta.get(children2.map(([name2]) => `${base}/${name2}`));
2924
+ const handed2 = this.owners ? await this.handedTo(dir) : void 0;
2925
+ const owned = (stat, entry) => handed2 ? { ...stat, ...ownOf(entry) ?? handed2 } : stat;
2764
2926
  return Promise.all(
2765
2927
  children2.map(async ([name2, child], i) => {
2766
- if (!isHandle(child)) return [name2, this.linkStat(child)];
2928
+ if (!isHandle(child)) return [name2, owned(this.linkStat(child), child)];
2767
2929
  const stat = await this.statOf(`${base}/${name2}`, child, metas[i]).catch(() => null);
2768
- return [name2, stat];
2930
+ return [name2, stat && owned(stat, metas[i])];
2769
2931
  })
2770
2932
  );
2771
2933
  }
@@ -2783,11 +2945,22 @@ var OpfsFs = class {
2783
2945
  }
2784
2946
  const [entry] = await this.meta.get([path]);
2785
2947
  if (entry?.link !== void 0) throw fsError("EEXIST", path);
2786
- if (options.recursive) await this.mkdir(parentOf2(path), options);
2948
+ const { owner } = options;
2949
+ if (options.recursive) {
2950
+ const mode = owner?.parents;
2951
+ const above = owner && {
2952
+ uid: owner.uid,
2953
+ gid: owner.gid,
2954
+ ...mode ? { mode, parents: mode } : {}
2955
+ };
2956
+ await this.mkdir(parentOf2(path), { recursive: true, ...above ? { owner: above } : {} });
2957
+ }
2787
2958
  const parent = await this.locate(parentOf2(path), true);
2788
2959
  if (parent.handle?.kind !== "directory") throw fsError("ENOTDIR", path);
2789
2960
  await parent.handle.getDirectoryHandle(baseOf(path), { create: true });
2790
- this.touch(parent.path, `${parent.path === "/" ? "" : parent.path}/${baseOf(path)}`);
2961
+ const made = `${parent.path === "/" ? "" : parent.path}/${baseOf(path)}`;
2962
+ await this.stampNew(made, owner, true);
2963
+ this.touch(parent.path, made);
2791
2964
  }
2792
2965
  async rm(path, options = {}) {
2793
2966
  let found;
@@ -2834,9 +3007,11 @@ var OpfsFs = class {
2834
3007
  throw fsError("ENOTEMPTY", to);
2835
3008
  }
2836
3009
  const now = Date.now();
3010
+ const moving = await this.movedOwner(source);
2837
3011
  const carry = (entry) => ({
2838
3012
  ino: inodeOf(source.path),
2839
3013
  ...entry,
3014
+ ...moving,
2840
3015
  path: destination,
2841
3016
  ctimeMs: now
2842
3017
  });
@@ -2910,7 +3085,7 @@ var OpfsFs = class {
2910
3085
  const target = await dir.getDirectoryHandle(name2, { create: true });
2911
3086
  for await (const child of handle.values()) await this.copy(child, target, child.name);
2912
3087
  }
2913
- async symlink(target, path) {
3088
+ async symlink(target, path, owner) {
2914
3089
  const parent = await this.locate(parentOf2(path), true);
2915
3090
  if (parent.handle?.kind !== "directory") throw fsError("ENOTDIR", path);
2916
3091
  const at = resolvePath(parent.path, baseOf(path));
@@ -2921,13 +3096,15 @@ var OpfsFs = class {
2921
3096
  throw fsError("EEXIST", path);
2922
3097
  }
2923
3098
  const now = Date.now();
3099
+ const fields = await this.newFields(at, owner && { uid: owner.uid, gid: owner.gid }, false);
2924
3100
  await this.meta.update(at, () => ({
2925
3101
  path: at,
2926
3102
  link: target,
2927
3103
  dir: parent.path,
2928
3104
  ino: inodeOf(at),
2929
3105
  mtimeMs: now,
2930
- ctimeMs: now
3106
+ ctimeMs: now,
3107
+ ...fields
2931
3108
  }));
2932
3109
  this.touch(parent.path);
2933
3110
  }
@@ -4234,7 +4411,7 @@ function withCommandDirs(fs, names, userDir = async () => void 0) {
4234
4411
  throw err;
4235
4412
  }
4236
4413
  },
4237
- writeFile: (path, content) => fs.writeFile(path, content),
4414
+ writeFile: (path, content, owner) => fs.writeFile(path, content, owner),
4238
4415
  async exists(path) {
4239
4416
  return await fs.exists(path) || await linked(path) !== void 0;
4240
4417
  },
@@ -4272,9 +4449,10 @@ function withCommandDirs(fs, names, userDir = async () => void 0) {
4272
4449
  mkdir: (path, options) => fs.mkdir(path, options),
4273
4450
  rm: (path, options) => fs.rm(path, options),
4274
4451
  rename: (from, to) => fs.rename(from, to),
4275
- symlink: (target, path) => fs.symlink(target, path),
4452
+ symlink: (target, path, owner) => fs.symlink(target, path, owner),
4276
4453
  readlink: (path) => fs.readlink(path),
4277
4454
  ...fs.realpath ? { realpath: fs.realpath.bind(fs) } : {},
4455
+ ...fs.chown ? { chown: fs.chown.bind(fs) } : {},
4278
4456
  chmod: (path, mode) => fs.chmod(path, mode),
4279
4457
  utimes: (path, atime, mtime) => fs.utimes(path, atime, mtime),
4280
4458
  lutimes: (path, atime, mtime) => lutimesOf(fs, path, atime, mtime),
@@ -4400,6 +4578,7 @@ var FsWatchers = class {
4400
4578
  rename: after(fs.rename.bind(fs), (from, to) => [from, to]),
4401
4579
  symlink: after(fs.symlink.bind(fs), (_target, path) => [path]),
4402
4580
  chmod: after(fs.chmod.bind(fs), (path) => [path]),
4581
+ ...fs.chown ? { chown: after(fs.chown.bind(fs), (path) => [path]) } : {},
4403
4582
  utimes: after(fs.utimes.bind(fs), (path) => [path]),
4404
4583
  lutimes: after(
4405
4584
  (path, atime, mtime) => lutimesOf(fs, path, atime, mtime),
@@ -4471,13 +4650,13 @@ var ChildTable = class {
4471
4650
  if (!this.forker) throw new SpawnError("ENOSYS");
4472
4651
  return this.track(state, this.parentFds.fork(), /* @__PURE__ */ new Map(), this.forker);
4473
4652
  }
4474
- async spawn(req, stdio, inherit = []) {
4653
+ async spawn(req, stdio, inherit2 = []) {
4475
4654
  if (!this.spawner) throw new SpawnError("ENOSYS");
4476
4655
  const fds = new FdTable();
4477
4656
  const captured = /* @__PURE__ */ new Map();
4478
4657
  try {
4479
4658
  for (const [n, slot] of stdio.entries()) fds.installAt(n, this.openSlot(slot, n, captured));
4480
- for (const slot of inherit) {
4659
+ for (const slot of inherit2) {
4481
4660
  if (slot.fd <= 2) continue;
4482
4661
  if ("device" in slot) {
4483
4662
  fds.installAt(slot.fd, deviceFile(slot.device, slot.access));
@@ -4558,8 +4737,8 @@ var ChildTable = class {
4558
4737
  candidates(pid, flags) {
4559
4738
  const all = [...this.children];
4560
4739
  if (pid > 0) return all.filter(([p]) => p === pid);
4561
- const inGroup = flags.inGroup;
4562
- return pid === -1 || !inGroup ? all : all.filter(([p]) => inGroup(p));
4740
+ const inGroup2 = flags.inGroup;
4741
+ return pid === -1 || !inGroup2 ? all : all.filter(([p]) => inGroup2(p));
4563
4742
  }
4564
4743
  async nextChange(candidates, flags, interrupt) {
4565
4744
  let wake;
@@ -5280,10 +5459,10 @@ var MountFs = class {
5280
5459
  }
5281
5460
  return concat3(chunks2, total);
5282
5461
  }
5283
- async writeFile(path, content) {
5462
+ async writeFile(path, content, owner) {
5284
5463
  const real = await this.follow(path, 0, true);
5285
5464
  const found = this.at(real);
5286
- if (!found) return this.base.writeFile(real, content);
5465
+ if (!found) return this.base.writeFile(real, content, owner);
5287
5466
  const { mount, rel: rel2 } = found;
5288
5467
  this.writable(mount, real);
5289
5468
  const bytes = typeof content === "string" ? new TextEncoder().encode(content) : content;
@@ -5550,9 +5729,9 @@ var MountFs = class {
5550
5729
  this.table.forget(a.mount, normalizePath(to));
5551
5730
  }
5552
5731
  }
5553
- async symlink(target, path) {
5732
+ async symlink(target, path, owner) {
5554
5733
  const found = this.at(path);
5555
- if (!found) return this.base.symlink(target, path);
5734
+ if (!found) return this.base.symlink(target, path, owner);
5556
5735
  this.writable(found.mount, normalizePath(path));
5557
5736
  if (!found.mount.caps.symlinks)
5558
5737
  throw errnoError("EPERM", `${path}: this file system has no symlinks`);
@@ -5570,6 +5749,15 @@ var MountFs = class {
5570
5749
  if (!found) return this.base.readlink(path);
5571
5750
  return await found.mount.conn.call({ op: "readlink", path: found.rel });
5572
5751
  }
5752
+ async chown(path, change) {
5753
+ const real = change.follow === false || change.kind === "new" ? path : await this.follow(path);
5754
+ if (this.at(real)) {
5755
+ if (change.kind === "new") return;
5756
+ throw errnoError("EPERM", `${path}: this file system keeps no owners`);
5757
+ }
5758
+ if (!this.base.chown) throw errnoError("EPERM", path);
5759
+ return this.base.chown(real, change);
5760
+ }
5573
5761
  async chmod(path, mode) {
5574
5762
  const found = this.at(path);
5575
5763
  if (!found) return this.base.chmod(path, mode);
@@ -5644,7 +5832,9 @@ function statJson(s) {
5644
5832
  ...s.readonly ? { readonly: true } : {},
5645
5833
  ...s.maxFile ? { maxFile: s.maxFile } : {},
5646
5834
  ...s.ranged ? { ranged: true } : {},
5647
- ...s.version !== void 0 ? { version: s.version } : {}
5835
+ ...s.version !== void 0 ? { version: s.version } : {},
5836
+ ...s.uid !== void 0 ? { uid: s.uid } : {},
5837
+ ...s.gid !== void 0 ? { gid: s.gid } : {}
5648
5838
  };
5649
5839
  }
5650
5840
  async function readdirStat(fs, dir) {
@@ -5659,13 +5849,34 @@ var done = { ok: true, kind: "void" };
5659
5849
  function json2(value) {
5660
5850
  return { ok: true, kind: "json", json: value };
5661
5851
  }
5852
+ function ownerFor2(entry, mode) {
5853
+ const cred = entry.cred?.();
5854
+ if (!cred) return void 0;
5855
+ return ownerOf(cred, mode === void 0 ? void 0 : mode & ~(entry.umask?.() ?? 18));
5856
+ }
5857
+ function mkdirOptions(entry, mode = 511) {
5858
+ const owner = ownerFor2(entry, mode);
5859
+ if (!owner) return { recursive: true };
5860
+ const parents = 511 & ~(entry.umask?.() ?? 18) | 192;
5861
+ return { recursive: true, owner: { ...owner, parents } };
5862
+ }
5863
+ async function chown(entry, path, req) {
5864
+ const cred = entry.cred?.();
5865
+ if (!cred) return done;
5866
+ await chownAs(entry.fs, cred, path, {
5867
+ ...req.uid !== void 0 ? { uid: req.uid } : {},
5868
+ ...req.gid !== void 0 ? { gid: req.gid } : {},
5869
+ follow: req.op === "chown"
5870
+ });
5871
+ return done;
5872
+ }
5662
5873
  async function run(entry, path, req) {
5663
5874
  const { fs, cwd } = entry;
5664
5875
  switch (req.op) {
5665
5876
  case "read":
5666
5877
  return { ok: true, kind: "bytes", bytes: await fs.readFileBuffer(path) };
5667
5878
  case "write":
5668
- await fs.writeFile(path, req.body ?? new Uint8Array(0));
5879
+ await fs.writeFile(path, req.body ?? new Uint8Array(0), ownerFor2(entry, 438));
5669
5880
  return done;
5670
5881
  case "exists":
5671
5882
  return json2(await fs.exists(path));
@@ -5678,7 +5889,7 @@ async function run(entry, path, req) {
5678
5889
  case "readdir-stat":
5679
5890
  return json2(await readdirStat(fs, path));
5680
5891
  case "mkdir":
5681
- await fs.mkdir(path, { recursive: true });
5892
+ await fs.mkdir(path, mkdirOptions(entry, req.mode));
5682
5893
  return done;
5683
5894
  case "rm":
5684
5895
  await fs.rm(path, { recursive: true });
@@ -5699,10 +5910,10 @@ async function run(entry, path, req) {
5699
5910
  await fs.rm(path, { recursive: true });
5700
5911
  return done;
5701
5912
  case "symlink":
5702
- await fs.symlink(req.arg2 ?? "", path);
5913
+ await fs.symlink(req.arg2 ?? "", path, ownerFor2(entry));
5703
5914
  return done;
5704
5915
  case "create":
5705
- await createExclusive(fs, path);
5916
+ await createExclusive(fs, path, ownerFor2(entry, req.mode ?? 438));
5706
5917
  return done;
5707
5918
  case "readlink":
5708
5919
  return json2(await fs.readlink(path));
@@ -5715,6 +5926,9 @@ async function run(entry, path, req) {
5715
5926
  case "lutimes":
5716
5927
  await lutimesOf(fs, path, new Date(req.atimeMs ?? 0), new Date(req.mtimeMs ?? 0));
5717
5928
  return done;
5929
+ case "chown":
5930
+ case "lchown":
5931
+ return chown(entry, path, req);
5718
5932
  case "pread":
5719
5933
  case "pwrite":
5720
5934
  case "truncate":
@@ -5723,14 +5937,14 @@ async function run(entry, path, req) {
5723
5937
  return { ok: false, errno: "EINVAL", message: `sync-fs: unknown op '${req.op}'` };
5724
5938
  }
5725
5939
  }
5726
- async function createExclusive(fs, path) {
5727
- if (fs.createExclusive) return fs.createExclusive(path);
5940
+ async function createExclusive(fs, path, owner) {
5941
+ if (fs.createExclusive) return fs.createExclusive(path, owner);
5728
5942
  if (await fs.lstat(path).then(
5729
5943
  () => true,
5730
5944
  () => false
5731
5945
  ))
5732
5946
  throw syncError("EEXIST", path);
5733
- await fs.writeFile(path, new Uint8Array(0));
5947
+ await fs.writeFile(path, new Uint8Array(0), owner);
5734
5948
  }
5735
5949
  async function ranged(fs, path, req) {
5736
5950
  const ops = rangedOps(fs);
@@ -7220,10 +7434,10 @@ var RealmProxy = class {
7220
7434
  }
7221
7435
  async bridge(ctx, socket, hangup) {
7222
7436
  const stop = this.stop.signal;
7223
- let chain = Promise.resolve();
7437
+ let chain2 = Promise.resolve();
7224
7438
  const toGuest = (bytes) => {
7225
- chain = chain.then(() => ctx.sink.write(bytes, stop));
7226
- return chain;
7439
+ chain2 = chain2.then(() => ctx.sink.write(bytes, stop));
7440
+ return chain2;
7227
7441
  };
7228
7442
  const done4 = new AbortController();
7229
7443
  const reading = AbortSignal.any([stop, done4.signal, hangup]);
@@ -9273,12 +9487,12 @@ var VfsNodes = class {
9273
9487
  }
9274
9488
  return walk();
9275
9489
  }
9276
- createExclusive(path) {
9490
+ createExclusive(path, owner) {
9277
9491
  return this.onEntry(path, async () => {
9278
9492
  const node = this.byPath.get(path);
9279
9493
  const held = node ? await node.serial(async () => node.holds()) : false;
9280
9494
  if (held || await present(this.fs, path)) throw fsError("EEXIST", path);
9281
- await this.fs.writeFile(path, new Uint8Array(0));
9495
+ await this.fs.writeFile(path, new Uint8Array(0), owner);
9282
9496
  });
9283
9497
  }
9284
9498
  open(path, pin, writable = false) {
@@ -9495,9 +9709,9 @@ function keepingOpen(fs, nodes) {
9495
9709
  nodes.renamed(a, b);
9496
9710
  });
9497
9711
  },
9498
- writeFile: (path, content) => nodes.onEntry(fs.resolvePath("/", path), () => fs.writeFile(path, content)),
9499
- createExclusive: async (path) => nodes.createExclusive(await nodes.entryKey(fs.resolvePath("/", path))),
9500
- symlink: (target, path) => nodes.onEntry(fs.resolvePath("/", path), () => fs.symlink(target, path)),
9712
+ writeFile: (path, content, owner) => nodes.onEntry(fs.resolvePath("/", path), () => fs.writeFile(path, content, owner)),
9713
+ createExclusive: async (path, owner) => nodes.createExclusive(await nodes.entryKey(fs.resolvePath("/", path)), owner),
9714
+ symlink: (target, path, owner) => nodes.onEntry(fs.resolvePath("/", path), () => fs.symlink(target, path, owner)),
9501
9715
  mkdir: (path, options) => nodes.onEntry(fs.resolvePath("/", path), () => fs.mkdir(path, options))
9502
9716
  };
9503
9717
  }
@@ -9768,9 +9982,9 @@ var WasmProcess = class {
9768
9982
  return "deliver";
9769
9983
  }
9770
9984
  async spawn(req) {
9771
- const { file, argv, env, cwd, stdio, inherit, exec } = req;
9985
+ const { file, argv, env, cwd, stdio, inherit: inherit2, exec } = req;
9772
9986
  const spawned = { file, argv, env, cwd, ...exec ? { exec } : {} };
9773
- const pid = await this.children.spawn(spawned, stdio, inherit);
9987
+ const pid = await this.children.spawn(spawned, stdio, inherit2);
9774
9988
  if (!exec) return pid;
9775
9989
  if (this.killed) void Promise.allSettled([this.options.kill?.(pid, SIG.KILL)]);
9776
9990
  else this.adoptExec(pid);
@@ -9935,12 +10149,14 @@ var WasmProcess = class {
9935
10149
  }
9936
10150
  const open = { ...req, path };
9937
10151
  if (req.exclusive) {
9938
- await nodes.createExclusive(path);
10152
+ await nodes.createExclusive(path, this.newOwner());
9939
10153
  this.fds.installAt(fd, this.openVfsFile(open));
9940
10154
  } else if (req.create) {
9941
10155
  await nodes.onEntry(path, async () => {
10156
+ const fresh = this.options.owners && !await present(this.options.fs, path, true);
9942
10157
  this.fds.installAt(fd, this.openVfsFile(open));
9943
10158
  await this.fds.get(fd).file.stat?.();
10159
+ if (fresh) await this.options.fs.chown?.(path, { ...this.newOwner(), kind: "new" });
9944
10160
  });
9945
10161
  } else {
9946
10162
  this.fds.installAt(fd, this.openVfsFile(open));
@@ -9954,6 +10170,10 @@ var WasmProcess = class {
9954
10170
  }
9955
10171
  return fd;
9956
10172
  }
10173
+ newOwner() {
10174
+ if (!this.options.owners) return void 0;
10175
+ return { uid: this.cred.euid, gid: this.cred.egid, mode: 438 & ~this.umask };
10176
+ }
9957
10177
  async openProc(req, path) {
9958
10178
  return this.fds.install(await this.procFile(req, path), 3);
9959
10179
  }
@@ -10600,6 +10820,7 @@ function processInit(opts, sab, umask, cred, lane) {
10600
10820
  sab,
10601
10821
  umask,
10602
10822
  cred,
10823
+ ...opts.owners ? { owners: true } : {},
10603
10824
  ...opts.ppid !== void 0 ? { ppid: opts.ppid } : {}
10604
10825
  };
10605
10826
  if (opts.program.abi === "js") {
@@ -10623,8 +10844,9 @@ function inherited(opts) {
10623
10844
  ...opts.cred ? { cred: opts.cred } : {}
10624
10845
  };
10625
10846
  }
10626
- function holdingToken(opts, holds) {
10847
+ function holdingToken(opts, holds, caller) {
10627
10848
  return mintSyncFsToken({
10849
+ ...opts.owners ? caller : {},
10628
10850
  fs: opts.fs,
10629
10851
  cwd: opts.cwd,
10630
10852
  ...opts.statfs ? { statfs: opts.statfs } : {},
@@ -10640,6 +10862,7 @@ function holdingToken(opts, holds) {
10640
10862
  function processOptions(opts, header4, hooks) {
10641
10863
  return {
10642
10864
  ...inherited(opts),
10865
+ ...opts.owners ? { owners: true } : {},
10643
10866
  ...opts.identity ? { identity: opts.identity } : {},
10644
10867
  ...opts.onSyscall ? { onSyscall: opts.onSyscall } : {},
10645
10868
  ...opts.settling ? { settling: opts.settling } : {},
@@ -10684,7 +10907,10 @@ function spawnWasmProcess(opts) {
10684
10907
  publishStops(process2, header4);
10685
10908
  const holds = new HeldPaths();
10686
10909
  opts.held?.add(holds);
10687
- const token = holdingToken(opts, holds);
10910
+ const token = holdingToken(opts, holds, {
10911
+ cred: () => process2.cred,
10912
+ umask: () => process2.umask
10913
+ });
10688
10914
  process2.keptPath = (path) => holds.keptAt(path)?.hidden;
10689
10915
  process2.keptHandle = (handle2) => holds.keptFor(handle2)?.hidden;
10690
10916
  const worker = opts.createWorker();
@@ -11045,26 +11271,26 @@ function replayingHops(inner, heldMs = HELD_MS) {
11045
11271
  void entry.final.cancel().catch(() => void 0);
11046
11272
  };
11047
11273
  const keyOf2 = (owner, method, url) => `${owner} ${method} ${url}`;
11048
- const hold = (key, chain) => {
11274
+ const hold = (key, chain2) => {
11049
11275
  drop(key);
11050
- const mine = [...held.keys()].filter((k) => held.get(k)?.owner === chain.owner);
11276
+ const mine = [...held.keys()].filter((k) => held.get(k)?.owner === chain2.owner);
11051
11277
  if (mine.length >= HELD_PER_OWNER) drop(mine[0]);
11052
11278
  if (held.size >= HELD_MAX) drop(held.keys().next().value);
11053
11279
  const timer = setTimeout(() => drop(key), heldMs);
11054
11280
  timer.unref?.();
11055
- held.set(key, { ...chain, timer });
11281
+ held.set(key, { ...chain2, timer });
11056
11282
  };
11057
- const replay = (chain) => {
11058
- const [hop, ...rest2] = chain.hops;
11283
+ const replay = (chain2) => {
11284
+ const [hop, ...rest2] = chain2.hops;
11059
11285
  const location = hop && header2(hop.headers, "location");
11060
- if (!hop || location === void 0) return chain.final;
11286
+ if (!hop || location === void 0) return chain2.final;
11061
11287
  const target = new URL(location, hop.url);
11062
11288
  target.hash = "";
11063
- const method = followedWith(hop.status, chain.method);
11289
+ const method = followedWith(hop.status, chain2.method);
11064
11290
  const away = target.origin !== new URL(hop.url).origin;
11065
- const print = away ? unauthorized(chain.fingerprint) : chain.fingerprint;
11066
- hold(keyOf2(chain.owner, method, target.href), {
11067
- ...chain,
11291
+ const print = away ? unauthorized(chain2.fingerprint) : chain2.fingerprint;
11292
+ hold(keyOf2(chain2.owner, method, target.href), {
11293
+ ...chain2,
11068
11294
  method,
11069
11295
  fingerprint: print,
11070
11296
  hops: rest2
@@ -12756,6 +12982,11 @@ var DECIDE_MS = 100;
12756
12982
  var INIT_PID = 1;
12757
12983
  var SHARED_DIRS = ["/tmp", "/home"];
12758
12984
  var encoder3 = new TextEncoder();
12985
+ var USERS_MODES = ["off", "report", "enforce"];
12986
+ function usersMode(mode = "off") {
12987
+ if (USERS_MODES.includes(mode)) return mode;
12988
+ throw new Error(`users must be 'off', 'report' or 'enforce', not ${String(mode)}`);
12989
+ }
12759
12990
  function baseName(path) {
12760
12991
  return path.slice(path.lastIndexOf("/") + 1);
12761
12992
  }
@@ -12883,10 +13114,13 @@ var Launcher = class {
12883
13114
  processMounts;
12884
13115
  booting = new AbortController();
12885
13116
  fstab = Promise.resolve([]);
13117
+ usersMode;
12886
13118
  nextPid = 1e3;
12887
13119
  boot = Date.now();
12888
13120
  terminals = 0;
12889
13121
  constructor(options) {
13122
+ this.usersMode = usersMode(options.users);
13123
+ if (this.owners) options.fs.trackOwners?.();
12890
13124
  this.mounts = new MountTable({
12891
13125
  open: (type, spec) => this.driver(type, spec),
12892
13126
  busy: (target) => [...this.openFiles].some((nodes) => nodes.holds(target)) || heldUnder(this.held, target),
@@ -12940,7 +13174,7 @@ var Launcher = class {
12940
13174
  }
12941
13175
  );
12942
13176
  this.fs = this.unlinked.wrap(keepingOpen(fs, this.nodes));
12943
- this.users = new UserDb(this.fs);
13177
+ this.users = new UserDb(this.fs, this.owners);
12944
13178
  void this.unlinked.sweep("/");
12945
13179
  this.watchers.watch(
12946
13180
  [this.modulesDir, this.pnpmHome],
@@ -12952,6 +13186,9 @@ var Launcher = class {
12952
13186
  this.resetCatalogs();
12953
13187
  });
12954
13188
  }
13189
+ get owners() {
13190
+ return this.usersMode !== "off";
13191
+ }
12955
13192
  setRoutes(table) {
12956
13193
  this.routes.set(table);
12957
13194
  }
@@ -13210,6 +13447,7 @@ var Launcher = class {
13210
13447
  ...req.ignored ? { ignored: req.ignored } : {},
13211
13448
  ...req.umask !== void 0 ? { umask: req.umask } : {},
13212
13449
  cred: req.cred,
13450
+ ...this.owners ? { owners: true } : {},
13213
13451
  program: req.program,
13214
13452
  argv0: req.argv0,
13215
13453
  args: req.args,
@@ -14671,6 +14909,7 @@ async function attachKernel(port, options = {}) {
14671
14909
  return {
14672
14910
  protocol: hello.protocol,
14673
14911
  transport,
14912
+ users: hello.users ?? "off",
14674
14913
  closed: channel.ended.promise,
14675
14914
  spawn: (argv, opts = {}) => spawnOn(channel, argv, opts),
14676
14915
  run: (argv, opts = {}) => runOn(channel, argv, opts),
@@ -14798,7 +15037,8 @@ async function createNodeKernel(options = {}) {
14798
15037
  ...options.hostfs ? { hostfs: options.hostfs } : {},
14799
15038
  ...hostfsFetchOf(options),
14800
15039
  ...options.cdp ? { cdp: options.cdp } : {},
14801
- ...options.hostname !== void 0 ? { hostname: options.hostname } : {}
15040
+ ...options.hostname !== void 0 ? { hostname: options.hostname } : {},
15041
+ ...options.users !== void 0 ? { users: options.users } : {}
14802
15042
  });
14803
15043
  await launcher.prepare();
14804
15044
  const guard = (work) => new Promise((resolve, reject) => {
@@ -14864,6 +15104,7 @@ async function createNodeKernel(options = {}) {
14864
15104
  mounts: async () => launcher.mounts.list(),
14865
15105
  setRoutes: (routes) => guard(async () => launcher.setRoutes(routes)),
14866
15106
  users: {
15107
+ mode: launcher.usersMode,
14867
15108
  add: (user) => guard(() => launcher.users.add(user)),
14868
15109
  remove: (name2, options2) => guard(() => launcher.users.remove(name2, options2)),
14869
15110
  list: () => guard(() => launcher.users.list())