@ai-ecoverse/slicc-kernel 1.54.9 → 1.56.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 +4 -3
- package/dist/kernel-worker.js +146 -51
- package/dist/kernel-worker.js.map +2 -2
- package/dist/node-process-worker.js +58 -6
- package/dist/node-process-worker.js.map +3 -3
- package/dist/node.js +146 -51
- package/dist/node.js.map +2 -2
- package/dist/process-worker.js +58 -6
- package/dist/process-worker.js.map +3 -3
- package/package.json +1 -1
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
|
|
|
@@ -188,7 +188,7 @@ On top of preview1 a WASI program gets:
|
|
|
188
188
|
- **Threads**: `wasi.thread-spawn` (wasm32-wasip1-threads) and WASIX `thread_spawn_v2` start a worker per thread on the process's shared memory, sized from the module's own import (a 2 GiB maximum when the engine cannot reserve more). The threads share one descriptor table; `exit` or a trap in any thread ends the process. At most 64 threads per process; `SLICC_WASM_THREADS` sets another cap, up to 256.
|
|
189
189
|
- **WASIX** (a module importing `wasix_32v1`): fork and setjmp/longjmp through Asyncify (the module is built with `wasm-opt --asyncify`), exec, `posix_spawn`, `waitpid` (with `WNOHANG`, and `WUNTRACED` for a child that stops; WASIX has no `WCONTINUED`), pipes, `dup2`, signal handlers and interval timers, terminal modes, and dynamic linking: a position-independent main module loads side modules with `dlopen` from `LD_LIBRARY_PATH`, its runtime path, `/lib`, `/usr/lib` and `/usr/local/lib`. Each thread has its own copy of the function table, so the kernel hands out its slots (a side module's range, a `dlsym`'d function, a closure): threads loading or looking up at the same time never get the same slot. As in glibc, one `dlopen` at a time runs in a process, constructors included: a `dlopen` from a library's constructor goes ahead in its own thread, and another thread's `dlopen` waits, then finds the library loaded and gets the same handle. A thread waiting there still ends or stops with its process, but a signal handler it has runs once that `dlopen` returns. The lock is let go when a `dlopen` fails (an exception, a trap or `pthread_exit` out of a constructor included) and when its thread ends; a forked child starts with it free. In such a program, `dlopen(NULL)`, and `dlopen` of the program's own file (also by a path that resolves to it through symlinks), give a handle for the program that is already running (no second copy is loaded); a program that isn't position-independent gets an error from `dlopen(NULL)`, as from any `dlopen`; `dlsym` on it searches the program, then the loaded libraries, and `dlclose` of it does nothing. A function already in the table (the program's own, or a library's) keeps its slot, so `dlsym(handle, "strlen") == strlen`.
|
|
190
190
|
- **Foreign calls** (libffi's WASIX backend, so Python's `ctypes`): `call_dynamic` calls a function through the program's indirect function table (a static module's exported `__indirect_function_table`, or the dynamic linker's), its arguments and results packed in memory one after another (`i32`/`f32` 4 bytes, `i64`/`f64` 8). With `strict` set, the buffers must fit the function exactly (else `EINVAL`, before the function is called); without it, missing arguments are zero (one cut short keeps the bytes given, with zeros above them) and results that do not fit are left out. A trap or exception of the callee goes through to the caller. JS cannot ask a function for its type, so the kernel reads the types from the module files (the program's and each side module's, once, when a program first calls through `call_dynamic`): a table slot that is empty, past the end, holds a function of no module the kernel knows of, or takes or returns a `v128`, gives `EINVAL`. For closures (callbacks such as `CFUNCTYPE`), a dynamically linked program gets table slots from `closure_allocate`, and `closure_free` gives them back for reuse and empties the slot in every thread; a static program gets `ENOTSUP`. `closure_prepare` puts a trampoline with the requested signature (any of `i32`, `i64`, `f32`, `f64`, `v128`) into such a slot, in every thread: each call stores its arguments below the stack pointer and calls the backing function as `backing(values, results, user_data)`, then returns what it wrote to `results`. The trampolines are small wasm modules made and compiled at run time, once per signature in each thread (an instance per closure), so a page whose Content Security Policy forbids compiling WebAssembly (no `'wasm-unsafe-eval'`) cannot run them; it cannot run the program either. A `fork` or a `longjmp` (both through Asyncify) inside a function reached through `call_dynamic` cannot unwind through it.
|
|
191
|
-
- **Job control:** `^Z`, `SIGTSTP`, `SIGTTIN` and `SIGTTOU` stop a WASIX program at once, unless it has a handler for them. A stopped program that is busy computing is stopped for job control (`jobs` shows it, `fg`, `bg` and `SIGCONT` work), but its worker keeps a core busy until its next system call, since a worker's running code cannot be frozen from outside. Agents should `kill` such a job rather than leave it stopped. For the same reason, a signal with a handler or a terminating default (`^C`, `SIGTERM`) reaches a program that is busy computing only at its next system call, so a CPU-bound loop may not end on `^C`; `kill -9` always ends it. A thread blocked in the kernel or waiting on a lock (`pthread_cond_wait`, a futex) is woken by a signal for it (a futex wait checks for signals every 50 ms, so each waiting thread wakes briefly that often), and `pthread_kill` runs the handler on the thread it names. A sleep, or a clock in `poll_oneoff`, never ends before the time asked (it can end later, never earlier). A signal that ends a sleep or a `poll`/`select` with a timeout makes the call fail with `EINTR` in a program whose libc says it is wasix-sysroot 2025.9.30-22 or later (its `slicc.libc` custom section). Older libcs turn that answer into `ENOTSUP` from `nanosleep` (up to -18), so for a program without the section the wait ends as if it had timed out, unless it imports `fd_fdflags_set` (then `EINTR`, as before).
|
|
191
|
+
- **Job control:** `^Z`, `SIGTSTP`, `SIGTTIN` and `SIGTTOU` stop a WASIX program at once, unless it has a handler for them. A stopped program that is busy computing is stopped for job control (`jobs` shows it, `fg`, `bg` and `SIGCONT` work), but its worker keeps a core busy until its next system call, since a worker's running code cannot be frozen from outside. Agents should `kill` such a job rather than leave it stopped. For the same reason, a signal with a handler or a terminating default (`^C`, `SIGTERM`) reaches a program that is busy computing only at its next system call, so a CPU-bound loop may not end on `^C`; `kill -9` always ends it. A pending signal is delivered at the program's next WASI or WASIX call, including the calls its worker answers without the kernel (`random_get`, `args_get`, …), except the exempt ones: `clock_time_get` (a tight loop of it measured up to 30% slower), `proc_exit`, `proc_exit2`, `thread_exit`, `thread_spawn_v2` and `wasi.thread-spawn` (wasi-threads), the calls that install handlers (`callback_signal`, `slicc.sigaction_set`), and calls made while Asyncify unwinds or rewinds the stack for `fork` or `longjmp`. A thread blocked in the kernel or waiting on a lock (`pthread_cond_wait`, a futex) is woken by a signal for it (a futex wait checks for signals every 50 ms, so each waiting thread wakes briefly that often), and `pthread_kill` runs the handler on the thread it names. A sleep, or a clock in `poll_oneoff`, never ends before the time asked (it can end later, never earlier). A signal that ends a sleep or a `poll`/`select` with a timeout makes the call fail with `EINTR` in a program whose libc says it is wasix-sysroot 2025.9.30-22 or later (its `slicc.libc` custom section). Older libcs turn that answer into `ENOTSUP` from `nanosleep` (up to -18), so for a program without the section the wait ends as if it had timed out, unless it imports `fd_fdflags_set` (then `EINTR`, as before).
|
|
192
192
|
- **Pipes** have no file type for WASI and WASIX programs' `fstat` (`st_mode` type 0), because WASI has no FIFO file type. A socket type, as kernels 1.47.3 to 1.48.1 gave them, made Ruby treat a `popen` pipe as a socket (`close_write` then never ends the child's input). The kernel's `slicc_fs.fd_mode` answers `S_IFIFO | 0600` (Linux numbering) for a pipe, and wasix-sysroot 2025.9.30-21 and later take the type from it, so programs built on it see a FIFO (`S_ISFIFO`); older libcs keep only the permission bits. Until then Python's asyncio refuses a pipe for a subprocess (`Pipe transport is only for pipes, sockets and character devices`). They are read and written as pipes; socket calls on them fail with `ENOTSOCK`, and `isatty()` is false. Emscripten programs, bash included, see a FIFO. The same holds for any kernel descriptor that is neither seekable nor a terminal or device, such as captured output.
|
|
193
193
|
- **Sockets**: the WASIX socket calls (`sock_open`, `sock_pair` for an `AF_UNIX` stream pair, `bind`, `listen`, `connect`, `accept`, options, local and peer names) are the kernel's loopback sockets, and preview1's `sock_recv` / `sock_send` / `sock_shutdown` work on them and on inherited ones. Writing to a socket whose peer is gone fails with `EPIPE`, through `fd_write` as through `sock_send`, as if `MSG_NOSIGNAL` were set, since a WASI program cannot ignore `SIGPIPE`; a pipe or terminal whose reader is gone still ends the program with status 141. HTTP and HTTPS leave through the realm proxy that `https_proxy` names, as for any other program; `resolve` answers `/etc/hosts` names and literal addresses, and the [uplink](#uplink)'s names.
|
|
194
194
|
- **Diagnostics**: a trap ends the program with 134 and its message on stderr; with `SLICC_WASM_BACKTRACE=1` the wasm frames follow, named from the module's name section, or from a sidecar for a module shipped without one (`<module>.names` beside it, or the same path in a `<package>-names` package). `SLICC_WASI_STATS=1` makes a program print its calls, counted and timed, as it ends.
|
|
@@ -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
|
|
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.
|
package/dist/kernel-worker.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
12923
|
-
|
|
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(
|
|
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(
|
|
13022
|
-
this.
|
|
13023
|
-
|
|
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
|
-
|
|
13031
|
-
|
|
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
|
-
|
|
13034
|
-
|
|
13035
|
-
|
|
13036
|
-
|
|
13037
|
-
|
|
13038
|
-
|
|
13039
|
-
|
|
13040
|
-
|
|
13041
|
-
|
|
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
|
-
|
|
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
|
-
|
|
13048
|
-
|
|
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
|
|
13084
|
-
|
|
13085
|
-
const command =
|
|
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
|
|
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.
|
|
13637
|
-
|
|
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 = [];
|