This commit is contained in:
2026-08-18 18:02:58 -04:00
parent c072d24bb1
commit 0e9a650fa2
6 changed files with 1607 additions and 1301 deletions
+12 -202
View File
@@ -169,6 +169,8 @@ import { createKernelLoaderAuditAppend } from './lib/bare-os-loader-audit.js'
import { createBareOsPosixFdSimMethods } from './lib/bare-os-posix-fd-sim.js'
import { createBareOsPathconfMethods } from './lib/bare-os-pathconf.js'
import { createBareOsLogicalFdMethods } from './lib/bare-os-logical-fd.js'
import { createBareOsLifecycleHookMethods } from './lib/bare-os-lifecycle-hooks.js'
import { createBareOsProcessControlMethods } from './lib/bare-os-process-control.js'
import { createBareOsQvacBridge } from './lib/bare-os-qvac-host.mjs'
import { bareOsQvacEnsureModelsHdms } from './lib/bare-os-qvac-models-store.mjs'
import { buildPearIpcRegistryJson } from './lib/bare-os-pear-ipc-registry.js'
@@ -3525,62 +3527,11 @@ async function executeKernel(disk, store, swarm, initSource) {
pendingId: String(shellEnv.BARE_OS_SYSTEM_REVISION_PENDING || '').trim(),
slot: String(shellEnv.BARE_OS_SYSTEM_SLOT || 'a').trim()
}),
/** @param {() => void | Promise<void>} fn */
bareOsRegisterSuspendHook(fn) {
if (typeof fn === 'function') bareOsSuspendHooks.push(fn)
return () => {
const i = bareOsSuspendHooks.indexOf(fn)
if (i >= 0) bareOsSuspendHooks.splice(i, 1)
}
},
/** @param {() => void | Promise<void>} fn */
bareOsRegisterResumeHook(fn) {
if (typeof fn === 'function') bareOsResumeHooks.push(fn)
return () => {
const i = bareOsResumeHooks.indexOf(fn)
if (i >= 0) bareOsResumeHooks.splice(i, 1)
}
},
async bareOsInvokeSuspendHooks() {
for (const fn of [...bareOsSuspendHooks]) {
try {
await fn()
} catch {
/* ignore */
}
}
},
async bareOsInvokeResumeHooks() {
for (const fn of [...bareOsResumeHooks]) {
try {
await fn()
} catch {
/* ignore */
}
}
},
bareOsRequestKernelReload() {
const err = /** @type {Error & { code?: string }} */ (
new Error('BARE_OS_KERNEL_RELOAD')
)
err.code = 'BARE_OS_KERNEL_RELOAD'
throw err
},
bareOsRequestKernelProfileReload() {
const on =
shellEnv.BARE_OS_KERNEL_PROFILE_WARM === '1' ||
shellEnv.BARE_OS_KERNEL_PROFILE_WARM === 'true'
if (!on) {
throw new Error(
'bareOsRequestKernelProfileReload: enable BARE_OS_KERNEL_PROFILE_WARM=1'
)
}
const err = /** @type {Error & { code?: string }} */ (
new Error('BARE_OS_KERNEL_PROFILE_RELOAD')
)
err.code = 'BARE_OS_KERNEL_PROFILE_RELOAD'
throw err
},
...createBareOsLifecycleHookMethods({
suspendHooks: bareOsSuspendHooks,
resumeHooks: bareOsResumeHooks,
env: shellEnv
}),
/**
* Run trusted JS from the system image (kernel extensions only).
* @param {string} logicalPath
@@ -4190,152 +4141,11 @@ async function executeKernel(disk, store, swarm, initSource) {
line.slice(0, 4000)
)
},
/**
* POSIX-like signal dispatch over synthetic process IDs.
* Targets: numeric pid (**13** session stack, **4100+** shell jobs, **5100+** initd logical rows), **negative process group** (e.g. `-300` all running shell jobs with `pgid` 300), or `kernel` / `booter` / `shell`.
* Signals: HUP, INT, KILL, TERM, PIPE, CHLD, USR1, USR2; `PIPE`/`CHLD`/`USR*` update state only (no session exit).
*/
bareOsSendSignal(target, signal = 'TERM') {
const sig = bareOsNormalizeSignalName(signal)
if (!bareOsIsPosixSignalName(signal)) {
throw new Error('bareOsSendSignal: unsupported signal')
}
const raw = String(target == null ? '' : target).trim()
let pid = Number.parseInt(raw, 10)
let pgidTarget = null
if (Number.isFinite(pid) && pid < 0) {
pgidTarget = -pid
pid = NaN
}
if (!Number.isFinite(pid)) {
if (pgidTarget == null) {
if (raw === 'kernel') pid = 1
else if (raw === 'booter') pid = 2
else if (raw === 'shell') pid = 3
}
}
if (pgidTarget != null) {
const live = bareOsInteractiveCtxRef.ctx
const list =
live &&
live.shellBackgroundJobs &&
typeof live.shellBackgroundJobs === 'object' &&
Array.isArray(live.shellBackgroundJobs.list)
? live.shellBackgroundJobs.list
: []
const byPgid = list.filter(
(j) =>
j &&
typeof j.pgid === 'number' &&
Math.floor(j.pgid) === Math.floor(pgidTarget)
)
const active = byPgid.filter((j) => !j.done)
if (sig === '0') {
if (byPgid.length === 0) {
throw new Error('bareOsSendSignal: unknown process group')
}
return {
ok: true,
pgid: pgidTarget,
signal: sig,
delivered: false,
exists: true,
jobCount: byPgid.length
}
}
if (active.length === 0) {
throw new Error('bareOsSendSignal: unknown process group')
}
if (
this.bareOsLogicalSigaction &&
this.bareOsLogicalSigaction[sig] === 'IGNORE'
) {
return {
ok: true,
pgid: pgidTarget,
signal: sig,
delivered: false,
ignored: true,
atMs: Date.now()
}
}
const atMs = Date.now()
/** @type {number[]} */
const pids = []
for (const j of active) {
const jp = 4100 + j.id
pids.push(jp)
deliverBareOsVirtualSignalToPid(this, jp, sig)
}
return {
ok: true,
pgid: pgidTarget,
signal: sig,
delivered: true,
atMs,
pids
}
}
const extendedSynthetic =
Number.isFinite(pid) && pid >= 4100 && pid < 9000
if (!Number.isFinite(pid) || pid < 1 || (pid > 3 && !extendedSynthetic)) {
throw new Error('bareOsSendSignal: unknown process target')
}
if (sig === '0') {
return { ok: true, pid, signal: sig, delivered: false, exists: true }
}
const r = deliverBareOsVirtualSignalToPid(this, pid, sig)
if (r.ignored) {
return {
ok: true,
pid,
signal: sig,
delivered: false,
ignored: true,
atMs: r.atMs
}
}
return {
ok: true,
pid,
signal: sig,
delivered: !!r.delivered,
atMs: r.atMs
}
},
/**
* Logical nice adjustment for synthetic PIDs (stored on **`process_table.json`** rows; host scheduler is not affected).
* @param {number} pid
* @param {number} [delta]
*/
bareOsRenice(pid, delta = 0) {
const p = Number(pid)
const d = Number(delta)
if (!Number.isFinite(p) || p < 1) {
throw new Error('bareOsRenice: invalid pid')
}
if (!Number.isFinite(d)) {
throw new Error('bareOsRenice: invalid delta')
}
const clampNice = (n) =>
Math.max(-20, Math.min(19, Math.trunc(Number.isFinite(n) ? n : 0)))
const cur = clampNice(bareOsLogicalNiceByPid.get(p) ?? 0)
const next = clampNice(cur + d)
bareOsLogicalNiceByPid.set(p, next)
return {
ok: true,
pid: p,
delta: d,
nice: next,
previousNice: cur,
atMs: Date.now()
}
},
/**
* Try to spawn a host child via **`bare-subprocess`** when the addon is available (Bare hosts).
* @param {string[]} argv
* @param {Record<string, unknown>} [opts]
*/
...createBareOsProcessControlMethods({
deliverBareOsVirtualSignalToPid,
getInteractiveCtx: () => bareOsInteractiveCtxRef.ctx,
niceByPid: bareOsLogicalNiceByPid
}),
async bareOsTrySpawnHostSubprocess(argv, opts) {
const av = Array.isArray(argv) ? argv.map((x) => String(x)) : []
const incoming = opts && typeof opts === 'object' ? { ...opts } : {}
@@ -0,0 +1,73 @@
/**
* Suspend/resume hook registry and kernel reload request methods on `ctx`.
*/
/**
* @param {{
* suspendHooks: Array<() => void | Promise<void>>,
* resumeHooks: Array<() => void | Promise<void>>,
* env: Record<string, string | undefined>
* }} deps
*/
export function createBareOsLifecycleHookMethods(deps) {
const { suspendHooks, resumeHooks, env } = deps
return {
/** @param {() => void | Promise<void>} fn */
bareOsRegisterSuspendHook(fn) {
if (typeof fn === 'function') suspendHooks.push(fn)
return () => {
const i = suspendHooks.indexOf(fn)
if (i >= 0) suspendHooks.splice(i, 1)
}
},
/** @param {() => void | Promise<void>} fn */
bareOsRegisterResumeHook(fn) {
if (typeof fn === 'function') resumeHooks.push(fn)
return () => {
const i = resumeHooks.indexOf(fn)
if (i >= 0) resumeHooks.splice(i, 1)
}
},
async bareOsInvokeSuspendHooks() {
for (const fn of [...suspendHooks]) {
try {
await fn()
} catch {
/* ignore */
}
}
},
async bareOsInvokeResumeHooks() {
for (const fn of [...resumeHooks]) {
try {
await fn()
} catch {
/* ignore */
}
}
},
bareOsRequestKernelReload() {
const err = /** @type {Error & { code?: string }} */ (
new Error('BARE_OS_KERNEL_RELOAD')
)
err.code = 'BARE_OS_KERNEL_RELOAD'
throw err
},
bareOsRequestKernelProfileReload() {
const on =
env.BARE_OS_KERNEL_PROFILE_WARM === '1' ||
env.BARE_OS_KERNEL_PROFILE_WARM === 'true'
if (!on) {
throw new Error(
'bareOsRequestKernelProfileReload: enable BARE_OS_KERNEL_PROFILE_WARM=1'
)
}
const err = /** @type {Error & { code?: string }} */ (
new Error('BARE_OS_KERNEL_PROFILE_RELOAD')
)
err.code = 'BARE_OS_KERNEL_PROFILE_RELOAD'
throw err
}
}
}
@@ -0,0 +1,162 @@
/**
* Synthetic process signal and nice methods on kernel `ctx`.
*/
import {
bareOsIsPosixSignalName,
bareOsNormalizeSignalName
} from './bare-os-posix-signals.js'
/**
* @param {{
* deliverBareOsVirtualSignalToPid: (ctx: unknown, pid: number, sig: string) => { ignored?: boolean, delivered?: boolean, atMs?: number },
* getInteractiveCtx: () => Record<string, unknown> | null | undefined,
* niceByPid: Map<number, number>
* }} deps
*/
export function createBareOsProcessControlMethods(deps) {
const { deliverBareOsVirtualSignalToPid, getInteractiveCtx, niceByPid } = deps
return {
/**
* POSIX-like signal dispatch over synthetic process IDs.
* Targets: numeric pid (**13** session stack, **4100+** shell jobs, **5100+** initd logical rows), **negative process group** (e.g. `-300` → all running shell jobs with `pgid` 300), or `kernel` / `booter` / `shell`.
* Signals: HUP, INT, KILL, TERM, PIPE, CHLD, USR1, USR2; `PIPE`/`CHLD`/`USR*` update state only (no session exit).
*/
bareOsSendSignal(target, signal = 'TERM') {
const sig = bareOsNormalizeSignalName(signal)
if (!bareOsIsPosixSignalName(signal)) {
throw new Error('bareOsSendSignal: unsupported signal')
}
const raw = String(target == null ? '' : target).trim()
let pid = Number.parseInt(raw, 10)
let pgidTarget = null
if (Number.isFinite(pid) && pid < 0) {
pgidTarget = -pid
pid = NaN
}
if (!Number.isFinite(pid)) {
if (pgidTarget == null) {
if (raw === 'kernel') pid = 1
else if (raw === 'booter') pid = 2
else if (raw === 'shell') pid = 3
}
}
if (pgidTarget != null) {
const live = getInteractiveCtx()
const list =
live &&
live.shellBackgroundJobs &&
typeof live.shellBackgroundJobs === 'object' &&
Array.isArray(live.shellBackgroundJobs.list)
? live.shellBackgroundJobs.list
: []
const byPgid = list.filter(
(j) =>
j &&
typeof j.pgid === 'number' &&
Math.floor(j.pgid) === Math.floor(pgidTarget)
)
const active = byPgid.filter((j) => !j.done)
if (sig === '0') {
if (byPgid.length === 0) {
throw new Error('bareOsSendSignal: unknown process group')
}
return {
ok: true,
pgid: pgidTarget,
signal: sig,
delivered: false,
exists: true,
jobCount: byPgid.length
}
}
if (active.length === 0) {
throw new Error('bareOsSendSignal: unknown process group')
}
if (
this.bareOsLogicalSigaction &&
this.bareOsLogicalSigaction[sig] === 'IGNORE'
) {
return {
ok: true,
pgid: pgidTarget,
signal: sig,
delivered: false,
ignored: true,
atMs: Date.now()
}
}
const atMs = Date.now()
/** @type {number[]} */
const pids = []
for (const j of active) {
const jp = 4100 + j.id
pids.push(jp)
deliverBareOsVirtualSignalToPid(this, jp, sig)
}
return {
ok: true,
pgid: pgidTarget,
signal: sig,
delivered: true,
atMs,
pids
}
}
const extendedSynthetic =
Number.isFinite(pid) && pid >= 4100 && pid < 9000
if (!Number.isFinite(pid) || pid < 1 || (pid > 3 && !extendedSynthetic)) {
throw new Error('bareOsSendSignal: unknown process target')
}
if (sig === '0') {
return { ok: true, pid, signal: sig, delivered: false, exists: true }
}
const r = deliverBareOsVirtualSignalToPid(this, pid, sig)
if (r.ignored) {
return {
ok: true,
pid,
signal: sig,
delivered: false,
ignored: true,
atMs: r.atMs
}
}
return {
ok: true,
pid,
signal: sig,
delivered: !!r.delivered,
atMs: r.atMs
}
},
/**
* Logical nice adjustment for synthetic PIDs (stored on **`process_table.json`** rows; host scheduler is not affected).
* @param {number} pid
* @param {number} [delta]
*/
bareOsRenice(pid, delta = 0) {
const p = Number(pid)
const d = Number(delta)
if (!Number.isFinite(p) || p < 1) {
throw new Error('bareOsRenice: invalid pid')
}
if (!Number.isFinite(d)) {
throw new Error('bareOsRenice: invalid delta')
}
const clampNice = (n) =>
Math.max(-20, Math.min(19, Math.trunc(Number.isFinite(n) ? n : 0)))
const cur = clampNice(niceByPid.get(p) ?? 0)
const next = clampNice(cur + d)
niceByPid.set(p, next)
return {
ok: true,
pid: p,
delta: d,
nice: next,
previousNice: cur,
atMs: Date.now()
}
}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -89,6 +89,9 @@ import { createVfsRouteHelpers } from './lib/vfs-route.js'
import { createBareOsPosixFdSimMethods } from './lib/bare-os-posix-fd-sim.js'
import { createBareOsPathconfMethods } from './lib/bare-os-pathconf.js'
import { createBareOsLogicalFdMethods } from './lib/bare-os-logical-fd.js'
import { createVfsPseudoFileBytes } from './lib/vfs-pseudo-bytes.js'
import { createBareOsLifecycleHookMethods } from './lib/bare-os-lifecycle-hooks.js'
import { createBareOsProcessControlMethods } from './lib/bare-os-process-control.js'
test('posix env flags and caps', (t) => {
t.ok(wantPosixSocketFdBridge({ BARE_OS_POSIX_SOCKET_FD_BRIDGE: '1' }))
@@ -658,3 +661,62 @@ test('posix fd sim pipe write read', (t) => {
const probe = ctx.bareOsPosixPollProbe({ fds: [{ fd: 1, events: 'w' }] })
t.ok(probe.ready.some((x) => x.fd === 1 && x.revents === 'w'))
})
test('pseudo file bytes version and shm', (t) => {
const shm = new Map()
shm.set('q', new Uint8Array([9]))
const fn = createVfsPseudoFileBytes({
bareOsDevShm: shm,
pseudoVersionText: () => 'Bare OS\n',
pseudoUrandomBytes: () => new Uint8Array([1])
})
const ver = fn({ file: 'version', kind: 'proc' })
t.ok(ver.byteLength > 0)
const empty = fn({ file: 'null', kind: 'dev' })
t.is(empty.byteLength, 0)
const got = fn({ file: 'shm', kind: 'dev', shmName: 'q' })
t.is(got[0], 9)
})
test('lifecycle hooks register invoke and reload', async (t) => {
const suspendHooks = []
const resumeHooks = []
const m = createBareOsLifecycleHookMethods({
suspendHooks,
resumeHooks,
env: { BARE_OS_KERNEL_PROFILE_WARM: '1' }
})
let n = 0
m.bareOsRegisterSuspendHook(() => {
n++
})
await m.bareOsInvokeSuspendHooks()
t.is(n, 1)
t.exception(() => m.bareOsRequestKernelReload(), /BARE_OS_KERNEL_RELOAD/)
t.exception(
() => m.bareOsRequestKernelProfileReload(),
/BARE_OS_KERNEL_PROFILE_RELOAD/
)
})
test('process control existence probe and renice', (t) => {
const delivered = []
const nice = new Map()
const ctx = {
bareOsLogicalSigaction: {},
...createBareOsProcessControlMethods({
deliverBareOsVirtualSignalToPid: (_c, pid, sig) => {
delivered.push([pid, sig])
return { delivered: true, atMs: 1 }
},
getInteractiveCtx: () => ({ shellBackgroundJobs: { list: [] } }),
niceByPid: nice
})
}
const z = ctx.bareOsSendSignal('kernel', '0')
t.ok(z.exists)
t.is(z.pid, 1)
const r = ctx.bareOsRenice(1, 2)
t.is(r.nice, 2)
t.is(nice.get(1), 2)
})