@ai-ecoverse/slicc-kernel 1.29.0 → 1.29.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 CHANGED
@@ -360,12 +360,12 @@ Programs mount and unmount with `mount(2)` and `umount2(2)`, so a `mount`/`umoun
360
360
 
361
361
  An exec'd program takes over the pid of the process it replaces, as on Linux: `getpid()` and `$$` in the new image, `/proc`, `ps`, `kill` and the parent's `waitpid` all agree on that pid, and the image's parent pid is the replaced process's. Its own children see that pid as their parent. A file removed or renamed over while programs hold it open keeps its bytes for them, as on Linux: the kernel keeps one copy for all holders, shared by their descriptors (writes through one are seen through the others), and frees it when the last one closes. Signals a program ignores stay ignored in the image it execs and in the children it spawns or forks, as POSIX has it, so `nohup cmd` and `trap '' TERM; exec cmd` keep `cmd` safe from them; caught signals go back to their default. The kernel holds what was handed down, so a new image that sets one of those signals back to the default with `signal(SIG_DFL)` still ignores it, until it installs a handler.
362
362
 
363
- - **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 still works, but the image then reports a pid of its own from `getpid()`.
363
+ - **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).
364
364
  - **WASIX:** `proc_exec` does this by itself.
365
365
 
366
366
  ### `/proc`
367
367
 
368
- 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. 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`.
368
+ 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`.
369
369
 
370
370
  While OPFS has no `/etc/passwd`, `/etc/group` or `/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.
371
371
 
@@ -7732,6 +7732,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
7732
7732
  "mount",
7733
7733
  "umount",
7734
7734
  "proc-exec",
7735
+ "proc-identity",
7735
7736
  "proc-setpgid",
7736
7737
  "proc-getpgid",
7737
7738
  "proc-getsid",
@@ -7831,6 +7832,7 @@ var WasmProcess = class {
7831
7832
  for (const listener of this.stateListeners) listener("continued", SIG.CONT);
7832
7833
  }
7833
7834
  async syscall(req) {
7835
+ this.options.onSyscall?.();
7834
7836
  for (; ; ) {
7835
7837
  await this.resumed;
7836
7838
  const stops = this.stops;
@@ -8176,6 +8178,8 @@ var WasmProcess = class {
8176
8178
  const waited = await this.children.wait(req.pid, req.nohang, signal, flags);
8177
8179
  return { ok: true, kind: "json", json: waited };
8178
8180
  }
8181
+ case "proc-identity":
8182
+ return { ok: true, kind: "json", json: await this.options.identity?.() ?? null };
8179
8183
  case "proc-exec": {
8180
8184
  this.execChild = req.pid;
8181
8185
  this.options.jobs?.exec(this.pid, req.pid);
@@ -8326,6 +8330,8 @@ function spawnWasmProcess(opts) {
8326
8330
  const header2 = new Int32Array(sab, 0, SAB_HEADER_I32);
8327
8331
  const process = new WasmProcess(opts.pid, opts.fds, {
8328
8332
  ...opts.ignored ? { ignored: opts.ignored } : {},
8333
+ ...opts.identity ? { identity: opts.identity } : {},
8334
+ ...opts.onSyscall ? { onSyscall: opts.onSyscall } : {},
8329
8335
  spawner: opts.spawner,
8330
8336
  forker: opts.forker,
8331
8337
  fs: opts.fs,
@@ -9798,6 +9804,7 @@ var ENV_REFERENCE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
9798
9804
  var WASM_MAGIC = [0, 97, 115, 109];
9799
9805
  var SHEBANG_MAX = 256;
9800
9806
  var NOT_FOUND = 127;
9807
+ var DECIDE_MS = 100;
9801
9808
  var INIT_PID = 1;
9802
9809
  var SHARED_DIRS = ["/tmp", "/home"];
9803
9810
  var encoder3 = new TextEncoder();
@@ -9896,6 +9903,8 @@ var Launcher = class {
9896
9903
  fstabRetries;
9897
9904
  inserted = /* @__PURE__ */ new Map();
9898
9905
  openFiles = /* @__PURE__ */ new Set();
9906
+ deciding = /* @__PURE__ */ new Map();
9907
+ identities = { asked: 0, timedOut: 0 };
9899
9908
  nodes;
9900
9909
  held = /* @__PURE__ */ new Set();
9901
9910
  processMounts;
@@ -10102,8 +10111,19 @@ var Launcher = class {
10102
10111
  }
10103
10112
  const pid = this.nextPid++;
10104
10113
  const terminal = req.fds.stdioTerminal();
10114
+ const decided = req.decided ?? Promise.resolve(true);
10115
+ const count = (byParent) => {
10116
+ this.identities.asked++;
10117
+ if (!byParent) this.identities.timedOut++;
10118
+ };
10119
+ let asked;
10105
10120
  const handle = spawnWasmProcess({
10106
10121
  pid,
10122
+ identity: async () => {
10123
+ await (asked ??= decided.then(count));
10124
+ return this.identityOf(pid);
10125
+ },
10126
+ onSyscall: () => this.decide(pid),
10107
10127
  ...req.ignored ? { ignored: req.ignored } : {},
10108
10128
  program: req.program,
10109
10129
  argv0: req.argv0,
@@ -10176,7 +10196,8 @@ var Launcher = class {
10176
10196
  };
10177
10197
  const shown = (pid) => {
10178
10198
  const member = pid === void 0 ? void 0 : members.get(pid);
10179
- return member ? rootOf(member).pid : INIT_PID;
10199
+ const root = member && rootOf(member).pid;
10200
+ return root !== void 0 && this.running(root) ? root : INIT_PID;
10180
10201
  };
10181
10202
  const listed = [];
10182
10203
  for (const member of members.values()) {
@@ -10205,7 +10226,9 @@ var Launcher = class {
10205
10226
  throw new SpawnError(await this.unrunnable(req.file, req.cwd));
10206
10227
  }
10207
10228
  const exec = req.exec ? { exec: true } : {};
10229
+ const deciding = req.exec ? void 0 : this.undecided(ppid);
10208
10230
  const handle = await this.launch(planned, {
10231
+ ...deciding ? { decided: deciding.promise } : {},
10209
10232
  env: req.env,
10210
10233
  cwd: req.cwd,
10211
10234
  fds,
@@ -10214,9 +10237,39 @@ var Launcher = class {
10214
10237
  ignored: this.ignoredBy(ppid),
10215
10238
  ...exec
10216
10239
  });
10240
+ deciding?.attach();
10217
10241
  return childHandle(handle);
10218
10242
  };
10219
10243
  }
10244
+ undecided(ppid) {
10245
+ const decided = Promise.withResolvers();
10246
+ return {
10247
+ promise: decided.promise,
10248
+ attach: () => {
10249
+ const timer = setTimeout(() => decided.resolve(false), DECIDE_MS);
10250
+ void decided.promise.then(() => clearTimeout(timer));
10251
+ const waiting = this.deciding.get(ppid) ?? /* @__PURE__ */ new Set();
10252
+ waiting.add(decided.resolve);
10253
+ this.deciding.set(ppid, waiting);
10254
+ }
10255
+ };
10256
+ }
10257
+ identityOf(pid) {
10258
+ const parent = this.jobs.shownParent(pid);
10259
+ const alive = parent !== void 0 && this.running(parent);
10260
+ return { pid: this.jobs.shown(pid), ppid: alive ? parent : INIT_PID };
10261
+ }
10262
+ running(shown) {
10263
+ return this.jobs.list().some((member) => !this.zombies.has(member.pid) && this.jobs.shown(member.pid) === shown);
10264
+ }
10265
+ decide(ppid) {
10266
+ const waiting = this.deciding.get(ppid);
10267
+ if (!waiting) return;
10268
+ this.deciding.delete(ppid);
10269
+ setTimeout(() => {
10270
+ for (const resolve of waiting) resolve(true);
10271
+ }, 0);
10272
+ }
10220
10273
  forker(ppid, parent) {
10221
10274
  const { exec: _exec, ...image } = parent;
10222
10275
  return async (state, fds) => childHandle(