@ai-ecoverse/slicc-kernel 1.33.0 → 1.34.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/README.md +4 -2
- package/dist/kernel-worker.js +61 -29
- package/dist/kernel-worker.js.map +3 -3
- package/dist/node-process-worker.js +157 -27
- package/dist/node-process-worker.js.map +3 -3
- package/dist/node.js +66 -33
- package/dist/node.js.map +3 -3
- package/dist/process-worker.js +157 -27
- package/dist/process-worker.js.map +3 -3
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -65,7 +65,7 @@ Starts `argv` as the session leader on a new terminal and resolves once it is ru
|
|
|
65
65
|
| --- | --- |
|
|
66
66
|
| `pid` | the session leader's pid |
|
|
67
67
|
| `onData` | the output listener; output that arrives while it is `null` is kept and delivered when one is set |
|
|
68
|
-
| `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
|
|
68
|
+
| `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. |
|
|
69
69
|
| `resize(cols, rows)` | sets the window size (`TIOCGWINSZ`) and sends `SIGWINCH` to the foreground process group |
|
|
70
70
|
| `signal(name)` | sends a signal (`'SIGINT'`, `'SIGTSTP'`, `'SIGQUIT'`, `'SIGHUP'`, …) to the foreground process group, regardless of termios |
|
|
71
71
|
| `exited` | resolves with the session leader's exit status (`128 + n` if killed by signal `n`) |
|
|
@@ -334,6 +334,8 @@ The proxy answers `200` with `Content-Type: application/vnd.slicc.raw-fetch`: a
|
|
|
334
334
|
|
|
335
335
|
Directories are renamed with `FileSystemHandle.move()` where available, else by copy and delete.
|
|
336
336
|
|
|
337
|
+
`mmap` of a regular file works on every file system, for Emscripten programs built with `mmap` (whose glue exports `emscripten_builtin_memalign`) as well as for WASIX ones: the kernel reads the mapped range into fresh memory of the process, as Emscripten's MEMFS does. A `MAP_PRIVATE` mapping may be written, and its changes stay in the process. A writable `MAP_SHARED` mapping of such a file is `ENODEV`; a file in the process's own in-memory file system (MEMFS, as under `/dev`) keeps Emscripten's own mapping, which writes back. A read-only `MAP_SHARED` mapping is a copy taken at `mmap`, so later changes to the file do not show in it.
|
|
338
|
+
|
|
337
339
|
### Mounts
|
|
338
340
|
|
|
339
341
|
`kernel.mount({ type, source, target, options })` mounts a file system on an existing directory. Every process sees it, Emscripten and WASI alike, and so does every attached client. `kernel.umount(target)` unmounts it, and fails with `EBUSY` while a process has a file open under it. `kernel.mounts()` lists the table, as does `/proc/mounts`. The Node entry and attached clients have the same three calls (client protocol 1.2).
|
|
@@ -347,7 +349,7 @@ Directories are renamed with `FileSystemHandle.move()` where available, else by
|
|
|
347
349
|
- `@ai-ecoverse/slicc-kernel/driver` exports the types and `fsError`.
|
|
348
350
|
- **Capabilities:** `readonly`, `symlinks`, `chmod`, `ranges`, `maxIo` (the largest read or write per message), `maxFile`, `listingStats`, `linkTimes`, and `attrTtl`/`entryTtl` (in ms, default 1000). `linkTimes` promises that `setattr` with `mtime` on a symlink sets the link's own time. Without it, setting a link's own times (`touch -h`, `utimensat` with `AT_SYMLINK_NOFOLLOW`) fails with `EOPNOTSUPP` and never reaches the target. tmpfs has it; hostfs does not. Setting times through a link follows it in the kernel, so `setattr` gets the target. `ranges` (driver protocol 1.1) promises that a write lands in the file at once, with no copy of the whole file on `open` or `release`, and that `setattr` takes `size`.
|
|
349
351
|
- **Caching:** the kernel caches attributes and listings for those TTLs, and a driver can push `invalidate` for changes made outside.
|
|
350
|
-
- **File contents:** on a mount with `ranges`, as `tmpfs` is, and on the root file system when it is in OPFS (in a browser with sync access handles) or an in-memory root, a program reads and writes a file in 64 KiB pages: reads come from the driver 1 MiB at a time, and changed pages go back in 1 MiB runs once 8 MiB have changed, at `fsync`, and when the file is closed (from the kernel's own descriptors also 250 ms after a write). A file of any size the mount can hold works, with memory to spare for about one more copy of it. In OPFS a write opens a sync access handle for just that write. Elsewhere (`fsa` mounts, `hostfs` with an older proxy, and a root outside OPFS), file contents are read whole when a program opens a file and written back when it closes it.
|
|
352
|
+
- **File contents:** on a mount with `ranges`, as `tmpfs` is, and on the root file system when it is in OPFS (in a browser with sync access handles) or an in-memory root, a program reads and writes a file in 64 KiB pages: reads come from the driver 1 MiB at a time, and changed pages go back in 1 MiB runs once 8 MiB have changed, at `fsync`, and when the file is closed (from the kernel's own descriptors also 250 ms after a write). A file of any size the mount can hold works, with memory to spare for about one more copy of it. In OPFS a write opens a sync access handle for just that write. Elsewhere (`fsa` mounts, `hostfs` with an older proxy, and a root outside OPFS), file contents are read whole when a program opens a file and written back when it closes it. Before a program's next call into the kernel (any wait, read of a pipe or terminal, sleep or spawn), its changed pages also go back, so other processes and the embedder see a write as soon as the writer does anything but compute: a daemon's pidfile, a log another process follows. On a whole-file mount that holds only for files of up to 64 KiB; a larger one goes back at close, fsync and exit, as before. Beyond that, both give close-to-open consistency, as NFS has. A process that ends without closing its files keeps what it wrote: on exit, on a trap, and when a signal kills it. For a killed WASI program, the kernel lets its worker write its changed pages back before `waitpid` reports the death. The only exception is a worker that makes no call into the kernel for 2 seconds after the signal; it is stopped without that write-back.
|
|
351
353
|
- **Rules on a mount:**
|
|
352
354
|
- a rename across mounts is `EXDEV`, so `mv` copies;
|
|
353
355
|
- `df` reports each mount from its driver's `statfs`;
|
package/dist/kernel-worker.js
CHANGED
|
@@ -1144,14 +1144,6 @@ function sinkFile(onData) {
|
|
|
1144
1144
|
}
|
|
1145
1145
|
});
|
|
1146
1146
|
}
|
|
1147
|
-
function nullFile() {
|
|
1148
|
-
return new OpenFile({
|
|
1149
|
-
read: async () => new Uint8Array(0),
|
|
1150
|
-
write: async (bytes) => bytes.length,
|
|
1151
|
-
close: () => {
|
|
1152
|
-
}
|
|
1153
|
-
});
|
|
1154
|
-
}
|
|
1155
1147
|
function written(device, bytes) {
|
|
1156
1148
|
if (device === "full" && bytes.length > 0) throw new KernelError("ENOSPC");
|
|
1157
1149
|
return bytes.length;
|
|
@@ -5003,7 +4995,7 @@ var ChildTable = class {
|
|
|
5003
4995
|
captured.set(n, chunks);
|
|
5004
4996
|
return sinkFile((bytes) => chunks.push(bytes));
|
|
5005
4997
|
}
|
|
5006
|
-
return
|
|
4998
|
+
return deviceFile("null");
|
|
5007
4999
|
}
|
|
5008
5000
|
async wait(pid, nohang, signal, flags = {}) {
|
|
5009
5001
|
let interrupt;
|
|
@@ -6568,6 +6560,8 @@ var KernelTty = class {
|
|
|
6568
6560
|
waiters = [];
|
|
6569
6561
|
hungUp = false;
|
|
6570
6562
|
name;
|
|
6563
|
+
onRead;
|
|
6564
|
+
readers = 0;
|
|
6571
6565
|
screen;
|
|
6572
6566
|
signal;
|
|
6573
6567
|
constructor(screen, signal) {
|
|
@@ -6718,7 +6712,19 @@ var KernelTty = class {
|
|
|
6718
6712
|
}
|
|
6719
6713
|
this.screen.write(Uint8Array.from(out));
|
|
6720
6714
|
}
|
|
6715
|
+
get reading() {
|
|
6716
|
+
return this.readers > 0;
|
|
6717
|
+
}
|
|
6721
6718
|
async read(max, signal) {
|
|
6719
|
+
this.onRead?.();
|
|
6720
|
+
this.readers++;
|
|
6721
|
+
try {
|
|
6722
|
+
return await this.take(max, signal);
|
|
6723
|
+
} finally {
|
|
6724
|
+
this.readers--;
|
|
6725
|
+
}
|
|
6726
|
+
}
|
|
6727
|
+
async take(max, signal) {
|
|
6722
6728
|
while (this.readable.length === 0) {
|
|
6723
6729
|
if (this.hungUp) return new Uint8Array(0);
|
|
6724
6730
|
await this.changed(signal);
|
|
@@ -6783,13 +6789,14 @@ var PtyPair = class {
|
|
|
6783
6789
|
slaveSeen = false;
|
|
6784
6790
|
index;
|
|
6785
6791
|
onFree;
|
|
6786
|
-
constructor(index, signal, onFree) {
|
|
6792
|
+
constructor(index, signal, onFree, onRead) {
|
|
6787
6793
|
this.index = index;
|
|
6788
6794
|
this.onFree = onFree;
|
|
6789
6795
|
this.slave = new KernelTty(
|
|
6790
6796
|
{ write: (bytes) => this.fromSlave(bytes) },
|
|
6791
6797
|
(sig) => signal(this.slave, sig)
|
|
6792
6798
|
);
|
|
6799
|
+
if (onRead) this.slave.onRead = () => onRead(this.slave);
|
|
6793
6800
|
this.slave.name = `/dev/pts/${index}`;
|
|
6794
6801
|
}
|
|
6795
6802
|
master() {
|
|
@@ -6887,13 +6894,15 @@ var PtyPair = class {
|
|
|
6887
6894
|
var PtyTable = class {
|
|
6888
6895
|
pairs = /* @__PURE__ */ new Map();
|
|
6889
6896
|
signal;
|
|
6890
|
-
|
|
6897
|
+
onRead;
|
|
6898
|
+
constructor(signal, onRead) {
|
|
6891
6899
|
this.signal = signal;
|
|
6900
|
+
this.onRead = onRead;
|
|
6892
6901
|
}
|
|
6893
6902
|
open() {
|
|
6894
6903
|
let index = 0;
|
|
6895
6904
|
while (this.pairs.has(index)) index += 1;
|
|
6896
|
-
const pair = new PtyPair(index, this.signal, (p) => this.pairs.delete(p.index));
|
|
6905
|
+
const pair = new PtyPair(index, this.signal, (p) => this.pairs.delete(p.index), this.onRead);
|
|
6897
6906
|
this.pairs.set(index, pair);
|
|
6898
6907
|
return { pair, master: pair.master() };
|
|
6899
6908
|
}
|
|
@@ -8446,6 +8455,7 @@ var WasmProcess = class {
|
|
|
8446
8455
|
async exit() {
|
|
8447
8456
|
if (this.exited) return;
|
|
8448
8457
|
this.exited = true;
|
|
8458
|
+
this.die();
|
|
8449
8459
|
this.clearAlarm();
|
|
8450
8460
|
this.interrupt.abort();
|
|
8451
8461
|
this.options.locks?.release(this.pid);
|
|
@@ -8858,13 +8868,16 @@ var SETUP_OPS = /* @__PURE__ */ new Set([
|
|
|
8858
8868
|
"fd-info",
|
|
8859
8869
|
"sig-mask",
|
|
8860
8870
|
"proc-setpgid",
|
|
8861
|
-
"tty-pgrp-set"
|
|
8871
|
+
"tty-pgrp-set",
|
|
8872
|
+
"proc-exec",
|
|
8873
|
+
"proc-spawn"
|
|
8862
8874
|
]);
|
|
8863
|
-
var SETTLE_MS =
|
|
8875
|
+
var SETTLE_MS = 2e3;
|
|
8864
8876
|
var KEY_SIGNALS = /* @__PURE__ */ new Set([2, 3, 20]);
|
|
8865
8877
|
var SettlingChildren = class {
|
|
8866
8878
|
children = /* @__PURE__ */ new Map();
|
|
8867
8879
|
held = [];
|
|
8880
|
+
parked = [];
|
|
8868
8881
|
pgidOf;
|
|
8869
8882
|
settleMs;
|
|
8870
8883
|
constructor(pgidOf, settleMs = SETTLE_MS) {
|
|
@@ -8872,7 +8885,15 @@ var SettlingChildren = class {
|
|
|
8872
8885
|
this.settleMs = settleMs;
|
|
8873
8886
|
}
|
|
8874
8887
|
started(pid, ppid) {
|
|
8875
|
-
this.children.
|
|
8888
|
+
const parent = this.children.get(ppid);
|
|
8889
|
+
this.children.set(pid, parent ? { ppid: parent.ppid, execer: ppid } : { ppid });
|
|
8890
|
+
const group = this.pgidOf(ppid);
|
|
8891
|
+
for (const p of this.parked) if (p.pgid === group) this.held.push({ ...p, copy: true });
|
|
8892
|
+
this.parked = this.parked.filter((p) => p.pgid !== group);
|
|
8893
|
+
}
|
|
8894
|
+
reading(pgid) {
|
|
8895
|
+
this.parked = this.parked.filter((p) => p.pgid !== pgid);
|
|
8896
|
+
this.held = this.held.filter((h) => !(h.copy && h.pgid === pgid));
|
|
8876
8897
|
}
|
|
8877
8898
|
syscall(pid, op) {
|
|
8878
8899
|
const child = this.children.get(pid);
|
|
@@ -8888,13 +8909,25 @@ var SettlingChildren = class {
|
|
|
8888
8909
|
if (!child) return;
|
|
8889
8910
|
clearTimeout(child.timer);
|
|
8890
8911
|
this.children.delete(pid);
|
|
8912
|
+
if (child.execer !== void 0) this.settled(child.execer);
|
|
8891
8913
|
const ready2 = this.held.filter((h) => !this.blocking(h.pgid));
|
|
8892
8914
|
this.held = this.held.filter((h) => this.blocking(h.pgid));
|
|
8893
8915
|
for (const h of ready2) h.send();
|
|
8894
8916
|
}
|
|
8895
|
-
deliver(sig, pgid, send) {
|
|
8896
|
-
if (KEY_SIGNALS.has(sig)
|
|
8897
|
-
|
|
8917
|
+
deliver(sig, pgid, send, foreground) {
|
|
8918
|
+
if (!KEY_SIGNALS.has(sig)) {
|
|
8919
|
+
send();
|
|
8920
|
+
} else if (this.blocking(pgid)) {
|
|
8921
|
+
this.held.push({ pgid, send });
|
|
8922
|
+
} else if (foreground) {
|
|
8923
|
+
send();
|
|
8924
|
+
const moved2 = () => {
|
|
8925
|
+
if (foreground() !== pgid) send();
|
|
8926
|
+
};
|
|
8927
|
+
this.parked.push({ pgid, send: moved2 });
|
|
8928
|
+
} else {
|
|
8929
|
+
send();
|
|
8930
|
+
}
|
|
8898
8931
|
}
|
|
8899
8932
|
blocking(pgid) {
|
|
8900
8933
|
for (const { ppid } of this.children.values()) if (this.pgidOf(ppid) === pgid) return true;
|
|
@@ -10164,11 +10197,8 @@ var Launcher = class {
|
|
|
10164
10197
|
locks = new LockTable();
|
|
10165
10198
|
settling = new SettlingChildren((pid) => this.jobs.pgidOf(pid));
|
|
10166
10199
|
ptys = new PtyTable(
|
|
10167
|
-
(tty, sig) => this.
|
|
10168
|
-
|
|
10169
|
-
this.jobs.tcgetpgrp(tty, 0),
|
|
10170
|
-
() => this.jobs.signalOwnedForeground(tty, sig)
|
|
10171
|
-
)
|
|
10200
|
+
(tty, sig) => this.keySignal(tty, 0, sig, () => this.jobs.signalOwnedForeground(tty, sig)),
|
|
10201
|
+
(tty) => this.settling.reading(this.jobs.tcgetpgrp(tty, 0))
|
|
10172
10202
|
);
|
|
10173
10203
|
net = new LoopbackNet();
|
|
10174
10204
|
cdp;
|
|
@@ -10584,6 +10614,11 @@ var Launcher = class {
|
|
|
10584
10614
|
})
|
|
10585
10615
|
);
|
|
10586
10616
|
}
|
|
10617
|
+
keySignal(tty, fallback, sig, send) {
|
|
10618
|
+
const pgid = this.jobs.tcgetpgrp(tty, fallback);
|
|
10619
|
+
const foreground = () => this.jobs.tcgetpgrp(tty, fallback);
|
|
10620
|
+
this.settling.deliver(sig, pgid, send, tty.reading ? void 0 : foreground);
|
|
10621
|
+
}
|
|
10587
10622
|
ignoredBy(pid, fork = false) {
|
|
10588
10623
|
return this.processes.get(pid)?.ignoredSignals(fork) ?? 0;
|
|
10589
10624
|
}
|
|
@@ -10779,12 +10814,9 @@ var Launcher = class {
|
|
|
10779
10814
|
let leader = 0;
|
|
10780
10815
|
const tty = new KernelTty(
|
|
10781
10816
|
{ write: (bytes) => options.onData(bytes.slice()) },
|
|
10782
|
-
(sig) => this.
|
|
10783
|
-
sig,
|
|
10784
|
-
this.jobs.tcgetpgrp(tty, leader),
|
|
10785
|
-
() => this.jobs.signalForeground(tty, leader, sig)
|
|
10786
|
-
)
|
|
10817
|
+
(sig) => this.keySignal(tty, leader, sig, () => this.jobs.signalForeground(tty, leader, sig))
|
|
10787
10818
|
);
|
|
10819
|
+
tty.onRead = () => this.settling.reading(this.jobs.tcgetpgrp(tty, leader));
|
|
10788
10820
|
tty.name = `/dev/tty${++this.terminals}`;
|
|
10789
10821
|
tty.setSize(options.cols ?? 80, options.rows ?? 24);
|
|
10790
10822
|
const fds = new FdTable();
|
|
@@ -10833,7 +10865,7 @@ var Launcher = class {
|
|
|
10833
10865
|
return { status: NOT_FOUND, stdout: concat5(out), stderr: concat5(err) };
|
|
10834
10866
|
}
|
|
10835
10867
|
const fds = new FdTable();
|
|
10836
|
-
fds.installAt(0, options.stdin ? bytesSource(options.stdin) :
|
|
10868
|
+
fds.installAt(0, options.stdin ? bytesSource(options.stdin) : deviceFile("null"));
|
|
10837
10869
|
fds.installAt(1, sinkFile(stdout));
|
|
10838
10870
|
fds.installAt(2, sinkFile(stderr));
|
|
10839
10871
|
const group = options.pgid !== void 0 ? { pgid: options.pgid } : {};
|