@ai-ecoverse/slicc-kernel 1.54.9 → 1.55.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/README.md CHANGED
@@ -88,7 +88,7 @@ The user database, `/etc/passwd` and `/etc/group` (see [Users](#users)):
88
88
 
89
89
  | | |
90
90
  | --- | --- |
91
- | `add({ name, home?, groups?, umask? })` | adds a user and resolves with `{ name, uid, gid, home, shell, groups, umask? }`. It's idempotent: an existing name resolves with its entry and changes nothing but the remembered `umask`. The new user gets the lowest free id from 1000 up, a private group of the same id and name, membership of `users` and of each group in `groups` (such as `sudo`), `home` (default `/home/<name>`, created), and `/bin/bash`. `umask` is the umask its processes start with; the kernel keeps it in memory, so pass it again after a reload. A name must match `[a-z_][a-z0-9_.-]*`; a group that doesn't exist rejects with `ENOENT` |
91
+ | `add({ name, home?, groups?, umask? })` | adds a user and resolves with `{ name, uid, gid, home, shell, groups, umask? }`. It's idempotent: an existing name resolves with its entry and changes nothing but the remembered `umask`. The new user gets the lowest free id from 1000 up, a private group of the same id and name, membership of `users` and of each group in `groups` (such as `sudo`), `home` (default `/home/<name>`, created), its pnpm home `~/.local/share/pnpm` (`0700`, `0750` for a `scoop-` user) and `~/.config/pnpm/config.yaml` with `minimumReleaseAge: 1440` (pnpm skips releases less than a day old), each only if missing, which an `add` of an existing user also does, and `/bin/bash`. `umask` is the umask its processes start with; the kernel keeps it in memory, so pass it again after a reload. A name must match `[a-z_][a-z0-9_.-]*`; a group that doesn't exist rejects with `ENOENT` |
92
92
  | `remove(name, { keepHome }?)` | removes the user, its private group and its group memberships, and deletes its home unless `keepHome` is set. A home outside `/home/<dir>` is never deleted. Resolves `false` for an unknown name; system accounts (ids below 1000 or above 60000) reject with `EPERM` |
93
93
  | `list()` | every user, as `add` returns them |
94
94
 
@@ -490,7 +490,8 @@ Lowering a soft resource limit (`ulimit -S -n 64`) still fails with `EPERM` in E
490
490
  Every process has credentials in the kernel, as on Linux: real, effective and saved user and group ids, and supplementary groups. A process gets them from the user it was started as (`run`, `openTerminal`, a client's `spawn`, or the client's own user; root by default) and keeps its parent's across fork, spawn and exec. Setuid and setgid bits are never honoured on exec. Files aren't checked against the credentials yet; they decide who may signal whom, and what `/proc` and `ps` show.
491
491
 
492
492
  - **The database:** `/etc/passwd` and `/etc/group` are real files. On first start the kernel writes them when they're missing, with `root` (uid 0, home `/root`, created `0700`) and `nobody` (65534), and the groups `root`, `sudo` (27), `users` (100) and `nogroup` (65534); files already there are kept as they are. The kernel reads them for every lookup, so an edit counts at once. [`kernel.users`](#kernelusers) adds and removes users.
493
- - **Environment:** a process started as a user gets `HOME`, `USER`, `LOGNAME`, `PNPM_HOME` and `PATH`. A user's `PNPM_HOME` is `~/.local/share/pnpm`; root's is `/usr/local/share/pnpm` (or the embedder's `env.PNPM_HOME`), whose global packages are the commands every user finds. `PATH` is `$PNPM_HOME/bin:/usr/local/bin:/usr/bin:/bin`.
493
+ - **Environment:** a process started as a user gets `HOME`, `USER`, `LOGNAME`, `PNPM_HOME`, `NPM_CONFIG_PREFIX` and `PATH`. A user's `PNPM_HOME` is `~/.local/share/pnpm` and its npm prefix `~/.local`; root's are `/usr/local/share/pnpm` (or the embedder's `env.PNPM_HOME`) and `/usr/local`. `PATH` is `$PNPM_HOME/bin:/usr/local/bin:/usr/bin:/bin`.
494
+ - **Commands per user:** root's global packages (`pnpm add -g` as root) are commands every user finds in `/usr/bin`. A user's own global packages are commands for that user only: they show up in the user's `~/.local/share/pnpm/bin`, first on its `PATH`, and a program the user starts finds them by name before the system's; another user, root included, doesn't. A user's packages can map file suffixes (`binfmt`) for that user too. The kernel picks up an install, a removal and a new user as they happen.
494
495
  - **Changing credentials:** POSIX `setresuid`/`setresgid` rules. Root (effective uid 0) may set any id and the group list; anyone else may only swap among their own real, effective and saved ids, and gets `EPERM` otherwise.
495
496
  - **Signals:** a process may signal another if its effective uid is 0, or if its real or effective uid is the target's real or saved uid; otherwise `kill` fails with `EPERM`. A group kill signals the members it may, and fails with `EPERM` only when it may signal none.
496
497
  - **Visibility:** a process that isn't root sees only processes whose real or effective uid is its effective uid, in `/proc` and in `ps`, like Linux's `hidepid=2`; so does a client bound to a user.
@@ -661,7 +661,7 @@ var OpfsFs = class {
661
661
  const parent = await this.locate(parentOf2(path), true);
662
662
  if (parent.handle?.kind !== "directory") throw fsError("ENOTDIR", path);
663
663
  await parent.handle.getDirectoryHandle(baseOf(path), { create: true });
664
- this.touch(parent.path);
664
+ this.touch(parent.path, `${parent.path === "/" ? "" : parent.path}/${baseOf(path)}`);
665
665
  }
666
666
  async rm(path, options = {}) {
667
667
  let found;
@@ -2385,6 +2385,7 @@ var LAST_UID = 6e4;
2385
2385
  var USERS_GROUP = "users";
2386
2386
  var HOME_DIR = /^\/home\/[^/]+$/;
2387
2387
  var SYSTEM_PNPM_HOME = "/usr/local/share/pnpm";
2388
+ var PNPM_CONFIG = "minimumReleaseAge: 1440\n";
2388
2389
  var isId = (text) => text !== void 0 && /^\d+$/.test(text);
2389
2390
  function lines(text) {
2390
2391
  return text.split("\n").filter((line, i, all) => line !== "" || i < all.length - 1);
@@ -2440,6 +2441,7 @@ function userEnv(account, system) {
2440
2441
  USER: account.name,
2441
2442
  LOGNAME: account.name,
2442
2443
  PNPM_HOME: pnpm,
2444
+ NPM_CONFIG_PREFIX: account.uid === 0 ? "/usr/local" : `${account.home}/.local`,
2443
2445
  PATH: `${pnpm}/bin:/usr/local/bin:/usr/bin:/bin`
2444
2446
  };
2445
2447
  }
@@ -2464,6 +2466,18 @@ var UserDb = class {
2464
2466
  group: parseGroup(await this.read(GROUP, DEFAULT_GROUP))
2465
2467
  };
2466
2468
  }
2469
+ async provision(row) {
2470
+ const pnpm = `${row.home}/.local/share/pnpm`;
2471
+ if (!await this.fs.exists(pnpm)) {
2472
+ await this.fs.mkdir(pnpm, { recursive: true });
2473
+ await this.fs.chmod(pnpm, row.name.startsWith("scoop-") ? 488 : 448);
2474
+ }
2475
+ const config = `${row.home}/.config/pnpm/config.yaml`;
2476
+ if (!await this.fs.exists(config)) {
2477
+ await this.fs.mkdir(`${row.home}/.config/pnpm`, { recursive: true });
2478
+ await this.fs.writeFile(config, PNPM_CONFIG);
2479
+ }
2480
+ }
2467
2481
  async commit(passwd, group, then) {
2468
2482
  const before = [await this.read(PASSWD, DEFAULT_PASSWD), await this.read(GROUP, DEFAULT_GROUP)];
2469
2483
  try {
@@ -2537,6 +2551,7 @@ var UserDb = class {
2537
2551
  const groups = parsed(group);
2538
2552
  const found = users.find((r) => r.name === name2);
2539
2553
  if (found) {
2554
+ if (found.uid >= FIRST_UID && found.uid <= LAST_UID) await this.provision(found);
2540
2555
  remember();
2541
2556
  return this.account(found, groups);
2542
2557
  }
@@ -2555,6 +2570,7 @@ var UserDb = class {
2555
2570
  const fresh = !await this.fs.exists(home);
2556
2571
  await this.fs.mkdir(home, { recursive: true });
2557
2572
  try {
2573
+ await this.provision(row);
2558
2574
  await this.fs.mkdir("/etc", { recursive: true });
2559
2575
  await this.commit(formatPasswd(passwd), formatGroup(group));
2560
2576
  } catch (err) {
@@ -5558,17 +5574,22 @@ async function followLinks(fs, path) {
5558
5574
  }
5559
5575
  return current;
5560
5576
  }
5561
- function withCommandDirs(fs, names) {
5577
+ function withCommandDirs(fs, names, userDir = async () => void 0) {
5562
5578
  async function children2(path) {
5563
5579
  if (path === "/") return ["bin", "usr"];
5564
5580
  if (path === "/usr") return ["bin"];
5565
5581
  if (path === "/bin" || path === "/usr/bin") return [...await names()];
5566
- return void 0;
5582
+ const own = await userDir(path);
5583
+ return own?.size ? [...own] : void 0;
5567
5584
  }
5568
5585
  async function virtual(path) {
5569
5586
  if (path === "/usr" || path === "/bin" || path === "/usr/bin") return synthetic(true, path);
5570
5587
  const name2 = COMMAND.exec(path)?.[1];
5571
- return name2 !== void 0 && (await names()).has(name2) ? synthetic(false, path) : void 0;
5588
+ if (name2 !== void 0) return (await names()).has(name2) ? synthetic(false, path) : void 0;
5589
+ if ((await userDir(path))?.size) return synthetic(true, path);
5590
+ const slash = path.lastIndexOf("/");
5591
+ const own = await userDir(path.slice(0, slash));
5592
+ return own?.has(path.slice(slash + 1)) ? synthetic(false, path) : void 0;
5572
5593
  }
5573
5594
  async function linked(path) {
5574
5595
  return await virtual(path) ?? virtual(await followLinks(fs, path));
@@ -12919,8 +12940,11 @@ var Launcher = class {
12919
12940
  createWorker;
12920
12941
  modulesDir;
12921
12942
  env;
12922
- catalog;
12923
- binfmts;
12943
+ catalogs = /* @__PURE__ */ new Map();
12944
+ ownCatalogs = /* @__PURE__ */ new Map();
12945
+ binfmts = /* @__PURE__ */ new Map();
12946
+ scopes;
12947
+ unwatchScopes = () => void 0;
12924
12948
  warned = /* @__PURE__ */ new Set();
12925
12949
  compiled = /* @__PURE__ */ new Map();
12926
12950
  processes = /* @__PURE__ */ new Map();
@@ -13000,7 +13024,11 @@ var Launcher = class {
13000
13024
  ca: this.ca
13001
13025
  });
13002
13026
  enableCdp(this.net, this.cdp);
13003
- const fs = withCommandDirs(this.base, async () => new Set((await this.commands()).keys()));
13027
+ const fs = withCommandDirs(
13028
+ this.base,
13029
+ async () => new Set((await this.commands()).keys()),
13030
+ (dir) => this.userCommandDir(dir)
13031
+ );
13004
13032
  this.nodes = new VfsNodes(fs, (path) => this.released(path));
13005
13033
  this.nodes.keepsOnDisk = true;
13006
13034
  this.openFiles.add(this.nodes);
@@ -13018,35 +13046,99 @@ var Launcher = class {
13018
13046
  this.fs = this.unlinked.wrap(keepingOpen(fs, this.nodes));
13019
13047
  this.users = new UserDb(this.fs);
13020
13048
  void this.unlinked.sweep("/");
13021
- this.watchers.watch([this.modulesDir, this.pnpmHome], { recursive: true }, () => {
13022
- this.catalog = void 0;
13023
- this.binfmts = void 0;
13049
+ this.watchers.watch(
13050
+ [this.modulesDir, this.pnpmHome],
13051
+ { recursive: true },
13052
+ () => this.resetCatalogs()
13053
+ );
13054
+ this.watchers.watch([PASSWD], { recursive: false }, () => {
13055
+ this.scopes = void 0;
13056
+ this.resetCatalogs();
13024
13057
  });
13025
13058
  }
13026
13059
  setRoutes(table) {
13027
13060
  this.routes.set(table);
13028
13061
  }
13029
- commands() {
13030
- this.catalog ??= this.roots().then((roots) => scanCommands(this.base, roots));
13031
- return this.catalog;
13062
+ commands(scope = "") {
13063
+ let found = this.catalogs.get(scope);
13064
+ if (!found) {
13065
+ found = this.roots(scope).then((roots) => scanCommands(this.base, roots));
13066
+ this.catalogs.set(scope, found);
13067
+ }
13068
+ return found;
13032
13069
  }
13033
- interpreters() {
13034
- this.binfmts ??= this.roots().then(
13035
- (roots) => scanBinfmts(this.base, roots, (message3) => {
13036
- if (this.warned.has(message3)) return;
13037
- this.warned.add(message3);
13038
- console.warn(message3);
13039
- })
13040
- );
13041
- return this.binfmts;
13070
+ ownCommands(scope) {
13071
+ let found = this.ownCatalogs.get(scope);
13072
+ if (!found) {
13073
+ found = pnpmGlobalRoots(this.base, scope).then((roots) => scanCommands(this.base, roots)).then((commands) => new Set(commands.keys()));
13074
+ this.ownCatalogs.set(scope, found);
13075
+ }
13076
+ return found;
13077
+ }
13078
+ interpreters(scope = "") {
13079
+ let found = this.binfmts.get(scope);
13080
+ if (!found) {
13081
+ found = this.roots(scope).then(
13082
+ (roots) => scanBinfmts(this.base, roots, (message3) => {
13083
+ if (this.warned.has(message3)) return;
13084
+ this.warned.add(message3);
13085
+ console.warn(message3);
13086
+ })
13087
+ );
13088
+ this.binfmts.set(scope, found);
13089
+ }
13090
+ return found;
13042
13091
  }
13043
- async roots() {
13044
- return [this.modulesDir, ...await pnpmGlobalRoots(this.base, this.pnpmHome)];
13092
+ async roots(scope = "") {
13093
+ const system = [this.modulesDir, ...await pnpmGlobalRoots(this.base, this.pnpmHome)];
13094
+ return scope ? [...await pnpmGlobalRoots(this.base, scope), ...system] : system;
13095
+ }
13096
+ resetCatalogs() {
13097
+ this.catalogs.clear();
13098
+ this.ownCatalogs.clear();
13099
+ this.binfmts.clear();
13100
+ }
13101
+ userScopes() {
13102
+ this.scopes ??= this.users.list().then(
13103
+ (accounts) => {
13104
+ const users = accounts.filter((account) => account.uid !== 0);
13105
+ const homes = users.map((account) => pnpmHomeOf(account, this.pnpmHome));
13106
+ this.unwatchScopes();
13107
+ this.unwatchScopes = this.watchers.watch(
13108
+ homes,
13109
+ { recursive: true },
13110
+ () => this.resetCatalogs()
13111
+ );
13112
+ return {
13113
+ byUid: new Map(users.map((account, i) => [account.uid, homes[i]])),
13114
+ byBin: new Map(homes.map((home) => [`${home}/bin`, home]))
13115
+ };
13116
+ },
13117
+ () => ({ byUid: /* @__PURE__ */ new Map(), byBin: /* @__PURE__ */ new Map() })
13118
+ );
13119
+ return this.scopes;
13120
+ }
13121
+ async userCommandDir(dir) {
13122
+ const scope = (await this.userScopes()).byBin.get(dir);
13123
+ return scope === void 0 ? void 0 : this.ownCommands(scope);
13124
+ }
13125
+ async scopeOf(uid) {
13126
+ return (await this.userScopes()).byUid.get(uid) ?? "";
13127
+ }
13128
+ async commandAt(file, scope) {
13129
+ const system = COMMAND2.exec(file)?.[1];
13130
+ if (system !== void 0) return (await this.commands()).get(system);
13131
+ if (!file.includes("/")) return (await this.commands(scope)).get(file);
13132
+ const slash = file.lastIndexOf("/");
13133
+ const owner = (await this.userScopes()).byBin.get(file.slice(0, slash));
13134
+ if (owner === void 0 || !(await this.ownCommands(owner)).has(file.slice(slash + 1))) {
13135
+ return void 0;
13136
+ }
13137
+ return (await this.commands(owner)).get(file.slice(slash + 1));
13045
13138
  }
13046
- async resolve(file, argv0, cwd) {
13047
- const name2 = COMMAND2.exec(file)?.[1] ?? (file.includes("/") ? void 0 : file);
13048
- if (name2 !== void 0) {
13049
- const command = (await this.commands()).get(name2);
13139
+ async resolve(file, argv0, cwd, scope = "") {
13140
+ if (!file.includes("/") || COMMAND2.test(file)) {
13141
+ const command = await this.commandAt(file, scope);
13050
13142
  return command && !command.script ? targetOf(command) : void 0;
13051
13143
  }
13052
13144
  const path = this.fs.resolvePath(cwd, file);
@@ -13057,6 +13149,8 @@ var Launcher = class {
13057
13149
  const byPath = !COMMAND2.test(path) && (await this.commands()).get(linked)?.argv0Path === true;
13058
13150
  return target && byPath ? { ...target, argv0: path } : target;
13059
13151
  }
13152
+ const own = await this.commandAt(glue, scope);
13153
+ if (own) return own.script ? void 0 : targetOf(own);
13060
13154
  const head = await this.head(glue);
13061
13155
  if (!head) return void 0;
13062
13156
  if (isWasm(head)) {
@@ -13079,45 +13173,45 @@ var Launcher = class {
13079
13173
  return void 0;
13080
13174
  }
13081
13175
  }
13082
- async scriptCommand(file) {
13083
- const name2 = COMMAND2.exec(file)?.[1] ?? (file.includes("/") ? void 0 : file);
13084
- if (name2 === void 0) return void 0;
13085
- const command = (await this.commands()).get(name2);
13176
+ async scriptCommand(file, cwd, scope) {
13177
+ const direct = !file.includes("/") || COMMAND2.test(file);
13178
+ const at = direct ? file : await followLinks(this.base, this.fs.resolvePath(cwd, file));
13179
+ const command = await this.commandAt(at, scope);
13086
13180
  return command?.script ? command : void 0;
13087
13181
  }
13088
- async interpreted(file, argv, cwd, depth = 0) {
13089
- const command = await this.scriptCommand(file);
13182
+ async interpreted(file, argv, cwd, scope, depth = 0) {
13183
+ const command = await this.scriptCommand(file, cwd, scope);
13090
13184
  const script = command?.script ?? this.fs.resolvePath(cwd, file);
13091
13185
  const head = await this.head(script);
13092
13186
  if (!head) return void 0;
13093
- const binfmt = isWasm(head) ? void 0 : binfmtOf(await this.interpreters(), script);
13187
+ const binfmt = isWasm(head) ? void 0 : binfmtOf(await this.interpreters(scope), script);
13094
13188
  const words = shebang(head) ?? (binfmt ? [binfmt] : void 0);
13095
13189
  if (!words) return void 0;
13096
13190
  if (baseName(words[0]) === "env") words.shift();
13097
13191
  const [interp, ...rest2] = words;
13098
13192
  const arg = rest2.join(" ");
13099
13193
  const passed = [...arg ? [arg] : [], command ? script : file, ...argv.slice(1)];
13100
- const found = interp ? await this.resolve(interp, interp, cwd) : void 0;
13194
+ const found = interp ? await this.resolve(interp, interp, cwd, scope) : void 0;
13101
13195
  if (!found) {
13102
- if (!interp || depth >= MAX_INTERPRETERS || !await this.scriptCommand(interp)) {
13196
+ if (!interp || depth >= MAX_INTERPRETERS || !await this.scriptCommand(interp, cwd, scope)) {
13103
13197
  return void 0;
13104
13198
  }
13105
- const inner = await this.interpreted(interp, [interp, ...passed], cwd, depth + 1);
13199
+ const inner = await this.interpreted(interp, [interp, ...passed], cwd, scope, depth + 1);
13106
13200
  return inner && { ...inner, target: withScriptEnv(inner.target, command) };
13107
13201
  }
13108
13202
  const target = withScriptEnv(found, command);
13109
13203
  return { target, args: [...target.prefix ?? [], ...passed] };
13110
13204
  }
13111
- async plan(file, argv, cwd) {
13112
- const direct = await this.resolve(file, argv[0] ?? file, cwd);
13205
+ async plan(file, argv, cwd, scope) {
13206
+ const direct = await this.resolve(file, argv[0] ?? file, cwd, scope);
13113
13207
  if (direct) return { target: direct, args: [...direct.prefix ?? [], ...argv.slice(1)] };
13114
- return this.interpreted(file, argv, cwd);
13208
+ return this.interpreted(file, argv, cwd, scope);
13115
13209
  }
13116
- async unrunnable(file, cwd) {
13210
+ async unrunnable(file, cwd, scope) {
13117
13211
  const path = this.fs.resolvePath(cwd, file);
13118
13212
  const head = await this.base.readFileBuffer(path).catch(() => null);
13119
13213
  if (!head || head[0] === 35 && head[1] === 33) return "ENOENT";
13120
- return binfmtOf(await this.interpreters(), path) ? "ENOENT" : "ENOEXEC";
13214
+ return binfmtOf(await this.interpreters(scope), path) ? "ENOENT" : "ENOEXEC";
13121
13215
  }
13122
13216
  module(path) {
13123
13217
  return this.base.stat(path).then((st) => {
@@ -13327,10 +13421,11 @@ var Launcher = class {
13327
13421
  }
13328
13422
  spawner(ppid, report) {
13329
13423
  return async (req, fds) => {
13330
- const planned = await this.plan(req.file, req.argv, req.cwd);
13424
+ const scope = await this.scopeOf(this.credOf(ppid).euid);
13425
+ const planned = await this.plan(req.file, req.argv, req.cwd, scope);
13331
13426
  if (!planned) {
13332
13427
  await fds.closeAll();
13333
- throw new SpawnError(await this.unrunnable(req.file, req.cwd));
13428
+ throw new SpawnError(await this.unrunnable(req.file, req.cwd, scope));
13334
13429
  }
13335
13430
  const exec = req.exec ? { exec: true } : {};
13336
13431
  const deciding = req.exec ? void 0 : this.undecided(ppid);
@@ -13632,18 +13727,18 @@ var Launcher = class {
13632
13727
  ...account.umask !== void 0 ? { umask: account.umask } : {}
13633
13728
  };
13634
13729
  }
13635
- async starting(argv, dir) {
13636
- this.catalog = void 0;
13637
- this.binfmts = void 0;
13730
+ async starting(argv, dir, account) {
13731
+ this.resetCatalogs();
13732
+ const scope = account.uid === 0 ? "" : pnpmHomeOf(account, this.pnpmHome);
13638
13733
  const start2 = dir ?? "/";
13639
13734
  const cwd = await physicalPathAsync(this.base, start2.startsWith("/") ? start2 : `/${start2}`);
13640
13735
  const [file = ""] = argv;
13641
13736
  await this.base.mkdir(cwd, { recursive: true });
13642
- return { cwd, file, planned: await this.plan(file, argv, cwd) };
13737
+ return { cwd, file, planned: await this.plan(file, argv, cwd, scope) };
13643
13738
  }
13644
13739
  async openTerminal(argv, options) {
13645
13740
  const account = await this.account(options.user);
13646
- const { cwd, file, planned } = await this.starting(argv, options.cwd);
13741
+ const { cwd, file, planned } = await this.starting(argv, options.cwd, account);
13647
13742
  if (!planned) throw new Error(`${file}: command not found`);
13648
13743
  let leader = 0;
13649
13744
  const tty = new KernelTty(
@@ -13686,7 +13781,7 @@ var Launcher = class {
13686
13781
  }
13687
13782
  async run(argv, options = {}) {
13688
13783
  const account = await this.account(options.user);
13689
- const { cwd, file, planned } = await this.starting(argv, options.cwd);
13784
+ const { cwd, file, planned } = await this.starting(argv, options.cwd, account);
13690
13785
  const env = this.environment(cwd, options.env, account);
13691
13786
  const out = [];
13692
13787
  const err = [];