@ai-ecoverse/slicc-kernel 1.17.0 → 1.17.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
@@ -244,6 +244,13 @@ Programs mount and unmount with `mount(2)` and `umount2(2)`, so a `mount`/`umoun
244
244
  - **Errors** are Linux's: `ENODEV` for an unknown type (or `hostfs` without a hook), `ENOENT` and `ENOTDIR` for the target, `EBUSY` for a target that is mounted already, and `EINVAL` for unmounting what is not a mount point.
245
245
  - **`/proc/mounts`** escapes spaces and backslashes as Linux does, and adds `nomedium` or `failed` to the options of a mount in that state.
246
246
 
247
+ ### Exec
248
+
249
+ 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.
250
+
251
+ - **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()`.
252
+ - **WASIX:** `proc_exec` does this by itself.
253
+
247
254
  ### `/proc`
248
255
 
249
256
  Every process of the kernel has `/proc/<pid>/` with `cmdline`, `comm`, `stat`, `statm` and `status`, in the formats procps reads, and `/proc/self/` has the same files for the process that reads them. 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`. 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`.
@@ -6299,8 +6299,9 @@ var WasmProcess = class {
6299
6299
  case "proc-fork":
6300
6300
  return { ok: true, kind: "json", json: await this.children.fork(req.state) };
6301
6301
  case "proc-spawn": {
6302
- const { file, argv, env, cwd, stdio, inherit } = req;
6303
- const pid = await this.children.spawn({ file, argv, env, cwd }, stdio, inherit);
6302
+ const { file, argv, env, cwd, stdio, inherit, exec } = req;
6303
+ const spawned = { file, argv, env, cwd, ...exec ? { exec } : {} };
6304
+ const pid = await this.children.spawn(spawned, stdio, inherit);
6304
6305
  return { ok: true, kind: "json", json: pid };
6305
6306
  }
6306
6307
  case "proc-wait": {
@@ -6567,7 +6568,7 @@ function spawnWasmProcess(opts) {
6567
6568
  });
6568
6569
  const tinit = {
6569
6570
  type: WASM_THREAD_INIT,
6570
- pid: opts.pid,
6571
+ pid: opts.shownPid ?? opts.pid,
6571
6572
  program: opts.program,
6572
6573
  argv0: opts.argv0,
6573
6574
  args: opts.args,
@@ -6581,7 +6582,7 @@ function spawnWasmProcess(opts) {
6581
6582
  };
6582
6583
  const init = {
6583
6584
  type: WASM_PROCESS_INIT,
6584
- pid: opts.pid,
6585
+ pid: opts.shownPid ?? opts.pid,
6585
6586
  program: opts.program,
6586
6587
  argv0: opts.argv0,
6587
6588
  args: opts.args,
@@ -6631,12 +6632,22 @@ var JobTable = class {
6631
6632
  remove(pid) {
6632
6633
  this.members.delete(pid);
6633
6634
  }
6634
- exec(pid, child) {
6635
+ exec(pid, child, adopt = false) {
6635
6636
  const member = this.members.get(child);
6637
+ if (member && adopt) member.shownAs = this.shown(pid);
6636
6638
  if (member) member.execParent = pid;
6637
6639
  const execer = this.members.get(pid);
6638
6640
  if (execer) execer.execed = true;
6639
6641
  }
6642
+ shown(pid) {
6643
+ let member = this.members.get(pid);
6644
+ for (let up = member; up; up = this.members.get(up.execParent ?? -1)) member = up;
6645
+ return member?.pid ?? pid;
6646
+ }
6647
+ shownParent(pid) {
6648
+ const parent = this.members.get(this.shown(pid))?.ppid;
6649
+ return parent === void 0 ? void 0 : this.shown(parent);
6650
+ }
6640
6651
  pgidOf(pid) {
6641
6652
  return this.members.get(pid)?.pgid;
6642
6653
  }
@@ -6645,34 +6656,46 @@ var JobTable = class {
6645
6656
  if (!member) throw new KernelError("ESRCH");
6646
6657
  return member;
6647
6658
  }
6659
+ idOf(pid) {
6660
+ return this.members.get(pid)?.shownAs ?? pid;
6661
+ }
6662
+ byId(id) {
6663
+ for (const member of this.members.values()) if (member.shownAs === id) return member;
6664
+ return this.member(id);
6665
+ }
6648
6666
  setpgid(caller, pid, pgid) {
6649
- const target = this.member(pid || caller);
6650
- const group = pgid || target.pid;
6667
+ const me = this.idOf(caller);
6668
+ const target = this.byId(pid || me);
6669
+ const id = this.idOf(target.pid);
6670
+ const group = pgid || id;
6651
6671
  if (group < 0) throw new KernelError("EINVAL");
6652
6672
  const self = this.member(caller);
6653
- if (target.pid !== caller) {
6654
- if (target.ppid !== caller) throw new KernelError("ESRCH");
6673
+ if (id !== me) {
6674
+ if (target.ppid === void 0 || this.idOf(target.ppid) !== me) {
6675
+ throw new KernelError("ESRCH");
6676
+ }
6655
6677
  if (target.execed) throw new KernelError("EACCES");
6656
6678
  }
6657
6679
  if (target.sid !== self.sid) throw new KernelError("EPERM");
6658
- if (target.pid === target.sid) throw new KernelError("EPERM");
6680
+ if (id === target.sid) throw new KernelError("EPERM");
6659
6681
  const exists = [...this.members.values()].some((m) => m.pgid === group && m.sid === target.sid);
6660
- if (group !== target.pid && !exists) throw new KernelError("EPERM");
6682
+ if (group !== id && !exists) throw new KernelError("EPERM");
6661
6683
  target.pgid = group;
6662
6684
  }
6663
6685
  getpgid(caller, pid) {
6664
- return this.member(pid || caller).pgid;
6686
+ return this.byId(pid || this.idOf(caller)).pgid;
6665
6687
  }
6666
6688
  getsid(caller, pid) {
6667
- return this.member(pid || caller).sid;
6689
+ return this.byId(pid || this.idOf(caller)).sid;
6668
6690
  }
6669
6691
  setsid(pid) {
6670
6692
  const member = this.member(pid);
6671
- if (member.pgid === pid) throw new KernelError("EPERM");
6672
- member.sid = pid;
6673
- member.pgid = pid;
6674
- this.terminals.delete(pid);
6675
- return pid;
6693
+ const id = this.idOf(pid);
6694
+ if (member.pgid === id) throw new KernelError("EPERM");
6695
+ member.sid = id;
6696
+ member.pgid = id;
6697
+ this.terminals.delete(id);
6698
+ return id;
6676
6699
  }
6677
6700
  terminalNamed(name2) {
6678
6701
  const known = /* @__PURE__ */ new Set([...this.terminals.values(), ...this.foreground.keys()]);
@@ -6684,9 +6707,10 @@ var JobTable = class {
6684
6707
  }
6685
6708
  acquireTerminal(pid, tty) {
6686
6709
  const member = this.members.get(pid);
6687
- if (!member || member.sid !== pid || this.terminals.has(pid)) return false;
6710
+ const id = this.idOf(pid);
6711
+ if (!member || member.sid !== id || this.terminals.has(id)) return false;
6688
6712
  if ([...this.terminals.values()].includes(tty)) return false;
6689
- this.terminals.set(pid, tty);
6713
+ this.terminals.set(id, tty);
6690
6714
  this.foreground.set(tty, member.pgid);
6691
6715
  return true;
6692
6716
  }
@@ -8022,10 +8046,11 @@ var Launcher = class {
8022
8046
  locks: this.locks,
8023
8047
  onReap: (child) => this.reaped(child),
8024
8048
  ...req.fork ? { fork: req.fork } : {},
8025
- ...req.ppid !== void 0 ? { ppid: req.ppid } : {}
8049
+ ...this.shownIds(req)
8026
8050
  });
8027
8051
  this.processes.set(pid, handle);
8028
8052
  this.jobs.add(pid, req.ppid, (sig) => handle.signal(sig), terminal);
8053
+ if (req.exec && req.ppid !== void 0) this.jobs.exec(req.ppid, pid, true);
8029
8054
  this.described.set(pid, {
8030
8055
  argv: [req.argv0, ...req.args],
8031
8056
  tty: terminal?.name ?? null,
@@ -8038,6 +8063,12 @@ var Launcher = class {
8038
8063
  });
8039
8064
  return handle;
8040
8065
  }
8066
+ shownIds(req) {
8067
+ if (req.ppid === void 0) return {};
8068
+ if (!req.exec) return { ppid: this.jobs.shown(req.ppid) };
8069
+ const ppid = this.jobs.shownParent(req.ppid);
8070
+ return { shownPid: this.jobs.shown(req.ppid), ...ppid !== void 0 ? { ppid } : {} };
8071
+ }
8041
8072
  forget(pid) {
8042
8073
  this.jobs.remove(pid);
8043
8074
  this.described.delete(pid);
@@ -8085,12 +8116,21 @@ var Launcher = class {
8085
8116
  await fds.closeAll();
8086
8117
  throw new SpawnError(await this.unrunnable(req.file, req.cwd));
8087
8118
  }
8088
- const handle = await this.launch(planned, { env: req.env, cwd: req.cwd, fds, report, ppid });
8119
+ const exec = req.exec ? { exec: true } : {};
8120
+ const handle = await this.launch(planned, {
8121
+ env: req.env,
8122
+ cwd: req.cwd,
8123
+ fds,
8124
+ report,
8125
+ ppid,
8126
+ ...exec
8127
+ });
8089
8128
  return childHandle(handle);
8090
8129
  };
8091
8130
  }
8092
8131
  forker(ppid, parent) {
8093
- return async (state, fds) => childHandle(this.start({ ...parent, cwd: state.cwd ?? parent.cwd, fds, ppid, fork: state }));
8132
+ const { exec: _exec, ...image } = parent;
8133
+ return async (state, fds) => childHandle(this.start({ ...image, cwd: state.cwd ?? parent.cwd, fds, ppid, fork: state }));
8094
8134
  }
8095
8135
  kill(pid, sig) {
8096
8136
  if (pid < 0) return this.jobs.killGroup(-pid, sig);