@ai-ecoverse/slicc-kernel 1.99.0 → 1.99.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 +3 -1
- package/dist/kernel-worker.js +36 -13
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-process-worker.js +27 -5
- package/dist/node-process-worker.js.map +2 -2
- package/dist/node.js +36 -13
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +27 -5
- package/dist/process-worker.js.map +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -346,7 +346,7 @@ The module's default export (or its `main` export) is called with a context and
|
|
|
346
346
|
- `fdStatus(fd)` (`{ type, tty, seekable }`, `type` being `file`, `pipe`, `tty`, `socket`, `device` or `directory`) and `isatty(fd)`, to tell `< file` from a pipe;
|
|
347
347
|
- `open(path, { read, write, append, create, exclusive, truncate })`, which returns a file with `read(position, length)`, `write(position, bytes)`, `size()`, `truncate(size)`, `sync()` and `close()`. Reads and writes go through the kernel's paged files at those positions, so a large file is never read whole;
|
|
348
348
|
- `fs`: `stat`, `lstat`, `exists`, `readdir`, `mkdir` (with parents, or, with `{ recursive: false }`, as Node's `fs.mkdir` without `recursive`: `EEXIST` for an existing entry and `ENOENT` for a missing parent, atomically among the processes of one kernel, so it can serve as a lock there, but not between two kernels on one origin, such as two tabs ([#519](https://github.com/ai-ecoverse/slicc-kernel/issues/519)); `sync.fs.mkdir` takes the same option), `rm` (recursive), `unlink`, `rename`, `symlink`, `readlink`, `statfs`, `chmod(path, mode)`, and `readFile`/`writeFile` for small files. `chmod` takes the permission bits (`0` to `0o7777`, `EINVAL` otherwise) and follows a symlink; as `chmod(2)`, only the file's owner or root may (`EPERM`), so a program can make a script it wrote runnable by path. `sync.fs.chmod` is the same, synchronously. `statfs(path)` answers `{ type, bsize, blocks, bfree, bavail, files, ffree, quota, usage }`: the fields of Node's `StatFs`, plus `quota` and `usage` in bytes (what jsh 0.17 reads). The values come from the same source as an Emscripten program's `statfs`/`df`: the driver of the mount the path is on, or, on the root file system, the origin's storage estimate (`navigator.storage.estimate()`). `bsize` is 4096, `bfree` and `bavail` are what's left of the quota, and `type`, `files` and `ffree` are 0, since the kernel doesn't know them. Where there's no estimate (the Node entry's root), every count is 0. A missing path is `ENOENT`. `sync.fs` has it too. `rm` removes a whole tree in one kernel call (on the OPFS root, one recursive removal and one metadata delete: 50,000 files take about 3 s in Chrome, where `rm -r` takes about 25 s) with the result of a file-by-file removal: symlinks inside the tree are removed, never followed; a tree holding a protected path is refused with `EROFS` and nothing is removed; a mount inside the tree is emptied and its mount point answers `EBUSY`, while the rest is removed; under `users`, what the user may remove is removed and the call then fails with the first `EACCES`/`EPERM`; files a process holds open stay readable to it;
|
|
349
|
-
- `/dev`, in `open`, `fs` and their `sync` forms, as a WASI or Emscripten program sees it, with the same modes and owners as under `users`: `null`, `zero`, `full`, `random`, `urandom` and `tty` (character devices, `0666`, root's), `stdin`, `stdout` and `stderr` (links to `/proc/self/fd/0`, `1`, `2`), `fd` (a link to `/proc/self/fd`, so `/dev/fd/N` is descriptor N) and the kernel's terminals `/dev/ttyN` and `/dev/pts/N` (with users on, owned by the session's user or the master's opener, `0620`). `/dev/shm` and `/dev/ptmx`
|
|
349
|
+
- `/dev`, in `open`, `fs` and their `sync` forms, as a WASI or Emscripten program sees it, with the same modes and owners as under `users`: `null`, `zero`, `full`, `random`, `urandom` and `tty` (character devices, `0666`, root's), `stdin`, `stdout` and `stderr` (links to `/proc/self/fd/0`, `1`, `2`), `fd` (a link to `/proc/self/fd`, so `/dev/fd/N` is descriptor N) and the kernel's terminals `/dev/ttyN` and `/dev/pts/N` (with users on, owned by the session's user or the master's opener, `0620`). `/dev/shm` is the kernel's shared tmpfs (see [Mounts](#mounts)), read and written like any other mount; `/dev/ptmx` isn't there. `stat` through `/dev/stdin`, `/dev/stdout` or `/dev/fd/N` describes the program's own descriptor (the file it is redirected to, a pipe, a terminal), `readlink` of `/dev/fd/N` names it (`pipe:[N]` and `socket:[N]` for a pipe or a socket), and a closed descriptor is `ENOENT`. Opening a device gives a kernel descriptor on it; opening `/dev/std*` or `/dev/fd/N` opens the file again with the flags asked for when the descriptor is on a file (as Linux reopens `/proc/self/fd/N`, so `append` appends and `truncate` truncates), and otherwise duplicates the descriptor; `/dev/tty` is the program's controlling terminal (`ENXIO` without one); and `/dev/ttyN` and `/dev/pts/N` are opened by name through the kernel, so a terminal that isn't the user's is `EACCES` under `enforce`. A file on anything but a regular file reads and writes as a stream (the position is ignored) and only in the directions it was opened for (`EBADF` otherwise), its `size()` is 0 and `truncate` is `EINVAL`. `readFile` reads a device or a descriptor to its end, up to 64 MiB, and fails with `EFBIG` past that (where Node's `readFile` throws `ERR_FS_FILE_TOO_LARGE`), so `readFile('/dev/zero')` fails instead of hanging, while a bounded `read` of it works. Other paths pay only a prefix check: they make the same kernel calls as before. Creating, removing or renaming names in `/dev` goes to the root file system as before;
|
|
350
350
|
- `/proc`, in `fs`, `open` and their `sync` forms, the same as a WASI program's (see [`/proc`](#proc)): `stat`, `lstat`, `exists`, `readdir`, `readlink` and `readFile` answer it, and `/proc/self` is the program's own pid. It's read-only, so every change in it is `EROFS`: `writeFile`, `mkdir`, `rm`, `unlink`, `chmod`, a `rename`, `link` or `symlink` into or out of it, and an `open` for writing, creating or truncating. A file opened in it is a snapshot read at the open, with no descriptor (`fd` is `-1`): its `read` and `size` answer from the snapshot until it's closed, and `write` and `truncate` are `EBADF`. Other paths pay only a prefix check;
|
|
351
351
|
- `signals`: `on(signal, handler)` catches a signal (by number or as `'SIGINT'`), `ignore(signal)` ignores it and `reset(signal)` restores its default action. A call blocked in a read or write is restarted after a handler runs; signals without a handler keep their default actions, so an uncaught `SIGINT` ends the program with 130 and `SIGKILL` always does;
|
|
352
352
|
- `sync`: `read`, `write`, `close`, `open` (whose files' methods are synchronous too) and `fs`, made synchronously, for code that cannot `await`, such as a compiled program's file system or a `readFileSync`. A synchronous call holds the worker until the kernel answers, so nothing else of the program runs meanwhile, timers and callbacks included; a signal that arrives during a blocked read or write runs its handler, and the call is then restarted. Descriptors are shared with the rest of the context, so a file opened with `sync.open` can be closed with `close`, and the other way around; `sync.run` and `sync.exit` are described next;
|
|
@@ -554,6 +554,8 @@ Directories are renamed with `FileSystemHandle.move()` where available, else by
|
|
|
554
554
|
|
|
555
555
|
`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).
|
|
556
556
|
|
|
557
|
+
`/dev/shm` is a tmpfs the kernel mounts at boot (source `shm`), mode `1777` and owned by root, so with `users` the sticky bit keeps one user from removing or renaming another's file there (`EPERM`). It's in `/proc/mounts`, but not in `kernel.mounts()`, which lists the mounts a caller made. Every process sees the same files, Emscripten (whose glue otherwise keeps a private `/dev/shm` in memory), WASI and JS programs alike, and they last until the kernel terminates, which is what musl's `shm_open` and `sem_open` need.
|
|
558
|
+
|
|
557
559
|
- **Built in:** `tmpfs`, which lives in memory until unmounted, in 1 MiB chunks; `fsa`, a folder the user picks (see [Removable media](#removable-media-fsa)); and `hostfs`, a folder the local proxy exports (see [Host folders](#host-folders-hostfs)).
|
|
558
560
|
- **Package drivers:** a package declares a type in `"slicc": { "filesystems": { "<type>": { "module": "<file>" } } }`, and the kernel starts that module in a worker of its own for each mount. The module is a single self-contained file: it can't import bare specifiers.
|
|
559
561
|
- Its default export receives `{ fetch }`, the kernel's network transport. It returns `{ handlers, capabilities }`.
|
package/dist/kernel-worker.js
CHANGED
|
@@ -5063,7 +5063,7 @@ function serveClient(port, host) {
|
|
|
5063
5063
|
case "umount":
|
|
5064
5064
|
return { result: await l.umountAs(clientCred(self), req.target).then(() => true) };
|
|
5065
5065
|
case "mounts":
|
|
5066
|
-
return { result: l.mounts.
|
|
5066
|
+
return { result: l.mounts.listed() };
|
|
5067
5067
|
case "dial":
|
|
5068
5068
|
return dialFor(l, req, watches);
|
|
5069
5069
|
case "serve-cdp":
|
|
@@ -8449,6 +8449,18 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
|
|
|
8449
8449
|
};
|
|
8450
8450
|
}
|
|
8451
8451
|
|
|
8452
|
+
// src/fs/dev-removal.ts
|
|
8453
|
+
var SHM = "/dev/shm";
|
|
8454
|
+
function inDevTree(path) {
|
|
8455
|
+
if (path.startsWith(`${SHM}/`)) return false;
|
|
8456
|
+
return path === "/dev" || path.startsWith("/dev/");
|
|
8457
|
+
}
|
|
8458
|
+
function devRemoval(path, cred) {
|
|
8459
|
+
if (!inDevTree(path)) return void 0;
|
|
8460
|
+
if (cred && cred.euid !== 0) return "EACCES";
|
|
8461
|
+
return path === "/dev" ? "EBUSY" : "EPERM";
|
|
8462
|
+
}
|
|
8463
|
+
|
|
8452
8464
|
// src/fs/fresh-modules.ts
|
|
8453
8465
|
var MAGIC = [0, 97, 115, 109];
|
|
8454
8466
|
var MAX_FRESH = 65536;
|
|
@@ -9626,6 +9638,7 @@ var MountTable = class {
|
|
|
9626
9638
|
devices = 0;
|
|
9627
9639
|
pending = /* @__PURE__ */ new Set();
|
|
9628
9640
|
notes = /* @__PURE__ */ new Map();
|
|
9641
|
+
system = /* @__PURE__ */ new Set();
|
|
9629
9642
|
deps;
|
|
9630
9643
|
constructor(deps) {
|
|
9631
9644
|
this.deps = deps;
|
|
@@ -9647,6 +9660,13 @@ var MountTable = class {
|
|
|
9647
9660
|
const noted = [...this.notes.values()].filter((note) => !this.mounts.has(note.target));
|
|
9648
9661
|
return [...mounted, ...noted.map((note) => ({ ...note, options: { ...note.options } }))];
|
|
9649
9662
|
}
|
|
9663
|
+
listed() {
|
|
9664
|
+
return this.list().filter((entry) => !this.system.has(entry.target));
|
|
9665
|
+
}
|
|
9666
|
+
async mountSystem(spec, fs) {
|
|
9667
|
+
await this.mount(spec, fs);
|
|
9668
|
+
this.system.add(normalizePath(spec.target));
|
|
9669
|
+
}
|
|
9650
9670
|
note(spec, state, error) {
|
|
9651
9671
|
const target = normalizePath(spec.target);
|
|
9652
9672
|
this.notes.set(target, {
|
|
@@ -9753,6 +9773,7 @@ var MountTable = class {
|
|
|
9753
9773
|
if (!mount && this.notes.delete(at)) return;
|
|
9754
9774
|
if (!mount) throw errnoError("EINVAL", `${at} is not mounted`);
|
|
9755
9775
|
if (!detach && this.deps.busy(at)) throw errnoError("EBUSY", `${at} has open files`);
|
|
9776
|
+
this.system.delete(at);
|
|
9756
9777
|
this.notes.delete(at);
|
|
9757
9778
|
this.mounts.delete(at);
|
|
9758
9779
|
this.deps.reshaped?.();
|
|
@@ -10661,16 +10682,6 @@ var MountFs = class _MountFs {
|
|
|
10661
10682
|
}
|
|
10662
10683
|
};
|
|
10663
10684
|
|
|
10664
|
-
// src/fs/dev-removal.ts
|
|
10665
|
-
function inDevTree(path) {
|
|
10666
|
-
return path === "/dev" || path.startsWith("/dev/");
|
|
10667
|
-
}
|
|
10668
|
-
function devRemoval(path, cred) {
|
|
10669
|
-
if (!inDevTree(path)) return void 0;
|
|
10670
|
-
if (cred && cred.euid !== 0) return "EACCES";
|
|
10671
|
-
return path === "/dev" ? "EBUSY" : "EPERM";
|
|
10672
|
-
}
|
|
10673
|
-
|
|
10674
10685
|
// src/realm/sync-fs-token-registry.ts
|
|
10675
10686
|
var registry = /* @__PURE__ */ new Map();
|
|
10676
10687
|
function mintSyncFsToken(entry) {
|
|
@@ -12085,7 +12096,7 @@ var PATH_OPS = new Set(SYNC_FS_OPS);
|
|
|
12085
12096
|
var EARLY_WHOLE_FILE = 64 * 1024;
|
|
12086
12097
|
|
|
12087
12098
|
// src/version.ts
|
|
12088
|
-
var KERNEL_VERSION = true ? "1.99.
|
|
12099
|
+
var KERNEL_VERSION = true ? "1.99.1" : "0.0.0-dev";
|
|
12089
12100
|
var OS_RELEASE = "6.0.0-slicc";
|
|
12090
12101
|
function procVersion() {
|
|
12091
12102
|
return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
|
|
@@ -20547,6 +20558,9 @@ var NOT_EXECUTABLE = 126;
|
|
|
20547
20558
|
var DECIDE_MS = 100;
|
|
20548
20559
|
var INIT_PID = 1;
|
|
20549
20560
|
var SHARED_DIRS = ["/tmp", "/home"];
|
|
20561
|
+
function unref2(port) {
|
|
20562
|
+
port.unref?.();
|
|
20563
|
+
}
|
|
20550
20564
|
var LAYOUT = [
|
|
20551
20565
|
["/", 493],
|
|
20552
20566
|
["/root", 448],
|
|
@@ -20757,6 +20771,7 @@ var Launcher = class {
|
|
|
20757
20771
|
pnpmMade;
|
|
20758
20772
|
pnpmEpoch = 0;
|
|
20759
20773
|
mounts;
|
|
20774
|
+
shmPorts = [];
|
|
20760
20775
|
createDriverWorker;
|
|
20761
20776
|
media;
|
|
20762
20777
|
onMountPending;
|
|
@@ -22021,6 +22036,7 @@ var Launcher = class {
|
|
|
22021
22036
|
},
|
|
22022
22037
|
{ owned: true }
|
|
22023
22038
|
);
|
|
22039
|
+
if (spec.target === SHM) this.shmPorts = [port1, port2];
|
|
22024
22040
|
return {
|
|
22025
22041
|
port: port1,
|
|
22026
22042
|
dispose: () => {
|
|
@@ -22123,6 +22139,12 @@ var Launcher = class {
|
|
|
22123
22139
|
this.pnpmStale();
|
|
22124
22140
|
return entry;
|
|
22125
22141
|
}
|
|
22142
|
+
async mountShm() {
|
|
22143
|
+
await this.base.mkdir(SHM, { recursive: true });
|
|
22144
|
+
await this.mounts.mountSystem({ type: "tmpfs", source: "shm", target: SHM }, this.fs);
|
|
22145
|
+
for (const port of this.shmPorts) unref2(port);
|
|
22146
|
+
await this.fs.chmod(SHM, 1023);
|
|
22147
|
+
}
|
|
22126
22148
|
async ownTmp() {
|
|
22127
22149
|
const real = await this.realLayoutPath("/tmp");
|
|
22128
22150
|
const st = real === void 0 ? void 0 : await this.base.lstat(real).catch(() => void 0);
|
|
@@ -22279,6 +22301,7 @@ var Launcher = class {
|
|
|
22279
22301
|
await this.resolvePrefixes();
|
|
22280
22302
|
for (const dir of SHARED_DIRS) await this.base.mkdir(dir, { recursive: true });
|
|
22281
22303
|
if (this.owners) await this.ownTmp();
|
|
22304
|
+
await this.mountShm();
|
|
22282
22305
|
await this.pnpmReady();
|
|
22283
22306
|
await this.users.ensure();
|
|
22284
22307
|
if (this.owners) await this.stampLayout();
|
|
@@ -22827,7 +22850,7 @@ function serveKernel(port, deps) {
|
|
|
22827
22850
|
if (req.op === "mount") return l.mount(req.spec);
|
|
22828
22851
|
if (req.op === "umount") return l.umount(req.target);
|
|
22829
22852
|
if (req.op === "insert") return l.insert(req.target, req.handle, req.source);
|
|
22830
|
-
return l.mounts.
|
|
22853
|
+
return l.mounts.listed();
|
|
22831
22854
|
}
|
|
22832
22855
|
const attestOp = attestOps(port);
|
|
22833
22856
|
async function handle(req) {
|