sh, diff/patch, sort, printf, find, test, getfacl/setfacl/xattr), expanded /proc and metrics (process table, syscalls, replication, net, security posture, worker budget, swarm/replication hints), initd DAG supervision metadata and richer restart journal telemetry, synthetic process groups via IPC (assignProcessGroup/signalProcessGroup) mirrored into process_table, optional kernel.ext.d incremental hot reload (BARE_OS_KERNEL_EXT_D_HOT_RELOAD) with reload audit NDJSON, features proc for hyperblobs dedup and systemd subset documentation, vault threat model doc plus posture fields for AEAD, Pear enclave pointer, account rotation continuity, and Ed25519 consistency across boot manifest / extensions / replication. Adds or extends tests and keeps kernel/ and packages/bare-os-seeder/kernel/ in parity; guest init is bundled from kernel/lib/init/init-main.js via bundle-kernel-init.
325 lines
8.5 KiB
Plaintext
325 lines
8.5 KiB
Plaintext
/* BARE_OS_BIN_API 1.0.0 — bump when staged /bin script semantics change (see developer guide). */
|
|
/** Shared helpers for drive-resident /bin scripts (prepended before each command). */
|
|
function bareStdin(ctx) {
|
|
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
|
|
}
|
|
|
|
/** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */
|
|
function bareFormatModeString(mode, type) {
|
|
const typeChar = type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-'
|
|
const perm = mode & 0o777
|
|
const r = (bit) => (perm & bit ? 'r' : '-')
|
|
const w = (bit) => (perm & bit ? 'w' : '-')
|
|
const x = (bit) => (perm & bit ? 'x' : '-')
|
|
return (
|
|
typeChar +
|
|
r(0o400) +
|
|
w(0o200) +
|
|
x(0o100) +
|
|
r(0o040) +
|
|
w(0o020) +
|
|
x(0o010) +
|
|
r(0o004) +
|
|
w(0o002) +
|
|
x(0o001)
|
|
)
|
|
}
|
|
|
|
/** @param {number} mtimeMs @param {number} [nowMs] */
|
|
function bareFormatLsMtime(mtimeMs, nowMs) {
|
|
const now = nowMs != null ? nowMs : Date.now()
|
|
const d = new Date(mtimeMs)
|
|
const months = [
|
|
'Jan',
|
|
'Feb',
|
|
'Mar',
|
|
'Apr',
|
|
'May',
|
|
'Jun',
|
|
'Jul',
|
|
'Aug',
|
|
'Sep',
|
|
'Oct',
|
|
'Nov',
|
|
'Dec'
|
|
]
|
|
const mon = months[d.getMonth()]
|
|
const day = String(d.getDate()).padStart(2, ' ')
|
|
const sixMo = 180 * 24 * 3600 * 1000
|
|
if (Math.abs(now - mtimeMs) > sixMo) {
|
|
const yr = String(d.getFullYear()).padStart(4, ' ')
|
|
return mon + ' ' + day + ' ' + yr
|
|
}
|
|
const hh = String(d.getHours()).padStart(2, '0')
|
|
const mm = String(d.getMinutes()).padStart(2, '0')
|
|
return mon + ' ' + day + ' ' + hh + ':' + mm
|
|
}
|
|
|
|
/** @param {number} size */
|
|
function barePosixBlocks(size) {
|
|
return Math.ceil(Number(size) / 512) || 0
|
|
}
|
|
|
|
/**
|
|
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
|
|
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
|
|
* @param {Record<string, unknown>} ctx
|
|
* @param {string | Uint8Array} chunk
|
|
* @returns {boolean}
|
|
*/
|
|
function bareOsEmitRaw(ctx, chunk) {
|
|
if (typeof ctx.bareOsBinWrite === 'function') {
|
|
const b4 = ctx.b4a
|
|
const u8 =
|
|
typeof chunk === 'string'
|
|
? b4 && typeof b4.from === 'function'
|
|
? b4.from(chunk)
|
|
: new TextEncoder().encode(chunk)
|
|
: chunk
|
|
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
|
|
return true
|
|
}
|
|
const w = globalThis.process?.stdout?.write
|
|
if (typeof w === 'function') {
|
|
w.call(globalThis.process.stdout, chunk)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
/**
|
|
* Bounded xargs for Bare OS: invokes ctx.runBinCommand only (no host spawn).
|
|
* Limits: stdin 256KiB, 4096 whitespace/null tokens, 128 args per invocation,
|
|
* 64 invocations per run. Exceeding limits is a fatal error (exit 125).
|
|
* Supports -0/--null, -n, -I repl (replace repl in utility argv; implies -n 1 unless -n given).
|
|
* -P N runs up to N batches in parallel (each batch uses a shallow ctx clone so exitCode does not race).
|
|
* Max -P is min(requested, BARE_OS_XARGS_MAX_PROCS env, 32); default cap 8 when env unset.
|
|
*/
|
|
|
|
const MAX_STDIN = 256 * 1024
|
|
const MAX_TOKENS = 4096
|
|
const MAX_PER_INVOCATION = 128
|
|
const MAX_INVOCATIONS = 64
|
|
const DEFAULT_P_CAP = 8
|
|
const ABS_P_CAP = 32
|
|
|
|
/**
|
|
* @param {Record<string, unknown>} ctx
|
|
*/
|
|
function maxParallelFromEnv(ctx) {
|
|
const env = (ctx && ctx.vfs && ctx.vfs.env) || (ctx && ctx.env) || {}
|
|
const raw = String(env.BARE_OS_XARGS_MAX_PROCS || '').trim()
|
|
if (!raw || !/^\d+$/.test(raw)) return DEFAULT_P_CAP
|
|
const n = Number(raw)
|
|
return Math.min(ABS_P_CAP, Math.max(1, n))
|
|
}
|
|
|
|
async function run(ctx, argv) {
|
|
if (typeof ctx.runBinCommand !== 'function') {
|
|
ctx.console.error(
|
|
'xargs: ctx.runBinCommand is not available (requires a Bare OS booter session)'
|
|
)
|
|
ctx.exitCode = 1
|
|
return
|
|
}
|
|
const args = argv.slice(1)
|
|
let nullSep = false
|
|
let maxBatch = MAX_PER_INVOCATION
|
|
/** @type {string | null} */
|
|
let repl = null
|
|
let nExplicit = false
|
|
const envPCap = maxParallelFromEnv(ctx)
|
|
let pCap = 1
|
|
let i = 0
|
|
|
|
while (i < args.length && args[i].startsWith('-')) {
|
|
const a = args[i]
|
|
if (a === '--') {
|
|
i++
|
|
break
|
|
}
|
|
if (a === '-0' || a === '--null') {
|
|
nullSep = true
|
|
i++
|
|
continue
|
|
}
|
|
if (a === '-P' || a === '--max-procs') {
|
|
const n = args[i + 1]
|
|
if (n == null || !/^\d+$/.test(n)) {
|
|
ctx.console.error('xargs: -P requires a non-negative integer')
|
|
ctx.exitCode = 1
|
|
return
|
|
}
|
|
const raw = Number(n)
|
|
pCap = Math.min(envPCap, Math.max(1, raw))
|
|
if (raw > envPCap) {
|
|
ctx.console.error(
|
|
'xargs: -P ' +
|
|
raw +
|
|
' exceeds cap ' +
|
|
envPCap +
|
|
' (raise BARE_OS_XARGS_MAX_PROCS up to ' +
|
|
ABS_P_CAP +
|
|
')'
|
|
)
|
|
}
|
|
i += 2
|
|
continue
|
|
}
|
|
if (a.startsWith('-P') && a.length > 2 && /^\d+$/.test(a.slice(2))) {
|
|
const raw = Number(a.slice(2))
|
|
pCap = Math.min(envPCap, Math.max(1, raw))
|
|
if (raw > envPCap) {
|
|
ctx.console.error(
|
|
'xargs: -P exceeds cap ' +
|
|
envPCap +
|
|
' (raise BARE_OS_XARGS_MAX_PROCS up to ' +
|
|
ABS_P_CAP +
|
|
')'
|
|
)
|
|
}
|
|
i++
|
|
continue
|
|
}
|
|
if (a === '-n' || a === '--max-args') {
|
|
const n = args[i + 1]
|
|
if (n == null || !/^\d+$/.test(n) || Number(n) < 1) {
|
|
ctx.console.error('xargs: -n requires a positive integer')
|
|
ctx.exitCode = 1
|
|
return
|
|
}
|
|
maxBatch = Math.min(Number(n), MAX_PER_INVOCATION)
|
|
nExplicit = true
|
|
i += 2
|
|
continue
|
|
}
|
|
if (a.startsWith('-n') && a.length > 2 && /^\d+$/.test(a.slice(2))) {
|
|
const n = Number(a.slice(2))
|
|
maxBatch = Math.min(n, MAX_PER_INVOCATION)
|
|
nExplicit = true
|
|
i++
|
|
continue
|
|
}
|
|
if (a === '-I' || a === '-i') {
|
|
repl = args[i + 1] != null ? String(args[i + 1]) : '{}'
|
|
i += 2
|
|
if (!nExplicit) maxBatch = 1
|
|
continue
|
|
}
|
|
if (
|
|
(a.startsWith('-I') || a.startsWith('-i')) &&
|
|
a.length > 2 &&
|
|
a[2] !== '-'
|
|
) {
|
|
repl = a.slice(2) || '{}'
|
|
i++
|
|
if (!nExplicit) maxBatch = 1
|
|
continue
|
|
}
|
|
ctx.console.error('xargs: unsupported option: ' + a)
|
|
ctx.console.error(
|
|
'xargs: Bare OS supports: -0/--null, -n N (max ' +
|
|
MAX_PER_INVOCATION +
|
|
' per run), -I repl, -P N (parallel batches, cap ' +
|
|
envPCap +
|
|
')'
|
|
)
|
|
ctx.exitCode = 1
|
|
return
|
|
}
|
|
|
|
/** @type {string[]} */
|
|
let cmd = args.slice(i)
|
|
if (cmd.length === 0) cmd = ['echo']
|
|
|
|
let text = bareStdin(ctx) || ''
|
|
if (text.length > MAX_STDIN) {
|
|
ctx.console.error(
|
|
'xargs: stdin exceeds ' + MAX_STDIN + ' bytes (Bare OS limit)'
|
|
)
|
|
ctx.exitCode = 125
|
|
return
|
|
}
|
|
|
|
/** @type {string[]} */
|
|
let pieces
|
|
if (nullSep) {
|
|
pieces = text.split('\0').filter((s) => s.length > 0)
|
|
} else {
|
|
pieces = text.trim() === '' ? [] : text.trim().split(/\s+/).filter(Boolean)
|
|
}
|
|
|
|
if (pieces.length > MAX_TOKENS) {
|
|
ctx.console.error(
|
|
'xargs: too many arguments (' + pieces.length + ' > ' + MAX_TOKENS + ')'
|
|
)
|
|
ctx.exitCode = 125
|
|
return
|
|
}
|
|
|
|
/**
|
|
* @param {Record<string, unknown>} target
|
|
* @param {string[]} batch
|
|
*/
|
|
const runOne = async (target, batch) => {
|
|
const subst = batch.join(' ')
|
|
/** @type {string[]} */
|
|
const toRun =
|
|
repl == null
|
|
? cmd.concat(batch)
|
|
: cmd.map((c) => {
|
|
let o = c
|
|
let guard = 0
|
|
while (o.includes(repl) && guard < 4096) {
|
|
o = o.split(repl).join(subst)
|
|
guard++
|
|
}
|
|
return o
|
|
})
|
|
await target.runBinCommand(toRun)
|
|
}
|
|
|
|
if (pieces.length === 0) {
|
|
await runOne(ctx, [])
|
|
return
|
|
}
|
|
|
|
/** @type {string[][]} */
|
|
const batches = []
|
|
for (let o = 0; o < pieces.length; o += maxBatch) {
|
|
if (batches.length >= MAX_INVOCATIONS) {
|
|
ctx.console.error(
|
|
'xargs: exceeded ' + MAX_INVOCATIONS + ' invocations (Bare OS limit)'
|
|
)
|
|
ctx.exitCode = 125
|
|
return
|
|
}
|
|
batches.push(pieces.slice(o, o + maxBatch))
|
|
}
|
|
|
|
if (pCap <= 1) {
|
|
for (const batch of batches) {
|
|
await runOne(ctx, batch)
|
|
if ((Number(ctx.exitCode) || 0) !== 0) return
|
|
}
|
|
return
|
|
}
|
|
|
|
for (let w = 0; w < batches.length; w += pCap) {
|
|
const slice = batches.slice(w, w + pCap)
|
|
const codes = await Promise.all(
|
|
slice.map(async (batch) => {
|
|
const c = Object.assign({}, ctx, { exitCode: 0 })
|
|
await runOne(c, batch)
|
|
return Number(c.exitCode) || 0
|
|
})
|
|
)
|
|
const bad = codes.find((x) => x !== 0)
|
|
if (bad != null) {
|
|
ctx.exitCode = bad
|
|
return
|
|
}
|
|
}
|
|
ctx.exitCode = 0
|
|
}
|