@ai-ecoverse/slicc-kernel 1.54.6 → 1.54.8
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 +2 -2
- package/dist/kernel-worker.js +37 -16
- package/dist/kernel-worker.js.map +3 -3
- package/dist/node-process-worker.js +51 -26
- package/dist/node-process-worker.js.map +3 -3
- package/dist/node.js +37 -16
- package/dist/node.js.map +3 -3
- package/dist/process-worker.js +68 -43
- package/dist/process-worker.js.map +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -66,7 +66,7 @@ Starts `argv` as the session leader on a new terminal and resolves once it is ru
|
|
|
66
66
|
| --- | --- |
|
|
67
67
|
| `pid` | the session leader's pid |
|
|
68
68
|
| `onData` | the output listener; output that arrives while it is `null` is kept and delivered when one is set |
|
|
69
|
-
| `write(data)` | types a string or bytes into the terminal; with `ISIG` on, `^C`, `^Z` and `^\` signal the foreground process group. A key typed while a child of that group is still setting up is held until the child has finished, then sent to the group in the foreground at that point. Setting up means the child's calls before its first other one: `setpgid`, `tcsetpgrp`, signal masks, and starting the program it execs (which then sets itself up too). The hold ends 2 s after the child's first call at the latest. A key typed while the foreground group is busy, not reading the terminal (a shell between reading a command line and starting it), goes to that group and also to the job the group starts next, once that job has taken the terminal; the second copy is dropped when the group reads the terminal again. So `^C` or `^Z` right after a command line reaches the new job, as it does on Linux, where starting a job takes microseconds rather than a worker start. For the same reason, a child that is still setting up cannot give the terminal to its own group once its parent has already given the terminal to that group and taken it back: on Linux the child's `tcsetpgrp` would have landed first, so the parent's later choice stands. Otherwise `tcsetpgrp` behaves as before. A `^C`, `^\` or `^Z` that a program with a handler for it takes between two system calls, rather than while waiting, ends the program's next wait in the kernel that would block (reading nothing yet, `select` or `poll` with a timeout, a blocking write, `waitpid`) with `EINTR`, once, as if the key had come during that wait. A zero-timeout `select` or `poll` does not use it up. Without this, an interactive shell whose `^C` landed while it was taking in the key before it would wait for the next key before showing a new prompt: readline acts on `^C` only when a read ends with `EINTR`. On Linux that window is the microseconds between two reads; here it is a few kernel calls per key. A signal sent with `kill` does not do this. |
|
|
69
|
+
| `write(data)` | types a string or bytes into the terminal; with `ISIG` on, `^C`, `^Z` and `^\` signal the foreground process group. A key typed while a child of that group is still setting up is held until the child has finished, then sent to the group in the foreground at that point; this applies only to children of a job-control shell (a process that ignores `SIGTTOU`, as interactive shells do) and to the programs those children exec, so a program that starts a child without job control (Python's `subprocess`) gets the key at once. Setting up means the child's calls before its first other one: `setpgid`, `tcsetpgrp`, signal masks, and starting the program it execs (which then sets itself up too). The hold ends 2 s after the child's first call at the latest. A key typed while the foreground group is busy, not reading the terminal (a shell between reading a command line and starting it), goes to that group and also to the job the group starts next, once that job has taken the terminal; the second copy is dropped when the group reads the terminal again. So `^C` or `^Z` right after a command line reaches the new job, as it does on Linux, where starting a job takes microseconds rather than a worker start. For the same reason, a child that is still setting up cannot give the terminal to its own group once its parent has already given the terminal to that group and taken it back: on Linux the child's `tcsetpgrp` would have landed first, so the parent's later choice stands. Otherwise `tcsetpgrp` behaves as before. A `^C`, `^\` or `^Z` that a program with a handler for it takes between two system calls, rather than while waiting, ends the program's next wait in the kernel that would block (reading nothing yet, `select` or `poll` with a timeout, a blocking write, `waitpid`) with `EINTR`, once, as if the key had come during that wait. A zero-timeout `select` or `poll` does not use it up. Without this, an interactive shell whose `^C` landed while it was taking in the key before it would wait for the next key before showing a new prompt: readline acts on `^C` only when a read ends with `EINTR`. On Linux that window is the microseconds between two reads; here it is a few kernel calls per key. A signal sent with `kill` does not do this. |
|
|
70
70
|
| `resize(cols, rows)` | sets the window size (`TIOCGWINSZ`) and sends `SIGWINCH` to the foreground process group |
|
|
71
71
|
| `signal(name)` | sends a signal (`'SIGINT'`, `'SIGTSTP'`, `'SIGQUIT'`, `'SIGHUP'`, …) to the foreground process group, regardless of termios |
|
|
72
72
|
| `exited` | resolves with the session leader's exit status (`128 + n` if killed by signal `n`) |
|
|
@@ -500,7 +500,7 @@ Programs ask the kernel for their ids:
|
|
|
500
500
|
|
|
501
501
|
### `/proc`
|
|
502
502
|
|
|
503
|
-
Every process of the kernel has `/proc/<pid>/` with `cmdline`, `comm`, `stat`, `statm` and `status`, in the formats procps reads, and `/proc/self` is a link to the `/proc/<pid>` of the process that reads it, as on Linux (`readlink /proc/self` is its pid, the one it was exec'd under included). They come from the kernel's process table when they are opened, so a terminal sees the processes of every client; an exec'd program shows under the pid its parent knows. `/proc/uptime`, `/proc/loadavg`, `/proc/stat` and `/proc/meminfo` are there too, and `/proc/mounts`. A process with no parent in the kernel (one a page, a client or the Node entry started) has parent 1, as children of init do, in `getppid()`, `/proc/<pid>/stat` and `ps` alike, and so does a process whose parent has ended, waited for or not (`getppid()` asks the kernel each time; a shell's `$PPID` keeps the value it started with, as on Linux). Pids, parents, process groups, sessions, command lines, terminals, start and boot times are real, and so are the credentials in `status` (`Uid:`, `Gid:`, `Groups:`) and each process's memory: the size of its wasm memory (`VSZ` and `RSS` alike, as wasm has no paging), which `/proc/meminfo` counts as used. CPU times, load and `MemTotal` (from `navigator.deviceMemory`, else 4 GiB) are placeholders. A process that isn't root sees only its own user's processes (see [Users](#users)). WASI and WASIX programs see the same `/proc`, read-only (opening a file in it for writing fails with `EROFS`), from every thread, also through a symlink that points into it (where every change, `mkdir`, `rename`, `unlink`, `chmod` and so on, fails too: with `EROFS` when the root file system has a physical `/proc`, which stays hidden and untouched, and otherwise with the `ENOENT` of the missing directory, since that check is only paid for when a physical `/proc` exists), and `/` lists it, so a WASI program gets a preopen for it; `/proc/self` is the program's own pid there too (the pid an exec'd program took over). Neither has `/proc/<pid>/fd` yet.
|
|
503
|
+
Every process of the kernel has `/proc/<pid>/` with `cmdline`, `comm`, `stat`, `statm` and `status`, in the formats procps reads, and `/proc/self` is a link to the `/proc/<pid>` of the process that reads it, as on Linux (`readlink /proc/self` is its pid, the one it was exec'd under included). They come from the kernel's process table when they are opened, so a terminal sees the processes of every client; an exec'd program shows under the pid its parent knows. `/proc/uptime`, `/proc/loadavg`, `/proc/stat` and `/proc/meminfo` are there too, and `/proc/mounts`. A process with no parent in the kernel (one a page, a client or the Node entry started) has parent 1, as children of init do, in `getppid()`, `/proc/<pid>/stat` and `ps` alike, and so does a process whose parent has ended, waited for or not (`getppid()` asks the kernel each time; a shell's `$PPID` keeps the value it started with, as on Linux). Pids, parents, process groups, sessions, command lines, terminals, start and boot times are real, and so are the credentials in `status` (`Uid:`, `Gid:`, `Groups:`) and each process's memory: the size of its wasm memory (`VSZ` and `RSS` alike, as wasm has no paging), which `/proc/meminfo` counts as used. CPU times, load and `MemTotal` (from `navigator.deviceMemory`, else 4 GiB) are placeholders. A process that isn't root sees only its own user's processes (see [Users](#users)). WASI and WASIX programs see the same `/proc`, read-only (opening a file in it for writing fails with `EROFS`), from every thread, also through a symlink that points into it (where every change, `mkdir`, `rename`, `unlink`, `chmod` and so on, fails too: with `EROFS` when the root file system has a physical `/proc`, which stays hidden and untouched, and otherwise with the `ENOENT` of the missing directory, since that check is only paid for when a physical `/proc` exists; with one, a symlink whose target passes `..` after a file or a missing name, such as `/f/../proc`, fails with `ENOTDIR` or `ENOENT`, as on Linux), and `/` lists it, so a WASI program gets a preopen for it; `/proc/self` is the program's own pid there too (the pid an exec'd program took over). Neither has `/proc/<pid>/fd` yet.
|
|
504
504
|
|
|
505
505
|
The kernel writes `/etc/passwd` and `/etc/group` when it starts (see [Users](#users)). While the root has none of them, or no `/etc/mtab`, reading them gets per-process ones: root is uid 0 and the realm user, uid 1000, is named after `USER`, or `web_user` (Emscripten's default) without one, so it matches `$USER`, with `HOME` as its home, so `id`, `whoami`, `ls -l` and `ps` show names. Nothing is written to OPFS, and a real file there wins.
|
|
506
506
|
|
package/dist/kernel-worker.js
CHANGED
|
@@ -1806,9 +1806,9 @@ var KernelSocket = class _KernelSocket {
|
|
|
1806
1806
|
case SO_TYPE:
|
|
1807
1807
|
return SOCK_STREAM;
|
|
1808
1808
|
case SO_ERROR: {
|
|
1809
|
-
const
|
|
1809
|
+
const failed2 = this.failed;
|
|
1810
1810
|
this.failed = void 0;
|
|
1811
|
-
return
|
|
1811
|
+
return failed2 ? wasiErrno(failed2) : 0;
|
|
1812
1812
|
}
|
|
1813
1813
|
case SO_ACCEPTCONN:
|
|
1814
1814
|
return this.state === "listening" ? 1 : 0;
|
|
@@ -8022,6 +8022,7 @@ function* procWalk(path, follow2) {
|
|
|
8022
8022
|
let done4 = [];
|
|
8023
8023
|
let todo = path.split("/").filter((part) => part !== "");
|
|
8024
8024
|
let hops = 0;
|
|
8025
|
+
let broken;
|
|
8025
8026
|
while (todo.length > 0) {
|
|
8026
8027
|
const part = todo.shift();
|
|
8027
8028
|
if (part === ".") continue;
|
|
@@ -8031,19 +8032,31 @@ function* procWalk(path, follow2) {
|
|
|
8031
8032
|
}
|
|
8032
8033
|
const at = `/${[...done4, part].join("/")}`;
|
|
8033
8034
|
const whole = normalize(`/${[...done4, part, ...todo].join("/")}`);
|
|
8034
|
-
if (inProc(at) && inProc(whole)) return whole;
|
|
8035
|
+
if (inProc(at) && inProc(whole)) return broken ?? whole;
|
|
8035
8036
|
if (!follow2 && todo.length === 0) return void 0;
|
|
8036
|
-
const target = yield at;
|
|
8037
|
-
if (target ===
|
|
8038
|
-
if (target === void 0) done4.push(part);
|
|
8039
|
-
else {
|
|
8037
|
+
const target = yield { at, check: todo.find((next2) => next2 !== ".") === ".." };
|
|
8038
|
+
if (typeof target === "string") {
|
|
8040
8039
|
if (++hops > MAX_HOPS2) return void 0;
|
|
8041
|
-
|
|
8042
|
-
|
|
8040
|
+
[done4, todo] = throughLink(target, done4, todo);
|
|
8041
|
+
} else {
|
|
8042
|
+
broken ??= refusalOf(target);
|
|
8043
|
+
done4.push(part);
|
|
8043
8044
|
}
|
|
8044
8045
|
}
|
|
8045
8046
|
return void 0;
|
|
8046
8047
|
}
|
|
8048
|
+
function failed(err) {
|
|
8049
|
+
const code = err?.code;
|
|
8050
|
+
return { refused: typeof code === "string" ? code : "ENOENT" };
|
|
8051
|
+
}
|
|
8052
|
+
function refusalOf(found) {
|
|
8053
|
+
if (found === true) return void 0;
|
|
8054
|
+
return found === false ? { refused: "ENOTDIR" } : found;
|
|
8055
|
+
}
|
|
8056
|
+
function throughLink(target, done4, todo) {
|
|
8057
|
+
const parts = target.split("/").filter((part) => part !== "");
|
|
8058
|
+
return [target.startsWith("/") ? [] : done4, [...parts, ...todo]];
|
|
8059
|
+
}
|
|
8047
8060
|
async function procAlias(fs, path, follow2) {
|
|
8048
8061
|
const { readlink, lstat } = fs;
|
|
8049
8062
|
if (!readlink || !lstat) return void 0;
|
|
@@ -8055,7 +8068,10 @@ async function procAlias(fs, path, follow2) {
|
|
|
8055
8068
|
const walk = procWalk(path, follow2);
|
|
8056
8069
|
let step = walk.next();
|
|
8057
8070
|
while (!step.done) {
|
|
8058
|
-
const
|
|
8071
|
+
const { at, check } = step.value;
|
|
8072
|
+
const link2 = await readlink.call(fs, at).catch(() => void 0);
|
|
8073
|
+
const isDir = (st) => st.isDirectory === true;
|
|
8074
|
+
const target = link2 ?? (check ? await lstat.call(fs, at).then(isDir, failed) : true);
|
|
8059
8075
|
step = walk.next(target);
|
|
8060
8076
|
}
|
|
8061
8077
|
return step.value;
|
|
@@ -10101,6 +10117,8 @@ var WasmProcess = class {
|
|
|
10101
10117
|
async openVfs(req) {
|
|
10102
10118
|
if (inProc(req.path)) return this.openProc(req, req.path);
|
|
10103
10119
|
const alias = req.orphan ? void 0 : await procAlias(this.options.fs, req.path, !req.exclusive);
|
|
10120
|
+
if (typeof alias === "object")
|
|
10121
|
+
throw Object.assign(new Error(alias.refused), { code: alias.refused });
|
|
10104
10122
|
if (alias !== void 0) return this.openProc(req, alias);
|
|
10105
10123
|
await refuseReadonly(this.options.fs, req.path, req.flags, req);
|
|
10106
10124
|
const fd = this.fds.install(heldFile(), 3);
|
|
@@ -11757,22 +11775,22 @@ async function mountFstab(lines2, mount, signal, retries = FSTAB_RETRIES, discar
|
|
|
11757
11775
|
);
|
|
11758
11776
|
}
|
|
11759
11777
|
async function mountLine({ line, spec, invalid }, mount, signal, retries, discard) {
|
|
11760
|
-
let
|
|
11778
|
+
let failed2 = invalid;
|
|
11761
11779
|
for (let attempt = 0; !invalid && attempt <= retries.length && !signal.aborted; attempt++) {
|
|
11762
11780
|
try {
|
|
11763
11781
|
const entry = await mount(spec);
|
|
11764
11782
|
if (!signal.aborted) return { line, target: spec.target, entry };
|
|
11765
11783
|
discard(entry);
|
|
11766
|
-
|
|
11784
|
+
failed2 = Object.assign(new Error("the kernel stopped"), { code: "ECANCELED" });
|
|
11767
11785
|
break;
|
|
11768
11786
|
} catch (err) {
|
|
11769
|
-
|
|
11787
|
+
failed2 = err;
|
|
11770
11788
|
if (FINAL.has(codeOf3(err)) || attempt === retries.length) break;
|
|
11771
11789
|
await wait(retries[attempt], signal);
|
|
11772
11790
|
}
|
|
11773
11791
|
}
|
|
11774
|
-
const code = codeOf3(
|
|
11775
|
-
return { line, target: spec.target, code, error: String(
|
|
11792
|
+
const code = codeOf3(failed2);
|
|
11793
|
+
return { line, target: spec.target, code, error: String(failed2?.message ?? code) };
|
|
11776
11794
|
}
|
|
11777
11795
|
|
|
11778
11796
|
// src/mount/hostfs.ts
|
|
@@ -13078,7 +13096,7 @@ var Launcher = class {
|
|
|
13078
13096
|
this.processes.set(pid, handle);
|
|
13079
13097
|
const { cred } = req;
|
|
13080
13098
|
this.creds.set(pid, () => handle.cred?.() ?? copyCred(cred));
|
|
13081
|
-
if (req.ppid !== void 0) this.settling.started(pid, req.ppid);
|
|
13099
|
+
if (req.ppid !== void 0 && this.controlsJobs(req.ppid)) this.settling.started(pid, req.ppid);
|
|
13082
13100
|
this.jobs.add(pid, req.ppid, (sig) => handle.signal(sig), terminal);
|
|
13083
13101
|
if (req.exec && req.ppid !== void 0) this.jobs.exec(req.ppid, pid, true);
|
|
13084
13102
|
if (req.pgid !== void 0) this.jobs.join(pid, req.pgid);
|
|
@@ -13248,6 +13266,9 @@ var Launcher = class {
|
|
|
13248
13266
|
const masks = this.processes.get(pid)?.signalMasks?.();
|
|
13249
13267
|
return masks ? { sigIgn: masks.ignored, sigCgt: masks.caught } : {};
|
|
13250
13268
|
}
|
|
13269
|
+
controlsJobs(pid) {
|
|
13270
|
+
return this.settling.isSettling(pid) || (this.ignoredBy(pid) & sigbit(SIG.TTOU)) !== 0;
|
|
13271
|
+
}
|
|
13251
13272
|
ignoredBy(pid, fork = false) {
|
|
13252
13273
|
return this.processes.get(pid)?.ignoredSignals(fork) ?? 0;
|
|
13253
13274
|
}
|