@ai-ecoverse/slicc-kernel 1.59.1 → 1.60.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.
package/dist/node.js CHANGED
@@ -1774,6 +1774,29 @@ function userEnv(account, system) {
1774
1774
  PATH: `${pnpm}/bin:/usr/local/bin:/usr/bin:/bin`
1775
1775
  };
1776
1776
  }
1777
+ function groupsOf(groups, names) {
1778
+ return names.map((g) => {
1779
+ const row = groups.find((r) => r.name === g);
1780
+ if (!row) throw fsError("ENOENT", `no group ${g}`);
1781
+ return row;
1782
+ });
1783
+ }
1784
+ function homeModeOf(mode) {
1785
+ if (mode === void 0) return void 0;
1786
+ if (!Number.isInteger(mode) || mode < 0 || mode > 4095) {
1787
+ throw fsError("EINVAL", `not a mode: ${String(mode)}`);
1788
+ }
1789
+ return mode;
1790
+ }
1791
+ function joined(name2, rows) {
1792
+ let changed = false;
1793
+ for (const g of rows) {
1794
+ if (g.members.includes(name2)) continue;
1795
+ g.members.push(name2);
1796
+ changed = true;
1797
+ }
1798
+ return changed;
1799
+ }
1777
1800
  function homeGroupOf(groups, name2) {
1778
1801
  if (name2 === void 0) return void 0;
1779
1802
  const found = groups.find((g) => g.name === name2);
@@ -1789,14 +1812,14 @@ var UserDb = class {
1789
1812
  this.fs = fs;
1790
1813
  this.owners = owners;
1791
1814
  }
1792
- async stampHome(row, shared) {
1815
+ async stampHome(row, shared, homeMode) {
1793
1816
  const home = this.fs.resolvePath("/", row.home);
1794
1817
  if (!this.owners || !this.fs.chown || !HOME_DIR.test(home)) return;
1795
1818
  if (!await this.realDir(home)) return;
1796
1819
  const gid = shared?.gid ?? row.gid;
1797
1820
  await this.fs.chown(home, { uid: row.uid, gid, follow: false, kind: "adopt" });
1798
1821
  const scoop = row.name.startsWith("scoop-");
1799
- await this.fs.chmod(home, shared ? 1512 : scoop ? 488 : 448);
1822
+ await this.fs.chmod(home, homeMode ?? (shared ? 1512 : scoop ? 488 : 448));
1800
1823
  const pnpm = `${home}/.local/share/pnpm`;
1801
1824
  if (await this.realDir(pnpm)) await this.fs.chmod(pnpm, scoop ? 488 : 448);
1802
1825
  }
@@ -1910,31 +1933,33 @@ var UserDb = class {
1910
1933
  const groups = parsed(group);
1911
1934
  const found = users.find((r) => r.name === name2);
1912
1935
  const shared = homeGroupOf(groups, options.homeGroup);
1936
+ const homeMode = homeModeOf(options.homeMode);
1913
1937
  if (found) {
1938
+ const more = groupsOf(groups, options.groups ?? []);
1939
+ if (more.some((g) => g.gid === found.gid)) {
1940
+ throw fsError("EINVAL", `a user is no member of its own group ${name2}`);
1941
+ }
1914
1942
  if (found.uid >= FIRST_UID && found.uid <= LAST_UID) {
1915
1943
  await this.provision(found);
1916
- await this.stampHome(found, shared);
1944
+ await this.stampHome(found, shared, homeMode);
1917
1945
  }
1946
+ if (joined(name2, more)) await this.fs.writeFile(GROUP, formatGroup(group));
1918
1947
  remember();
1919
1948
  return this.account(found, groups);
1920
1949
  }
1921
1950
  const home = homeOf(options.home ?? `/home/${name2}`);
1922
- const extra = [USERS_GROUP, ...options.groups ?? []].map((g) => {
1923
- const row2 = groups.find((r) => r.name === g);
1924
- if (!row2) throw fsError("ENOENT", `no group ${g}`);
1925
- return row2;
1926
- });
1951
+ const extra = groupsOf(groups, [USERS_GROUP, ...options.groups ?? []]);
1927
1952
  if (groups.some((r) => r.name === name2)) throw fsError("EEXIST", `group ${name2} exists`);
1928
1953
  const uid = freeId(users, groups);
1929
1954
  const row = { name: name2, uid, gid: uid, gecos: name2, home, shell: "/bin/bash" };
1930
- for (const g of extra) if (!g.members.includes(name2)) g.members.push(name2);
1955
+ joined(name2, extra);
1931
1956
  group.push({ name: name2, gid: uid, members: [] });
1932
1957
  passwd.push(row);
1933
1958
  const fresh = !await this.fs.exists(home);
1934
1959
  await this.fs.mkdir(home, { recursive: true });
1935
1960
  try {
1936
1961
  await this.provision(row);
1937
- await this.stampHome(row, shared);
1962
+ await this.stampHome(row, shared, homeMode);
1938
1963
  await this.fs.mkdir("/etc", { recursive: true });
1939
1964
  await this.commit(formatPasswd(passwd), formatGroup(group));
1940
1965
  } catch (err) {
@@ -1945,6 +1970,31 @@ var UserDb = class {
1945
1970
  return this.account(row, parsed(group));
1946
1971
  });
1947
1972
  }
1973
+ async groups() {
1974
+ const { group } = await this.tables();
1975
+ return parsed(group).map(({ name: name2, gid, members }) => ({ name: name2, gid, members: [...members] }));
1976
+ }
1977
+ addMember(group, user) {
1978
+ return this.member(group, user, true);
1979
+ }
1980
+ removeMember(group, user) {
1981
+ return this.member(group, user, false);
1982
+ }
1983
+ member(name2, user, on) {
1984
+ return this.serial(async () => {
1985
+ const { passwd, group } = await this.tables();
1986
+ const row = parsed(group).find((g) => g.name === name2);
1987
+ if (!row) throw fsError("ENOENT", `no group ${String(name2)}`);
1988
+ const who = parsed(passwd).find((u) => u.name === user);
1989
+ if (!who) throw fsError("ENOENT", `no user ${String(user)}`);
1990
+ if (who.gid === row.gid) throw fsError("EINVAL", `${name2} is the own group of ${user}`);
1991
+ if (row.members.includes(user) !== on) {
1992
+ row.members = on ? [...row.members, user] : row.members.filter((m) => m !== user);
1993
+ await this.fs.writeFile(GROUP, formatGroup(group));
1994
+ }
1995
+ return { name: row.name, gid: row.gid, members: [...row.members] };
1996
+ });
1997
+ }
1948
1998
  remove(name2, options = {}) {
1949
1999
  return this.serial(async () => {
1950
2000
  const { passwd, group } = await this.tables();
@@ -2470,10 +2520,31 @@ var ERRNO_BY_DOM_ERROR = {
2470
2520
  TypeError: "EINVAL",
2471
2521
  NotAllowedError: "EACCES"
2472
2522
  };
2473
- function translate(err, path) {
2523
+ var reported = /* @__PURE__ */ new Set();
2524
+ function translate(err, path, op = "lookup", mapped = true) {
2474
2525
  if (typeof err?.code === "string") return err;
2475
- const name2 = err?.name;
2476
- return fsError(typeof name2 === "string" && ERRNO_BY_DOM_ERROR[name2] || "EIO", path);
2526
+ const raw = err?.name;
2527
+ const name2 = typeof raw === "string" ? raw : "Error";
2528
+ const code = mapped && ERRNO_BY_DOM_ERROR[name2] || "EIO";
2529
+ const error = Object.assign(fsError(code, path), { domName: name2, op, cause: err });
2530
+ if (code !== "EIO") return error;
2531
+ const detail = err?.message;
2532
+ error.message += ` (OPFS ${op}: ${name2}${typeof detail === "string" && detail ? `: ${detail}` : ""})`;
2533
+ if (!reported.has(`${name2}/${op}`)) {
2534
+ reported.add(`${name2}/${op}`);
2535
+ console.warn(
2536
+ `slicc-kernel: ${error.message}, reported as EIO; logged once per error and operation`
2537
+ );
2538
+ }
2539
+ return error;
2540
+ }
2541
+ var META_OPS = ["get", "update", "move", "remove", "links", "under", "all"];
2542
+ function sidecar(meta) {
2543
+ const pathOf = ([first]) => [first, "/"].flat().find((p) => typeof p === "string");
2544
+ const guarded = (op) => (...args) => meta[op].apply(meta, args).catch((err) => {
2545
+ throw translate(err, pathOf(args), "sidecar", false);
2546
+ });
2547
+ return Object.fromEntries(META_OPS.map((op) => [op, guarded(op)]));
2477
2548
  }
2478
2549
  function missing(err) {
2479
2550
  const code = err?.code;
@@ -2558,7 +2629,7 @@ var OpfsFs = class {
2558
2629
  owners = false;
2559
2630
  constructor(root, meta = new MemoryMeta(), channel, options = {}) {
2560
2631
  this.root = root;
2561
- this.meta = meta;
2632
+ this.meta = sidecar(meta);
2562
2633
  this.ranged = options.ranged === true;
2563
2634
  this.channel = channel === void 0 ? void 0 : new BroadcastChannel(channel);
2564
2635
  if (this.channel) {
@@ -2863,7 +2934,8 @@ var OpfsFs = class {
2863
2934
  } catch (err) {
2864
2935
  const name2 = err?.name;
2865
2936
  const again = name2 === "NotReadableError" || locks && LOCKED.has(name2);
2866
- if (!again || attempt >= READ_ATTEMPTS) throw translate(err, path);
2937
+ if (!again || attempt >= READ_ATTEMPTS)
2938
+ throw translate(err, path, locks ? "write" : "read");
2867
2939
  await new Promise((resolve) => setTimeout(resolve, attempt * 10));
2868
2940
  }
2869
2941
  }
@@ -2905,9 +2977,15 @@ var OpfsFs = class {
2905
2977
  true
2906
2978
  );
2907
2979
  return {
2908
- write: (at, bytes) => writable.write({ type: "write", position: at, data: bytes }),
2909
- truncate: (size) => writable.truncate(size),
2910
- close: () => writable.close()
2980
+ write: (at, bytes) => writable.write({ type: "write", position: at, data: bytes }).catch((err) => {
2981
+ throw translate(err, path, "write", false);
2982
+ }),
2983
+ truncate: (size) => writable.truncate(size).catch((err) => {
2984
+ throw translate(err, path, "write", false);
2985
+ }),
2986
+ close: () => writable.close().catch((err) => {
2987
+ throw translate(err, path, "commit", false);
2988
+ })
2911
2989
  };
2912
2990
  }
2913
2991
  async readFile(path) {
@@ -2942,9 +3020,13 @@ var OpfsFs = class {
2942
3020
  const [file, existed, target] = await this.fileFor(path);
2943
3021
  if (!existed) await this.stampNew(target, owner, false);
2944
3022
  if (!existed && content.length === 0) return;
2945
- const writable = await file.createWritable();
2946
- await writable.write(content);
2947
- await writable.close();
3023
+ try {
3024
+ const writable = await file.createWritable();
3025
+ await writable.write(content);
3026
+ await writable.close();
3027
+ } catch (err) {
3028
+ throw translate(err, path, "commit", false);
3029
+ }
2948
3030
  }
2949
3031
  async listing(path) {
2950
3032
  const found = await this.locate(path, true);
@@ -3220,6 +3302,49 @@ var OpfsFs = class {
3220
3302
  return removed;
3221
3303
  }
3222
3304
  };
3305
+ var READS = [
3306
+ "stat",
3307
+ "lstat",
3308
+ "exists",
3309
+ "readFile",
3310
+ "readFileBuffer",
3311
+ "pread",
3312
+ "readdir",
3313
+ "readdirStat",
3314
+ "readlink",
3315
+ "realpath"
3316
+ ];
3317
+ var WRITES = [
3318
+ "writeFile",
3319
+ "pwrite",
3320
+ "truncate",
3321
+ "writeSession",
3322
+ "mkdir",
3323
+ "rm",
3324
+ "link",
3325
+ "rename",
3326
+ "symlink",
3327
+ "chmod",
3328
+ "utimes",
3329
+ "lutimes",
3330
+ "chown"
3331
+ ];
3332
+ for (const [names, op] of [
3333
+ [READS, "read"],
3334
+ [WRITES, "write"]
3335
+ ]) {
3336
+ for (const name2 of names) {
3337
+ const proto = OpfsFs.prototype;
3338
+ const inner = proto[name2];
3339
+ proto[name2] = async function(...args) {
3340
+ try {
3341
+ return await inner.apply(this, args);
3342
+ } catch (err) {
3343
+ throw translate(err, String(args[0]), op, false);
3344
+ }
3345
+ };
3346
+ }
3347
+ }
3223
3348
 
3224
3349
  // src/fs/unlinked.ts
3225
3350
  var UNLINKED_DIR = ".slicc-unlinked";
@@ -11041,9 +11166,9 @@ var Dying = class {
11041
11166
  clear() {
11042
11167
  clearTimeout(this.timer);
11043
11168
  }
11044
- status(reported) {
11169
+ status(reported2) {
11045
11170
  if (this.ending) return [this.ending.code, this.ending.sig];
11046
- return [typeof reported === "number" ? reported : CRASHED, void 0];
11171
+ return [typeof reported2 === "number" ? reported2 : CRASHED, void 0];
11047
11172
  }
11048
11173
  };
11049
11174
  function killThread(thread, caught, sig) {
@@ -14831,10 +14956,10 @@ var ByteSource = class {
14831
14956
  async more() {
14832
14957
  const { done: done4, value } = await this.reader.read();
14833
14958
  if (done4) return false;
14834
- const joined = new Uint8Array(this.buffered.length + value.length);
14835
- joined.set(this.buffered);
14836
- joined.set(value, this.buffered.length);
14837
- this.buffered = joined;
14959
+ const joined2 = new Uint8Array(this.buffered.length + value.length);
14960
+ joined2.set(this.buffered);
14961
+ joined2.set(value, this.buffered.length);
14962
+ this.buffered = joined2;
14838
14963
  return true;
14839
14964
  }
14840
14965
  async until(marker) {
@@ -15522,6 +15647,11 @@ async function createNodeKernel(options = {}) {
15522
15647
  remove: (name2, options2) => guard(() => launcher.users.remove(name2, options2)),
15523
15648
  list: () => guard(() => launcher.users.list())
15524
15649
  },
15650
+ groups: {
15651
+ list: () => guard(() => launcher.users.groups()),
15652
+ addMember: (group, user) => guard(() => launcher.users.addMember(group, user)),
15653
+ removeMember: (group, user) => guard(() => launcher.users.removeMember(group, user))
15654
+ },
15525
15655
  async connect({ user } = {}) {
15526
15656
  if (terminated) throw new Error(TERMINATED);
15527
15657
  if (user !== void 0 && !await launcher.users.lookup(user)) {