@ai-ecoverse/slicc-kernel 1.139.1 → 1.140.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 +2 -2
- package/dist/kernel-worker.js +90 -22
- package/dist/kernel-worker.js.map +4 -4
- package/dist/meta.js +5 -3
- package/dist/meta.js.map +2 -2
- package/dist/node-process-worker.js +54 -6
- package/dist/node-process-worker.js.map +3 -3
- package/dist/node.js +82 -16
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +54 -6
- package/dist/process-worker.js.map +3 -3
- package/dist/types/fs/etc-files.d.ts +3 -0
- package/dist/types/fs/meta.d.ts +3 -3
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -330,7 +330,7 @@ On top of preview1 a WASI program gets:
|
|
|
330
330
|
A kernel without the imports answers `ENOSYS`, and the libc reports `SI_USER` with pid and uid 0, as before. **SIGCHLD:** with `SIGCHLD` set to `SIG_IGN` (by the program, or inherited), or with `SA_NOCLDWAIT` in the flags `slicc.sigaction_set` gets for `SIGCHLD`, an exited child is reaped at once and never stays a zombie: `waitpid` for it is `ECHILD`, and `wait` blocks until every child has exited, then fails with `ECHILD`, as on Linux. `SIGCHLD` is still sent. An explicit `SIG_DFL` turns it back off. A child that a process waits on for `exec`, or that `kernel.run` and the run calls collect, is never reaped that way.
|
|
331
331
|
- **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.
|
|
332
332
|
- **Sockets**: the WASIX socket calls (`sock_open`, `sock_pair` for an `AF_UNIX` stream or datagram 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. A write to a pipe, FIFO or terminal whose reader is gone follows the program's `SIGPIPE` disposition, as the kernel knows it (#810): at the default it ends the program with status 141; ignored (also an ignore inherited across `exec` and spawn, as bash's `trap '' PIPE` passes on), the write fails with `EPIPE`; with a handler, the handler runs on the thread that wrote and then the write fails with `EPIPE` (#820); blocked, `SIGPIPE` stays pending and the write fails with `EPIPE`. An Emscripten program's write to a pipe, and its `send` without `MSG_NOSIGNAL`, follow the same rules, except that the kernel doesn't see an Emscripten program's signal mask, so a blocked `SIGPIPE` there still ends it. A WASIX program's libc reads an inherited ignore back through `slicc.sigaction_inherited` (wasix-sysroot -32 and later; see below). An Emscripten program's shim reads it from `Module.sliccKernel.sigInherited()` (#965, homescoop#973). That's a mask with bit n set for signal n (not bit n-1, as the WASIX import has it) holding only inherited `SIG_IGN`s, never `SIGKILL` or `SIGSTOP`. The shim seeds its `sigaction` table from it in a constructor that runs before any user constructor. The kernel puts the function on `Module.sliccKernel` before the glue runs constructors, ahead of the rest of `sliccKernel`. On a kernel without the function it is undefined, and the shim keeps the defaults. 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. `sock_open(SOCK_DGRAM)` on `AF_INET`/`AF_INET6` gives a [datagram socket](#datagram-sockets-udp); `sock_send_to` and `sock_recv_from` carry its addresses, and `RECV_DATA_TRUNCATED` is set in `ro_flags` when a datagram did not fit.
|
|
333
|
-
- **Diagnostics**: a trap ends the program with 134 and its message on stderr; a program that crashes its worker otherwise (a JS stack overflow, `RangeError: Maximum call stack size exceeded`, or any other uncaught error) ends with 70, and its message goes to the process's own fd 2 too, so `2>` catches it (#1237), or to the session's stderr when it has no fd 2 to write; 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.
|
|
333
|
+
- **Diagnostics**: a trap ends the program with 134 and its message on stderr; a program that crashes its worker otherwise (a JS stack overflow, `RangeError: Maximum call stack size exceeded`, or any other uncaught error) ends with 70, and its message goes to the process's own fd 2 too, so `2>` catches it (#1237), or to the session's stderr when it has no fd 2 to write; 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). A sidecar that doesn't fit the module, naming another number of functions than the module has (imported and defined), gets one warning line before the trap message, and the frames keep their raw indices instead of wrong names (#1239). `SLICC_WASI_STATS=1` makes a program print its calls, counted and timed, as it ends.
|
|
334
334
|
|
|
335
335
|
### Program imports
|
|
336
336
|
|
|
@@ -610,7 +610,7 @@ The kernel answers DNS itself, so DNS clients (BIND's `dig`, `host` and `nslooku
|
|
|
610
610
|
### Node name
|
|
611
611
|
|
|
612
612
|
The kernel has one node name, `slicc` unless `createKernel({ hostname })` (or `createNodeKernel`) names another; it must be a valid host name, and not `localhost`, a `*.localhost` name, `host.slicc.internal`, `wasmer.sh` or an IPv4 address, or `createKernel` rejects. `no_proxy` lists it (and `emscripten`), so HTTP clients reach the kernel's own servers by it directly.
|
|
613
|
-
- **Where programs see it:** every process gets it in `HOSTNAME`, which the kernel sets on every start whatever the caller's environment says.
|
|
613
|
+
- **Where programs see it:** every process gets it in `HOSTNAME`, which the kernel sets on every start whatever the caller's environment says. Every program can read it from `/etc/hostname`: the node name and a newline, a `0644` root-owned file that a real `/etc/hostname` in the root replaces ([#1297](https://github.com/ai-ecoverse/slicc-kernel/issues/1297)). Emscripten programs also have the synthetic `/etc/hosts` and `/etc/passwd`, which only they have (WASI programs resolve names through the kernel instead), and `Module.sliccKernel.hostname`.
|
|
614
614
|
- **What `uname` says:** for an Emscripten program built with homescoop's `__syscall_uname` import (wasm-coreutils 9.12.0-7 on), the kernel answers `uname(2)`: sysname `Linux`, nodename the node name, release `6.0.0-slicc` (as `/proc/sys/kernel/osrelease`), version `#1 SMP PREEMPT_DYNAMIC` (as `/proc/version`), machine `wasm32` and domainname `(none)`, so `uname -a` and musl's `gethostname()` see them. `uname -o` is coreutils' compiled-in `Emscripten`. Older Emscripten builds still report their C library's `Emscripten 4.0.23` and `emscripten`, and WASIX programs `wasmer.sh`; the kernel has nothing to answer there.
|
|
615
615
|
- **Resolving it:** `/etc/hosts` maps the node name and both of those names to `127.0.1.1` (Debian's convention), and name resolution answers them, and `localhost.localdomain`, itself, as `127.0.1.1` and `::1`. So a program that looks up its own host name, as git does for every commit, never asks the [uplink](#uplink), and `127.0.1.1` is the kernel's loopback.
|
|
616
616
|
- **The trade-off:** a raw connection to the real `wasmer.sh` resolves to loopback too. HTTP(S) to it still goes through the realm proxy and the transport, which resolve names themselves; `wasmer.sh` is deliberately not in `no_proxy`.
|
package/dist/kernel-worker.js
CHANGED
|
@@ -118,8 +118,9 @@ var MemoryMeta = class {
|
|
|
118
118
|
this.entries.set(to, moved(root(carried.find((entry) => entry.path === from)), to));
|
|
119
119
|
if (left) this.entries.set(from, left);
|
|
120
120
|
}
|
|
121
|
-
async remove(paths) {
|
|
121
|
+
async remove(paths, left) {
|
|
122
122
|
for (const path of paths) this.entries.deleteTree(path);
|
|
123
|
+
if (left) this.entries.set(left.path, left);
|
|
123
124
|
}
|
|
124
125
|
absent(path) {
|
|
125
126
|
return !this.entries.has(path);
|
|
@@ -178,13 +179,13 @@ var IndexedDbMeta = class {
|
|
|
178
179
|
return [transaction, transaction.objectStore(META_STORE)];
|
|
179
180
|
}
|
|
180
181
|
async get(paths) {
|
|
181
|
-
const
|
|
182
|
-
if (
|
|
182
|
+
const missing5 = paths.filter((path) => !this.cache.has(path));
|
|
183
|
+
if (missing5.length > 0) {
|
|
183
184
|
this.settleRemoves();
|
|
184
185
|
const generation = this.generation;
|
|
185
186
|
const [transaction, store] = this.transaction("readonly");
|
|
186
187
|
const found = /* @__PURE__ */ new Map();
|
|
187
|
-
for (const path of
|
|
188
|
+
for (const path of missing5) {
|
|
188
189
|
const request3 = store.get(path);
|
|
189
190
|
request3.onsuccess = () => void found.set(path, request3.result ?? null);
|
|
190
191
|
}
|
|
@@ -265,9 +266,9 @@ var IndexedDbMeta = class {
|
|
|
265
266
|
}
|
|
266
267
|
removing = /* @__PURE__ */ new Map();
|
|
267
268
|
removeTimer;
|
|
268
|
-
async remove(paths) {
|
|
269
|
+
async remove(paths, left) {
|
|
269
270
|
this.settleRemoves();
|
|
270
|
-
return this.removeNow(paths);
|
|
271
|
+
return this.removeNow(paths, left);
|
|
271
272
|
}
|
|
272
273
|
async removeFile(path) {
|
|
273
274
|
const known = this.cache.get(path);
|
|
@@ -281,14 +282,16 @@ var IndexedDbMeta = class {
|
|
|
281
282
|
REMOVE_WAIT_MS
|
|
282
283
|
);
|
|
283
284
|
}
|
|
284
|
-
async removeNow(paths) {
|
|
285
|
+
async removeNow(paths, left) {
|
|
285
286
|
const [transaction, store] = this.transaction("readwrite");
|
|
286
287
|
for (const path of paths) {
|
|
287
288
|
store.delete(path);
|
|
288
289
|
store.delete(descendants(path));
|
|
289
290
|
}
|
|
291
|
+
if (left) store.put(left);
|
|
290
292
|
await settle(transaction, () => void 0);
|
|
291
293
|
this.changed({ trees: paths });
|
|
294
|
+
if (left) this.cache.set(left.path, left);
|
|
292
295
|
}
|
|
293
296
|
settleRemoves() {
|
|
294
297
|
if (this.removing.size > 0) void this.flushRemoves().catch(() => void 0);
|
|
@@ -2316,10 +2319,10 @@ var OpfsFs = class {
|
|
|
2316
2319
|
return;
|
|
2317
2320
|
}
|
|
2318
2321
|
}
|
|
2319
|
-
await this.meta.remove(
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2322
|
+
await this.meta.remove(
|
|
2323
|
+
[found.path],
|
|
2324
|
+
found.handle?.kind === "directory" ? vacatedAt(found.path) : void 0
|
|
2325
|
+
);
|
|
2323
2326
|
this.touch(Date.now(), parentOf3(found.path));
|
|
2324
2327
|
}
|
|
2325
2328
|
links() {
|
|
@@ -7125,7 +7128,7 @@ function satisfies(version, range2) {
|
|
|
7125
7128
|
}
|
|
7126
7129
|
|
|
7127
7130
|
// src/version.ts
|
|
7128
|
-
var KERNEL_VERSION = true ? "1.
|
|
7131
|
+
var KERNEL_VERSION = true ? "1.140.1" : "0.0.0-dev";
|
|
7129
7132
|
var OS_RELEASE = "6.0.0-slicc";
|
|
7130
7133
|
function procVersion() {
|
|
7131
7134
|
return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
|
|
@@ -9805,6 +9808,70 @@ function devCreation(path, cred) {
|
|
|
9805
9808
|
return cred && cred.euid !== 0 ? "EACCES" : "EPERM";
|
|
9806
9809
|
}
|
|
9807
9810
|
|
|
9811
|
+
// src/fs/etc-files.ts
|
|
9812
|
+
var HOSTNAME_PATH = "/etc/hostname";
|
|
9813
|
+
var S_IFREG3 = 32768;
|
|
9814
|
+
function missing4(err) {
|
|
9815
|
+
return err?.code === "ENOENT";
|
|
9816
|
+
}
|
|
9817
|
+
function statOf(path, bytes) {
|
|
9818
|
+
const epoch = /* @__PURE__ */ new Date(0);
|
|
9819
|
+
return {
|
|
9820
|
+
isFile: true,
|
|
9821
|
+
isDirectory: false,
|
|
9822
|
+
isSymbolicLink: false,
|
|
9823
|
+
size: bytes.length,
|
|
9824
|
+
mode: S_IFREG3 | 420,
|
|
9825
|
+
mtime: epoch,
|
|
9826
|
+
atime: epoch,
|
|
9827
|
+
ctime: epoch,
|
|
9828
|
+
ino: inodeOf(path),
|
|
9829
|
+
nlink: 1,
|
|
9830
|
+
uid: 0,
|
|
9831
|
+
gid: 0
|
|
9832
|
+
};
|
|
9833
|
+
}
|
|
9834
|
+
function withEtcFiles(fs, files) {
|
|
9835
|
+
const bytesOf2 = (path) => {
|
|
9836
|
+
const text = files.get(path)?.();
|
|
9837
|
+
return text === void 0 ? void 0 : new TextEncoder().encode(text);
|
|
9838
|
+
};
|
|
9839
|
+
const absent2 = (path) => fs.lstat(path).then(() => false, missing4);
|
|
9840
|
+
const fallback = (path, make) => async (err) => {
|
|
9841
|
+
const bytes = missing4(err) && files.has(path) && await absent2(path) ? bytesOf2(path) : void 0;
|
|
9842
|
+
if (bytes === void 0) throw err;
|
|
9843
|
+
return make(bytes);
|
|
9844
|
+
};
|
|
9845
|
+
const ranged2 = rangedOps(fs);
|
|
9846
|
+
const own = {
|
|
9847
|
+
stat: (path) => fs.stat(path).catch(fallback(path, (b) => statOf(path, b))),
|
|
9848
|
+
lstat: (path) => fs.lstat(path).catch(fallback(path, (b) => statOf(path, b))),
|
|
9849
|
+
exists: async (path) => await fs.exists(path) || files.has(path) && await absent2(path),
|
|
9850
|
+
...fs.createExclusive ? {
|
|
9851
|
+
createExclusive: async (path, owner) => {
|
|
9852
|
+
if (files.has(path) && await absent2(path)) throw fsError("EEXIST", path);
|
|
9853
|
+
return fs.createExclusive.call(
|
|
9854
|
+
fs,
|
|
9855
|
+
path,
|
|
9856
|
+
owner
|
|
9857
|
+
);
|
|
9858
|
+
}
|
|
9859
|
+
} : {},
|
|
9860
|
+
readFile: (path) => fs.readFile(path).catch(fallback(path, (b) => new TextDecoder().decode(b))),
|
|
9861
|
+
readFileBuffer: (path) => fs.readFileBuffer(path).catch(fallback(path, (b) => b)),
|
|
9862
|
+
...ranged2 ? {
|
|
9863
|
+
pread: (path, offset, length2, version) => ranged2.pread(path, offset, length2, version).catch(fallback(path, (b) => b.slice(offset, offset + length2)))
|
|
9864
|
+
} : {}
|
|
9865
|
+
};
|
|
9866
|
+
return new Proxy(fs, {
|
|
9867
|
+
get(target, key, receiver) {
|
|
9868
|
+
if (Object.hasOwn(own, key)) return own[key];
|
|
9869
|
+
const value2 = Reflect.get(target, key, receiver);
|
|
9870
|
+
return typeof value2 === "function" ? value2.bind(target) : value2;
|
|
9871
|
+
}
|
|
9872
|
+
});
|
|
9873
|
+
}
|
|
9874
|
+
|
|
9808
9875
|
// src/fs/fresh-modules.ts
|
|
9809
9876
|
var MAGIC = [0, 97, 115, 109];
|
|
9810
9877
|
var MAX_FRESH = 65536;
|
|
@@ -11260,7 +11327,7 @@ function inodeless(mount, attr2) {
|
|
|
11260
11327
|
if (mount.caps.links) mount.caps = { ...mount.caps, links: false };
|
|
11261
11328
|
return true;
|
|
11262
11329
|
}
|
|
11263
|
-
function
|
|
11330
|
+
function statOf2(attr2, path, mount) {
|
|
11264
11331
|
const mtime = new Date(attr2.mtime);
|
|
11265
11332
|
const fallback = attr2.kind === "directory" ? 16877 : attr2.kind === "symlink" ? 41471 : attr2.kind === "fifo" ? 4516 : 33188;
|
|
11266
11333
|
return {
|
|
@@ -11732,7 +11799,7 @@ var MountFs = class _MountFs {
|
|
|
11732
11799
|
const { mount, rel: rel2 } = found;
|
|
11733
11800
|
if (!owner || !mount.owners || !mount.caps.owners) return void 0;
|
|
11734
11801
|
const dir = parentOf7(real);
|
|
11735
|
-
const above =
|
|
11802
|
+
const above = statOf2(await this.table.getattr(mount, dir, parentOf7(rel2)), dir, mount);
|
|
11736
11803
|
const setgid = (above.mode & S_ISGID4) !== 0;
|
|
11737
11804
|
let mode = owner.mode;
|
|
11738
11805
|
if (directory && setgid) mode = (mode ?? 493) | S_ISGID4;
|
|
@@ -12064,7 +12131,7 @@ var MountFs = class _MountFs {
|
|
|
12064
12131
|
}
|
|
12065
12132
|
statWriting(attr2, real, mount) {
|
|
12066
12133
|
const ends = (this.table.writing(real)?.extents ?? []).map(([, end]) => end);
|
|
12067
|
-
return
|
|
12134
|
+
return statOf2({ ...attr2, size: Math.max(attr2.size, ...ends) }, real, mount);
|
|
12068
12135
|
}
|
|
12069
12136
|
async stat(path, redirects = 0) {
|
|
12070
12137
|
const real = await this.follow(path);
|
|
@@ -15475,11 +15542,11 @@ function ipv6(host) {
|
|
|
15475
15542
|
const back = halves.length === 2 ? groups(halves[1]) : [];
|
|
15476
15543
|
if (![...head, ...back].every((g) => /^[0-9A-Fa-f]{1,4}$/.test(g))) return void 0;
|
|
15477
15544
|
const words2 = [...head, ...back].map((g) => Number.parseInt(g, 16));
|
|
15478
|
-
const
|
|
15479
|
-
if (halves.length === 2 ?
|
|
15545
|
+
const missing5 = 8 - words2.length - tail2.length;
|
|
15546
|
+
if (halves.length === 2 ? missing5 < 1 : missing5 !== 0) return void 0;
|
|
15480
15547
|
const all = [
|
|
15481
15548
|
...head.map((g) => Number.parseInt(g, 16)),
|
|
15482
|
-
...new Array(halves.length === 2 ?
|
|
15549
|
+
...new Array(halves.length === 2 ? missing5 : 0).fill(0),
|
|
15483
15550
|
...back.map((g) => Number.parseInt(g, 16)),
|
|
15484
15551
|
...tail2
|
|
15485
15552
|
];
|
|
@@ -27888,13 +27955,13 @@ function interpreter(head) {
|
|
|
27888
27955
|
function expandDefaults(defaults2, env) {
|
|
27889
27956
|
const out = {};
|
|
27890
27957
|
for (const [key, value2] of Object.entries(defaults2 ?? {})) {
|
|
27891
|
-
let
|
|
27958
|
+
let missing5 = false;
|
|
27892
27959
|
const expanded = value2.replace(ENV_REFERENCE, (_, name2) => {
|
|
27893
27960
|
const set3 = Object.hasOwn(env, name2) ? env[name2] : void 0;
|
|
27894
|
-
if (set3 === void 0)
|
|
27961
|
+
if (set3 === void 0) missing5 = true;
|
|
27895
27962
|
return set3 ?? "";
|
|
27896
27963
|
});
|
|
27897
|
-
if (!
|
|
27964
|
+
if (!missing5) out[key] = expanded;
|
|
27898
27965
|
}
|
|
27899
27966
|
return out;
|
|
27900
27967
|
}
|
|
@@ -30065,7 +30132,8 @@ var Launcher = class {
|
|
|
30065
30132
|
}
|
|
30066
30133
|
commandFs(root) {
|
|
30067
30134
|
return withCommandDirs(
|
|
30068
|
-
root,
|
|
30135
|
+
withEtcFiles(root, /* @__PURE__ */ new Map([[HOSTNAME_PATH, () => `${this.hostname}
|
|
30136
|
+
`]])),
|
|
30069
30137
|
async () => this.commandNames(await this.commands()),
|
|
30070
30138
|
(dir) => this.userCommandDir(dir),
|
|
30071
30139
|
this.owners,
|