packages/bare-os-booter/lib/shell.js $(...)/backticks are now enabled by default unless explicitly disabled via BARE_OS_SHELL_CMDSUBST=0|false|off. Fixed sh -c exit/status propagation: packages/bare-os-booter/lib/shell.js Syncs BARE_OS_EXIT_STATUS after each executed statement so later commands in the same line (like echo $?) see the immediately previous status. packages/bare-os-coreutils/src/sh.js In -c mode, reads final BARE_OS_EXIT_STATUS back into ctx.exitCode for consistent result propagation. Implemented trap behavior for requested scope (EXIT, INT, TERM): packages/bare-os-booter/lib/shell.js exit builtin now runs EXIT trap handler before requesting booter exit. packages/bare-os-booter/index.js Signal delivery for shell PID now triggers trap dispatch for INT/TERM. Hardened ulimit -f invalid diagnostics: packages/bare-os-coreutils/src/ulimit.js Explicit invalid-value error for malformed -f setter input, with nonzero exit. Supported-but-unimplemented setter values still return explicit unsupported-setter error.
117 lines
2.9 KiB
JavaScript
117 lines
2.9 KiB
JavaScript
import { readFile } from 'node:fs/promises'
|
|
import path from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
import test from 'brittle'
|
|
import b4a from 'b4a'
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor
|
|
|
|
async function loadBin(name) {
|
|
const runtime = await readFile(path.join(__dirname, '../lib/runtime.js'), 'utf8')
|
|
const body = await readFile(path.join(__dirname, `../src/${name}.js`), 'utf8')
|
|
return new AsyncFunction(
|
|
'ctx',
|
|
'argv',
|
|
`${runtime}\n${body}\nif (typeof run === 'function') return await run(ctx, argv)\n`
|
|
)
|
|
}
|
|
|
|
function mkCtx(vfs) {
|
|
const logs = []
|
|
const errs = []
|
|
return {
|
|
b4a,
|
|
exitCode: 0,
|
|
shellStdin: '',
|
|
console: { log: (m) => logs.push(String(m)), error: (m) => errs.push(String(m)) },
|
|
vfs,
|
|
_logs: logs,
|
|
_errs: errs
|
|
}
|
|
}
|
|
|
|
test('grep missing file exits 2', async (t) => {
|
|
const run = await loadBin('grep')
|
|
const ctx = mkCtx({
|
|
env: {},
|
|
async readFile() {
|
|
return null
|
|
}
|
|
})
|
|
await run(ctx, ['grep', 'foo', '/nonexist'])
|
|
t.is(ctx.exitCode, 2)
|
|
t.ok(ctx._errs.join('\n').includes('No such file'))
|
|
})
|
|
|
|
test('rm -f does not suppress permission errors', async (t) => {
|
|
const run = await loadBin('rm')
|
|
const ctx = mkCtx({
|
|
async rm() {
|
|
throw new Error('EACCES: denied')
|
|
},
|
|
async unlink() {
|
|
throw new Error('EACCES: denied')
|
|
},
|
|
async lstat() {
|
|
return { type: 'file' }
|
|
}
|
|
})
|
|
await run(ctx, ['rm', '-f', '/bin/sh'])
|
|
t.is(ctx.exitCode, 1)
|
|
t.ok(ctx._errs.join('\n').includes('EACCES'))
|
|
})
|
|
|
|
test('find missing root exits nonzero', async (t) => {
|
|
const run = await loadBin('find')
|
|
const ctx = mkCtx({
|
|
env: {},
|
|
resolveLogical: (p) => p,
|
|
async lstat() {
|
|
return null
|
|
}
|
|
})
|
|
await run(ctx, ['find', '/tmp/non', '-delete'])
|
|
t.is(ctx.exitCode, 1)
|
|
t.ok(ctx._errs.join('\n').includes('No such file or directory'))
|
|
})
|
|
|
|
test('ulimit -f setter reports unsupported', async (t) => {
|
|
const run = await loadBin('ulimit')
|
|
const ctx = mkCtx({
|
|
async readFile() {
|
|
return null
|
|
}
|
|
})
|
|
await run(ctx, ['ulimit', '-f', '1M'])
|
|
t.is(ctx.exitCode, 1)
|
|
t.ok(ctx._errs.join('\n').includes('not supported'))
|
|
})
|
|
|
|
test('ulimit -f invalid reports explicit diagnostic', async (t) => {
|
|
const run = await loadBin('ulimit')
|
|
const ctx = mkCtx({
|
|
async readFile() {
|
|
return null
|
|
}
|
|
})
|
|
await run(ctx, ['ulimit', '-f', 'invalid'])
|
|
t.is(ctx.exitCode, 1)
|
|
t.ok(ctx._errs.join('\n').includes('invalid file size'))
|
|
})
|
|
|
|
test('xargs -P0 maps to bounded parallel mode', async (t) => {
|
|
const run = await loadBin('xargs')
|
|
let calls = 0
|
|
const ctx = mkCtx({
|
|
env: { BARE_OS_XARGS_MAX_PROCS: '4' }
|
|
})
|
|
ctx.shellStdin = 'a b c d'
|
|
ctx.runBinCommand = async () => {
|
|
calls++
|
|
}
|
|
await run(ctx, ['xargs', '-P0', '-n1', 'echo'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(calls, 4)
|
|
})
|