@ai-ecoverse/slicc-kernel 1.34.0 → 1.35.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 +17 -1
- package/dist/kernel-worker.js +43 -17
- package/dist/kernel-worker.js.map +2 -2
- package/dist/node-process-worker.js +246 -19
- package/dist/node-process-worker.js.map +3 -3
- package/dist/node.js +48 -21
- package/dist/node.js.map +2 -2
- package/dist/process-worker.js +246 -19
- package/dist/process-worker.js.map +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -349,7 +349,7 @@ Directories are renamed with `FileSystemHandle.move()` where available, else by
|
|
|
349
349
|
- `@ai-ecoverse/slicc-kernel/driver` exports the types and `fsError`.
|
|
350
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`.
|
|
351
351
|
- **Caching:** the kernel caches attributes and listings for those TTLs, and a driver can push `invalidate` for changes made outside.
|
|
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.
|
|
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.
|
|
353
353
|
- **Rules on a mount:**
|
|
354
354
|
- a rename across mounts is `EXDEV`, so `mv` copies;
|
|
355
355
|
- `df` reports each mount from its driver's `statfs`;
|
|
@@ -421,6 +421,22 @@ An exec'd program takes over the pid of the process it replaces, as on Linux: `g
|
|
|
421
421
|
- **Emscripten:** `Module.sliccKernel.execve(file, argv, env, cwd)` runs `file` as this process's new image on its fds 0, 1 and 2, waits for it, and returns its wait status, or a negative errno when it cannot start. `env` and `cwd` may be `null` for this process's own. An exec shim detects it with `typeof Module.sliccKernel.execve === 'function'` and exits with the status it returns, as `execve` never returns on success. Older shims spawn the program and then call `Module.sliccKernel.execWait(pid)`; that works the same way: a new program asks the kernel for its pid before `main`, and the kernel answers once its parent has made its next call, so an image an older shim execs also reports the pid it replaced (the wait starts once the program is loaded; should the parent make no call within 100 ms after that, the kernel answers anyway, and the image may then report its own pid; `launcher.identities` counts the programs that asked and those answered by that timeout).
|
|
422
422
|
- **WASIX:** `proc_exec` does this by itself.
|
|
423
423
|
|
|
424
|
+
### File modes and the umask
|
|
425
|
+
|
|
426
|
+
Every process has a umask in the kernel, `022` for a process the embedder starts, inherited across fork, spawn and exec and shown in `/proc/<pid>/status` (`Umask:`). The kernel applies it when a program creates a file or directory, whatever its libc does: an Emscripten program's `open` with `O_CREAT`, `mkdir` and `mknod` (so `umask 077; touch f` gives `600` in a child too), and a WASI program's `path_open` with `O_CREAT` and `path_create_directory` (`0666` and `0777` less the umask).
|
|
427
|
+
|
|
428
|
+
WASI and WASIX have no call for file modes, so the kernel adds the import module `slicc_fs`, whose functions return a preview1 errno (kernels without them answer `ENOSYS`):
|
|
429
|
+
|
|
430
|
+
| Import | Does |
|
|
431
|
+
| --- | --- |
|
|
432
|
+
| `fd_chmod(fd, mode)` | `fchmod` on an open file or directory |
|
|
433
|
+
| `path_chmod(dirfd, path, path_len, mode, flags)` | `fchmodat`; with `flags & 1` (`AT_SYMLINK_NOFOLLOW`) a symlink answers `ENOTSUP` and is never followed |
|
|
434
|
+
| `umask(mask, old_out)` | sets the process's umask and writes the previous one |
|
|
435
|
+
| `fd_mode(fd, mode_out)` | the permission bits of an open file or directory |
|
|
436
|
+
| `path_mode(dirfd, path, path_len, flags, mode_out)` | those of a path, of the link itself with `flags & 1` |
|
|
437
|
+
|
|
438
|
+
Lowering a soft resource limit (`ulimit -S -n 64`) still fails with `EPERM` in Emscripten programs: their libc's `setrlimit` is a stub compiled into the program, not a call to the kernel.
|
|
439
|
+
|
|
424
440
|
### `/proc`
|
|
425
441
|
|
|
426
442
|
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 is 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. Only Emscripten processes see this `/proc`.
|
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;
|
|
@@ -7865,10 +7857,12 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
|
|
|
7865
7857
|
"fd-cloexec",
|
|
7866
7858
|
"fd-list",
|
|
7867
7859
|
"proc-alarm",
|
|
7860
|
+
"proc-umask",
|
|
7868
7861
|
"dl-log",
|
|
7869
7862
|
"fd-renumber",
|
|
7870
7863
|
"fd-promote",
|
|
7871
7864
|
"fd-open-tty",
|
|
7865
|
+
"fd-tty-names",
|
|
7872
7866
|
"tty-get",
|
|
7873
7867
|
"tty-set",
|
|
7874
7868
|
"tty-winsz",
|
|
@@ -7902,6 +7896,7 @@ function isWasmSyscall(req) {
|
|
|
7902
7896
|
return typeof op === "string" && SYSCALL_OPS.has(op);
|
|
7903
7897
|
}
|
|
7904
7898
|
var MAX_READ = 1024 * 1024;
|
|
7899
|
+
var DEFAULT_UMASK = 18;
|
|
7905
7900
|
var DYING = { ok: false, errno: "EINTR", message: "the process is being killed" };
|
|
7906
7901
|
var HOSTS_ONLY = new Resolver({ routes: new Routes() });
|
|
7907
7902
|
var WasmProcess = class {
|
|
@@ -7912,6 +7907,7 @@ var WasmProcess = class {
|
|
|
7912
7907
|
inherited = 0;
|
|
7913
7908
|
interrupt = new AbortController();
|
|
7914
7909
|
dying = false;
|
|
7910
|
+
umask = DEFAULT_UMASK;
|
|
7915
7911
|
execChild;
|
|
7916
7912
|
execTermsig;
|
|
7917
7913
|
stopped = 0;
|
|
@@ -7929,6 +7925,7 @@ var WasmProcess = class {
|
|
|
7929
7925
|
this.fds = fds;
|
|
7930
7926
|
this.options = options;
|
|
7931
7927
|
this.inherited = options.ignored ?? 0;
|
|
7928
|
+
this.umask = options.umask ?? DEFAULT_UMASK;
|
|
7932
7929
|
this.children = new ChildTable(fds, options.spawner, options.forker);
|
|
7933
7930
|
this.nodes = options.nodes ?? new VfsNodes(options.fs);
|
|
7934
7931
|
if (!options.nodes) options.openFiles?.add(this.nodes);
|
|
@@ -8001,6 +7998,11 @@ var WasmProcess = class {
|
|
|
8001
7998
|
return result;
|
|
8002
7999
|
}
|
|
8003
8000
|
}
|
|
8001
|
+
setUmask(mask) {
|
|
8002
|
+
const old = this.umask;
|
|
8003
|
+
if (mask !== void 0) this.umask = mask & 511;
|
|
8004
|
+
return old;
|
|
8005
|
+
}
|
|
8004
8006
|
die() {
|
|
8005
8007
|
if (this.dying) return;
|
|
8006
8008
|
this.dying = true;
|
|
@@ -8165,6 +8167,8 @@ var WasmProcess = class {
|
|
|
8165
8167
|
if (!tty) throw new KernelError("ENXIO");
|
|
8166
8168
|
return { ok: true, kind: "json", json: this.fds.install(tty.file(), 3) };
|
|
8167
8169
|
}
|
|
8170
|
+
case "fd-tty-names":
|
|
8171
|
+
return { ok: true, kind: "json", json: this.options.jobs?.terminalNames() ?? [] };
|
|
8168
8172
|
case "fd-select": {
|
|
8169
8173
|
const { read, write, timeoutMs } = req;
|
|
8170
8174
|
const signal = this.blockingSignal();
|
|
@@ -8396,6 +8400,8 @@ var WasmProcess = class {
|
|
|
8396
8400
|
case "proc-alarm":
|
|
8397
8401
|
this.setAlarm(req.sig, req.firstMs ?? req.ms, req.repeat ? req.ms : 0, req.timer);
|
|
8398
8402
|
return { ok: true, kind: "void" };
|
|
8403
|
+
case "proc-umask":
|
|
8404
|
+
return { ok: true, kind: "json", json: this.setUmask(req.mask) };
|
|
8399
8405
|
case "dl-log":
|
|
8400
8406
|
if (req.append) this.dlLog.push(req.append);
|
|
8401
8407
|
return { ok: true, kind: "json", json: this.dlLog.slice(req.from) };
|
|
@@ -8545,7 +8551,7 @@ var Dying = class {
|
|
|
8545
8551
|
return [typeof reported === "number" ? reported : CRASHED, void 0];
|
|
8546
8552
|
}
|
|
8547
8553
|
};
|
|
8548
|
-
function processInit(opts, sab) {
|
|
8554
|
+
function processInit(opts, sab, umask) {
|
|
8549
8555
|
const base = {
|
|
8550
8556
|
type: WASM_PROCESS_INIT,
|
|
8551
8557
|
pid: opts.shownPid ?? opts.pid,
|
|
@@ -8554,6 +8560,7 @@ function processInit(opts, sab) {
|
|
|
8554
8560
|
env: opts.env,
|
|
8555
8561
|
cwd: opts.cwd,
|
|
8556
8562
|
sab,
|
|
8563
|
+
umask,
|
|
8557
8564
|
...opts.ppid !== void 0 ? { ppid: opts.ppid } : {}
|
|
8558
8565
|
};
|
|
8559
8566
|
if (opts.program.abi === "js") {
|
|
@@ -8565,11 +8572,17 @@ function processInit(opts, sab) {
|
|
|
8565
8572
|
...opts.fork ? { fork: opts.fork } : { fds: inheritedFds(opts.fds) }
|
|
8566
8573
|
};
|
|
8567
8574
|
}
|
|
8575
|
+
function inherited(opts) {
|
|
8576
|
+
return {
|
|
8577
|
+
...opts.ignored ? { ignored: opts.ignored } : {},
|
|
8578
|
+
...opts.umask !== void 0 ? { umask: opts.umask } : {}
|
|
8579
|
+
};
|
|
8580
|
+
}
|
|
8568
8581
|
function spawnWasmProcess(opts) {
|
|
8569
8582
|
const sab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
|
|
8570
8583
|
const header2 = new Int32Array(sab, 0, SAB_HEADER_I32);
|
|
8571
8584
|
const process = new WasmProcess(opts.pid, opts.fds, {
|
|
8572
|
-
...opts
|
|
8585
|
+
...inherited(opts),
|
|
8573
8586
|
...opts.identity ? { identity: opts.identity } : {},
|
|
8574
8587
|
...opts.onSyscall ? { onSyscall: opts.onSyscall } : {},
|
|
8575
8588
|
spawner: opts.spawner,
|
|
@@ -8703,7 +8716,7 @@ function spawnWasmProcess(opts) {
|
|
|
8703
8716
|
};
|
|
8704
8717
|
tw.postMessage(tinit);
|
|
8705
8718
|
};
|
|
8706
|
-
const init = processInit(opts, sab);
|
|
8719
|
+
const init = processInit(opts, sab, process.umask);
|
|
8707
8720
|
worker.postMessage(init, opts.fork ? [opts.fork.memory.buffer] : []);
|
|
8708
8721
|
const signal = (sig) => {
|
|
8709
8722
|
if (process.signal(sig) !== "terminate") return;
|
|
@@ -8721,6 +8734,7 @@ function spawnWasmProcess(opts) {
|
|
|
8721
8734
|
onState: (listener) => process.onState(listener),
|
|
8722
8735
|
memory: () => Atomics.load(header2, SAB_I_MEMORY) * 65536,
|
|
8723
8736
|
ignoredSignals: (fork) => process.inheritable(fork),
|
|
8737
|
+
umask: () => process.umask,
|
|
8724
8738
|
writesBack: () => opts.program.abi === "wasi" && !done4
|
|
8725
8739
|
};
|
|
8726
8740
|
}
|
|
@@ -8826,8 +8840,13 @@ var JobTable = class {
|
|
|
8826
8840
|
return id;
|
|
8827
8841
|
}
|
|
8828
8842
|
terminalNamed(name2) {
|
|
8829
|
-
|
|
8830
|
-
|
|
8843
|
+
return this.knownTerminals().find((tty) => tty.name === name2);
|
|
8844
|
+
}
|
|
8845
|
+
terminalNames() {
|
|
8846
|
+
return this.knownTerminals().flatMap((tty) => tty.name ? [tty.name] : []);
|
|
8847
|
+
}
|
|
8848
|
+
knownTerminals() {
|
|
8849
|
+
return [.../* @__PURE__ */ new Set([...this.terminals.values(), ...this.foreground.keys()])];
|
|
8831
8850
|
}
|
|
8832
8851
|
controllingTerminal(pid) {
|
|
8833
8852
|
const member = this.members.get(pid);
|
|
@@ -10461,6 +10480,7 @@ var Launcher = class {
|
|
|
10461
10480
|
this.settling.syscall(pid, call.op);
|
|
10462
10481
|
},
|
|
10463
10482
|
...req.ignored ? { ignored: req.ignored } : {},
|
|
10483
|
+
...req.umask !== void 0 ? { umask: req.umask } : {},
|
|
10464
10484
|
program: req.program,
|
|
10465
10485
|
argv0: req.argv0,
|
|
10466
10486
|
args: req.args,
|
|
@@ -10552,7 +10572,8 @@ var Launcher = class {
|
|
|
10552
10572
|
...described,
|
|
10553
10573
|
started: (this.described.get(root.pid) ?? described).started,
|
|
10554
10574
|
state: this.processes.has(member.pid) ? "S" : "Z",
|
|
10555
|
-
memory: this.processes.get(member.pid)?.memory() ?? 0
|
|
10575
|
+
memory: this.processes.get(member.pid)?.memory() ?? 0,
|
|
10576
|
+
umask: this.umaskOf(member.pid)
|
|
10556
10577
|
});
|
|
10557
10578
|
}
|
|
10558
10579
|
return listed;
|
|
@@ -10574,6 +10595,7 @@ var Launcher = class {
|
|
|
10574
10595
|
report,
|
|
10575
10596
|
ppid,
|
|
10576
10597
|
ignored: this.ignoredBy(ppid),
|
|
10598
|
+
umask: this.umaskOf(ppid),
|
|
10577
10599
|
...exec
|
|
10578
10600
|
});
|
|
10579
10601
|
deciding?.attach();
|
|
@@ -10618,7 +10640,8 @@ var Launcher = class {
|
|
|
10618
10640
|
fds,
|
|
10619
10641
|
ppid,
|
|
10620
10642
|
fork: state,
|
|
10621
|
-
ignored: this.ignoredBy(ppid, true)
|
|
10643
|
+
ignored: this.ignoredBy(ppid, true),
|
|
10644
|
+
umask: this.umaskOf(ppid)
|
|
10622
10645
|
})
|
|
10623
10646
|
);
|
|
10624
10647
|
}
|
|
@@ -10627,6 +10650,9 @@ var Launcher = class {
|
|
|
10627
10650
|
const foreground = () => this.jobs.tcgetpgrp(tty, fallback);
|
|
10628
10651
|
this.settling.deliver(sig, pgid, send, tty.reading ? void 0 : foreground);
|
|
10629
10652
|
}
|
|
10653
|
+
umaskOf(pid) {
|
|
10654
|
+
return this.processes.get(pid)?.umask?.() ?? DEFAULT_UMASK;
|
|
10655
|
+
}
|
|
10630
10656
|
ignoredBy(pid, fork = false) {
|
|
10631
10657
|
return this.processes.get(pid)?.ignoredSignals(fork) ?? 0;
|
|
10632
10658
|
}
|
|
@@ -10873,7 +10899,7 @@ var Launcher = class {
|
|
|
10873
10899
|
return { status: NOT_FOUND, stdout: concat5(out), stderr: concat5(err) };
|
|
10874
10900
|
}
|
|
10875
10901
|
const fds = new FdTable();
|
|
10876
|
-
fds.installAt(0, options.stdin ? bytesSource(options.stdin) :
|
|
10902
|
+
fds.installAt(0, options.stdin ? bytesSource(options.stdin) : deviceFile("null"));
|
|
10877
10903
|
fds.installAt(1, sinkFile(stdout));
|
|
10878
10904
|
fds.installAt(2, sinkFile(stderr));
|
|
10879
10905
|
const group = options.pgid !== void 0 ? { pgid: options.pgid } : {};
|