Files
bare-operating-system/packages/bare-os-booter/test.posix-helpers.js
T
2026-08-18 18:11:34 -04:00

723 lines
23 KiB
JavaScript

import test from 'brittle'
import {
wantPosixSocketFdBridge,
wantPosixFcntlBlockingWait,
bareOsPosixDgramRecvQueueMax,
bareOsPosixAcceptQueueMax,
bareOsFlattenSendmsgIovs,
bareOsSendmsgAncillaryRejectReason,
bareOsResolveCooperativeLockPath
} from './lib/posix/bare-os-posix-syscall-helpers.js'
import { redactAuditShellLine } from './lib/security/bare-os-audit-redact.js'
import {
isExecLineBuiltinDenied,
shellCommandDeniedByPolicy,
shellUnsafeRedirectPath
} from './lib/shell/shell-policy.js'
import {
joinLogical,
dirnameAbs,
pathPrefixes,
isHyperdriveRootPath
} from './lib/vfs/vfs-path.js'
import { bareOsProtomuxTuningFromEnv } from './lib/p2p/swarm-opts.js'
import {
bareOsDupLogicalFdForScm,
prepareBridgeSendmsg,
bareOsShutdownSocketBridgeFd
} from './lib/posix/bare-os-socket-bridge.js'
import { createStockBareOsHrpcRequest } from './lib/tools/bare-os-hrpc-stock-request.js'
import {
splitTopLevelStatements,
isValidShellIdentifier,
parseShellFunctionDeclaration,
casePatternMatches
} from './lib/shell/shell-syntax.js'
import { createVfsPersonalLayout } from './lib/vfs/vfs-personal.js'
import {
listBareOsShellBuiltins,
isShellBuiltin,
bareOsShellReadSplitFields
} from './lib/shell/shell-builtins.js'
import {
getBareOsPipelineLimits,
mergePipelineChildCtx,
DEFAULT_PIPELINE_MAX_STAGES
} from './lib/shell/shell-runtime.js'
import { expandWord, expandShellWordTokens } from './lib/shell/shell-expand.js'
import {
utf8Encode,
createVfsPseudoLinux
} from './lib/vfs/vfs-pseudo-linux.js'
import { createBareOsVirtualSignalDeliverer } from './lib/posix/bare-os-virtual-signal.js'
import { createKernelLoaderAuditAppend } from './lib/host/bare-os-loader-audit.js'
import { createBooterBootEmitter } from './lib/boot/bare-os-boot-phases.js'
import { createBooterProcHelpers } from './lib/proc/bare-os-booter-proc-helpers.js'
import { tokenize } from './lib/shell/shell-token.js'
import {
defaultShellAliases,
expandArgvAliases,
applyAliasDefinition
} from './lib/shell/shell-alias.js'
import {
parsePipeline,
splitTokensBySemicolon,
splitTokensByAndOr,
planShellRedirections,
buildShellExecutionGraph
} from './lib/shell/shell-parse.js'
import {
assertUnionWriteNotDenied,
assertNotBootPolicyDenyVfs
} from './lib/vfs/vfs-policy.js'
import { createVfsWarmReadCache } from './lib/vfs/vfs-warm-cache.js'
import { createSessionReadMaskedLine } from './lib/cli/cli-readline.js'
import {
bareOsPipelineChildCtx,
bareOsPipelineRawChunkToText,
runWithShellPipelineStageTimeout
} from './lib/shell/shell-pipeline.js'
import {
assignShellFunctionPositionalEnv,
tryReportMisplacedReservedStatementStart,
casePatternList,
execDoubleBracketLimited,
bareOsResetShellIdentityState,
consumeInteractiveHeredoc
} from './lib/shell/shell-stmt.js'
import { createVfsRouteHelpers } from './lib/vfs/vfs-route.js'
import { createBareOsPosixFdSimMethods } from './lib/posix/bare-os-posix-fd-sim.js'
import { createBareOsPathconfMethods } from './lib/posix/bare-os-pathconf.js'
import { createBareOsLogicalFdMethods } from './lib/posix/bare-os-logical-fd.js'
import { createVfsPseudoFileBytes } from './lib/vfs/vfs-pseudo-bytes.js'
import { createBareOsLifecycleHookMethods } from './lib/host/bare-os-lifecycle-hooks.js'
import { createBareOsProcessControlMethods } from './lib/posix/bare-os-process-control.js'
test('posix env flags and caps', (t) => {
t.ok(wantPosixSocketFdBridge({ BARE_OS_POSIX_SOCKET_FD_BRIDGE: '1' }))
t.ok(!wantPosixSocketFdBridge({}))
t.ok(wantPosixFcntlBlockingWait({ BARE_OS_POSIX_FCNTL_BLOCKING_WAIT: 'yes' }))
t.is(bareOsPosixDgramRecvQueueMax({}), 256)
t.is(bareOsPosixDgramRecvQueueMax({ BARE_OS_POSIX_DGRAM_RECVQ_MAX: '8' }), 8)
t.is(bareOsPosixAcceptQueueMax({}), 64)
})
test('sendmsg flatten and ancillary reject', (t) => {
const a = bareOsFlattenSendmsgIovs({ iovs: [{ buf: new Uint8Array([1, 2]) }] })
t.ok(a.ok)
t.is(a.buf.byteLength, 2)
t.is(bareOsSendmsgAncillaryRejectReason({ cmsgs: [{}] }), 'cmsgs')
t.is(bareOsSendmsgAncillaryRejectReason({}), null)
t.is(
bareOsResolveCooperativeLockPath(
{ fd: 7 },
{ bareOsLogicalFds: { 7: '/tmp/x' } }
),
'/tmp/x'
)
})
test('audit redact and shell policy', (t) => {
t.is(redactAuditShellLine('PASSWORD=secret TOKEN=x', {}), 'PASSWORD=secret TOKEN=x')
t.is(
redactAuditShellLine('PASSWORD=secret TOKEN=x', { BARE_OS_AUDIT_REDACT: '1' }),
'PASSWORD=<redacted> TOKEN=<redacted>'
)
t.ok(isExecLineBuiltinDenied('cd', { BARE_OS_BOOT_POLICY_DENY_EXEC_LINE_BUILTINS: 'cd,export' }))
t.ok(!isExecLineBuiltinDenied('ls', { BARE_OS_BOOT_POLICY_DENY_EXEC_LINE_BUILTINS: 'cd' }))
t.ok(shellCommandDeniedByPolicy('rm', { BARE_OS_SHELL_DENY_COMMANDS: 'rm' }))
t.ok(
shellUnsafeRedirectPath('/proc/self/mem', { BARE_OS_SHELL_REDIRECT_GUARD: '1' })
)
})
test('vfs path helpers', (t) => {
t.ok(isHyperdriveRootPath('/'))
t.is(joinLogical('/', 'bin'), '/bin')
t.is(dirnameAbs('/home/user/x'), '/home/user')
t.alike(pathPrefixes('/a/b'), ['/', '/a', '/a/b'])
})
test('protomux tuning json', (t) => {
t.is(bareOsProtomuxTuningFromEnv({}), null)
t.alike(bareOsProtomuxTuningFromEnv({ BARE_OS_PROTOMUX_TUNING_JSON: '{' }), {
schema: 1,
parseError: true
})
t.is(
bareOsProtomuxTuningFromEnv({ BARE_OS_PROTOMUX_TUNING_JSON: '{"max":1}' }).max,
1
)
})
function mockBridgeCtx(extra = {}) {
const logical = { 8: '/tmp/x' }
const flags = { 8: 0x800 }
const sim = { nextFd: 20, nextPairId: 1, queues: Object.create(null), byteTotals: Object.create(null) }
const registered = []
return {
env: {},
bareOsLogicalFds: logical,
bareOsLogicalFdFlags: flags,
bareOsPosixFdSimState: sim,
bareOsSocketBridgeByFd: Object.create(null),
bareOsRegisterLogicalFd(fd, target) {
logical[String(fd)] = target
registered.push([fd, target])
},
bareOsUnregisterLogicalFd() {},
_registered: registered,
...extra
}
}
test('socket bridge logical dup and sendmsg payload', (t) => {
const c = mockBridgeCtx()
const n = bareOsDupLogicalFdForScm(c, 8)
t.is(n, 20)
t.is(c.bareOsLogicalFds['20'], '/tmp/x')
t.is(c.bareOsLogicalFdFlags['20'], 0x800)
const prep = prepareBridgeSendmsg(c, {
iovs: [{ buf: new Uint8Array([9, 8, 7]) }]
})
t.ok(prep.ok)
t.is(prep.payload.byteLength, 3)
t.alike(prep.scmRightsLocalDup, [])
})
test('prepareBridgeSendmsg rejects ancillary without SCM_RIGHTS', (t) => {
const c = mockBridgeCtx()
const prep = prepareBridgeSendmsg(c, {
iovs: [{ buf: new Uint8Array([1]) }],
cmsgs: [{ fds: [8] }]
})
t.absent(prep.ok)
t.is(prep.err.code, 'ENOTSUP')
})
test('bareOsShutdownSocketBridgeFd EINVAL on unknown fd', async (t) => {
const c = mockBridgeCtx()
const r = await bareOsShutdownSocketBridgeFd(c, 99)
t.absent(r.ok)
t.is(r.code, 'EINVAL')
})
test('stock hrpc kernel.ping and allowlist', async (t) => {
const audit = []
const hrpc = createStockBareOsHrpcRequest({
env: { BARE_OS_HRPC_AUDIT: '1' },
sessionId: 'sess1',
vfs: { readFile: async () => new Uint8Array() },
disk: {},
capabilityWords: { primary: 1 },
protocolVersion: '0.0-test',
booterVersion: '0.0-test',
auditAppend: (row) => audit.push(row)
})
t.ok(hrpc.__bareOsStockHrpcRequest)
const ping = await hrpc('kernel', 'ping')
t.ok(ping.ok)
t.is(ping.sessionId, 'sess1')
t.is(audit[0].route, 'kernel.ping')
await hrpc('kernel', 'capabilities').then((r) => {
t.ok(r.ok)
t.ok(r.hrpcStockRoutes.includes('kernel.ping'))
})
const denied = createStockBareOsHrpcRequest({
env: { BARE_OS_HRPC_ALLOWLIST_JSON: '["kernel.ping"]' },
sessionId: 's',
vfs: {},
disk: {},
capabilityWords: {},
protocolVersion: 'x',
booterVersion: 'x'
})
await t.exception(denied('vfs', 'readText'), /allowlist/)
await t.exception(hrpc('nope', 'x'), /unsupported route/)
})
test('stock hrpc vfs.readText and echo', async (t) => {
const hrpc = createStockBareOsHrpcRequest({
env: {},
sessionId: 's',
vfs: {
async readFile(p) {
t.is(p, '/etc/os-release')
return new TextEncoder().encode('ID=bare')
}
},
disk: {},
capabilityWords: {},
protocolVersion: 'x',
booterVersion: 'x'
})
const txt = await hrpc('vfs', 'readText', { path: '/etc/os-release' })
t.is(txt.text, 'ID=bare')
const echo = await hrpc('bare_os', 'echo', { a: 1 })
t.alike(echo.payload, { a: 1 })
await t.exception(hrpc('vfs', 'readText', { path: 'relative' }), /absolute/)
})
test('shell syntax split and function parse', (t) => {
const toks = [
{ type: 'word', value: 'echo' },
{ type: 'word', value: 'a' },
{ type: 'op', value: ';' },
{ type: 'word', value: 'echo' },
{ type: 'word', value: 'b' }
]
const parts = splitTopLevelStatements(toks)
t.is(parts.length, 2)
t.ok(isValidShellIdentifier('foo_1'))
t.ok(!isValidShellIdentifier('1foo'))
const fn = parseShellFunctionDeclaration([
{ type: 'word', value: 'greet' },
{ type: 'op', value: '(' },
{ type: 'op', value: ')' },
{ type: 'op', value: '{' },
{ type: 'word', value: 'echo' },
{ type: 'word', value: 'hi' },
{ type: 'op', value: '}' }
])
t.is(fn.name, 'greet')
t.ok(casePatternMatches('abc', 'a*'))
t.ok(!casePatternMatches('abc', 'x*'))
})
test('vfs personal layout and guest-sensitive paths', (t) => {
const guest = createVfsPersonalLayout(
{ HOME: '/home/guest/', BARE_OS_PERSONAL_ACCT_PREFIX: '1' },
{ session: 'guest' }
)
t.is(guest.normalizeHome(), '/home/guest')
t.is(guest.activeHomeBasename(), 'guest')
t.is(guest.personalLayoutRootAbs(), '/.bare-os/acct/_guest')
t.is(guest.personalHomeStorageRoot(), '/.bare-os/acct/_guest/home/guest')
t.ok(guest.bareOsGuestSensitivePersonalDenied('/.bare/vault/key'))
t.ok(!guest.environKeyAllowed('PASSWORD'))
t.ok(guest.environKeyAllowed('HOME'))
const unlocked = createVfsPersonalLayout(
{ HOME: '/home/guest' },
{ session: 'unlocked' }
)
t.ok(!unlocked.bareOsGuestSensitivePersonalDenied('/.bare/vault/key'))
t.is(unlocked.personalLayoutRootAbs(), '/.bare-os')
})
test('shell builtins and read field split', (t) => {
t.ok(isShellBuiltin('cd', {}))
t.ok(!isShellBuiltin('read', {}))
t.ok(isShellBuiltin('read', { BARE_OS_SHELL_READ_BUILTIN: '1' }))
t.ok(listBareOsShellBuiltins({ BARE_OS_SHELL_READ_BUILTIN: '1' }).includes('read'))
t.alike(bareOsShellReadSplitFields('a b c', ' ', 2), ['a', 'b c'])
})
test('shell runtime pipeline limits and child merge', (t) => {
t.is(getBareOsPipelineLimits({}).maxStages, DEFAULT_PIPELINE_MAX_STAGES)
t.is(getBareOsPipelineLimits({ BARE_OS_PIPELINE_MAX_STAGES: '4' }).maxStages, 4)
const ctx = { exitCode: 0 }
mergePipelineChildCtx(ctx, {
exitCode: 7,
identity: { state: 'unlocked', publicKey: 1, secretKey: 2 }
})
t.is(ctx.exitCode, 7)
t.is(ctx.identity.state, 'unlocked')
})
test('expandWord param and arithmetic', (t) => {
t.is(expandWord('x${HOME}y', { HOME: '/h' }), 'x/hy')
t.is(expandWord('$(( $a + 1 ))', { a: '2' }), '3')
})
test('expandShellWordTokens traces and expands unquoted words', async (t) => {
const ctx = {}
const env = {
HOME: '/h',
BARE_OS_SHELL_EXPANSION_TRACE: '1',
BARE_OS_SHELL_CMDSUBST: '0'
}
const out = await expandShellWordTokens(
ctx,
{ type: 'word', value: '$HOME', parts: [{ q: 'u', t: '$HOME' }] },
env,
{ disablePathnameExpansion: true }
)
t.alike(out, ['/h'])
t.ok(Array.isArray(ctx.shellExpansionTrace))
t.ok(ctx.shellExpansionTrace.some((r) => r.stage === 'expand-pre'))
t.ok(ctx.shellExpansionTrace.some((r) => r.stage === 'split-glob'))
})
test('vfs linux-shaped /proc texts', (t) => {
const linux = createVfsPseudoLinux({
env: {
BARE_OS_CTX_API_VERSION: '1.2.3',
BARE_OS_SESSION_ID: 'sess-1',
HOME: '/home/guest',
PASSWORD: 'nope'
},
procSnapshot: { version: '1.2.3', cmdline: 'bare-os' },
bootStartedMs: Date.now() - 2500,
environKeyAllowed: (k) => k !== 'PASSWORD',
getProcSyntheticLinuxCompat: () => ({
sessionId: 'sess-1',
swarmPeerCount: 1,
peerIds: ['aabbccdd']
})
})
t.ok(linux.pseudoVersionText().includes('1.2.3'))
t.ok(linux.pseudoMeminfoText().includes('MemTotal:'))
t.ok(linux.pseudoCpuinfoText().includes('processor'))
t.ok(linux.pseudoNetTcpText().includes('ESTABLISHED'))
t.ok(linux.pseudoSelfCgroupsText().includes('sess-1'))
t.ok(utf8Encode('hi').byteLength >= 2)
})
test('virtual signal deliverer ignore and existence probe', (t) => {
const state = new Map()
const traps = []
const deliver = createBareOsVirtualSignalDeliverer({
dispatchShellTrapSignal: (ctx, sig) => {
traps.push(sig)
},
virtualSignalState: state
})
const ctx = {
bareOsLogicalSigaction: { TERM: 'IGNORE' },
requestBooterExit() {
t.fail('should not exit when ignored')
}
}
const ignored = deliver(ctx, 3, 'TERM')
t.ok(ignored.ignored)
t.is(traps.length, 0)
const probe = deliver({ requestBooterExit() {} }, 3, '0')
t.ok(probe.exists)
t.absent(probe.delivered)
})
test('loader audit appends when enabled', async (t) => {
const files = new Map()
const append = createKernelLoaderAuditAppend({
env: { BARE_OS_LOADER_AUDIT: '1' },
sessionId: 's1',
vfs: {
async readFile(p) {
if (!files.has(p)) throw new Error('missing')
return files.get(p)
},
async writeFile(p, buf) {
files.set(p, buf)
}
}
})
await append({ phase: 'vfs' })
const txt = new TextDecoder().decode(files.get('/run/bare-os/loader-audit.ndjson'))
t.ok(txt.includes('"type":"loader_audit"'))
t.ok(txt.includes('"phase":"vfs"'))
})
test('booter boot emitter records phases', (t) => {
const bootReadyStateRef = { booterPhases: [], booterStages: [] }
const events = []
const { emitBooterBootStep } = createBooterBootEmitter({
bootReadyStateRef,
bootStartedMs: Date.now(),
bootEventSubs: [(ev) => events.push(ev)],
diagnosticsSubs: [],
sessionId: 's',
lifecycleSchemaVersion: 1
})
emitBooterBootStep('vfs')
emitBooterBootStep('vfs')
t.alike(bootReadyStateRef.booterPhases, ['vfs'])
t.is(events.length, 2)
t.is(events[0].phase, 'booter:vfs')
})
test('booter proc helpers chat/meshdrop off notes', (t) => {
const helpers = createBooterProcHelpers({
interactiveCtxRef: { ctx: { bareOsLogicalFds: { 7: '/tmp/x' } } },
disk: {},
shellEnv: {},
hostEnv: {}
})
t.alike(helpers.bareOsCollectLogicalFdRows(), [{ fd: 7, target: '/tmp/x' }])
t.ok(helpers.bareOsChatProcSnapshotRecord().note)
t.ok(helpers.bareOsMeshdropProcSnapshotRecord().note)
})
test('shell token alias parse', (t) => {
const toks = tokenize('echo a; echo b')
t.ok(toks.length >= 3)
t.alike(expandArgvAliases(['ll'], defaultShellAliases()).slice(0, 1), ['ls'])
const ctx = { shellAliases: {} }
t.ok(applyAliasDefinition(ctx, "gst='git status'"))
t.is(ctx.shellAliases.gst, 'git status')
const pipe = parsePipeline(tokenize('echo hi > out'))
t.is(planShellRedirections(pipe[0]).stdout, 'truncate')
t.is(splitTokensBySemicolon(tokenize('a; b')).length, 2)
t.alike(splitTokensByAndOr(tokenize('a && b')).ops, ['&&'])
t.is(buildShellExecutionGraph('echo x').schema, 1)
})
test('vfs deny asserts and warm cache evict', (t) => {
t.exception(
() =>
assertUnionWriteNotDenied('/mnt/x/a', ['/mnt/x'], ['/mnt/x']),
/union write denied/
)
assertUnionWriteNotDenied('/home/g/a', ['/mnt/x'], ['/mnt/x'])
t.exception(
() =>
assertNotBootPolicyDenyVfs('/etc/shadow', 'read', {
BARE_OS_BOOT_POLICY_DENY_VFS: '/etc'
}),
/boot policy denies/
)
const ref = { current: null }
const cache = createVfsWarmReadCache({
env: { BARE_OS_VFS_BIN_CACHE: '1' },
warmReadCacheStatsRef: ref
})
t.ok(cache.binReadCache)
cache.binReadCache.set('/bin/ls', new Uint8Array([1]))
t.is(cache.bareOsEvictWarmReadPrefixes(['/bin/']), 1)
t.is(cache.binReadCache.size, 0)
})
test('session masked line falls back without TTY', async (t) => {
const read = createSessionReadMaskedLine({
stdin: {},
stdout: {},
fallbackReadLine: async (p) => 'fb:' + p
})
t.is(await read('pw> '), 'fb:pw> ')
})
test('pipeline child ctx chunk and timeout', async (t) => {
const child = bareOsPipelineChildCtx({ a: 1 }, { X: '1' }, 'in', true)
t.is(child.shellStdin, 'in')
t.ok(child.bareOsStdoutCaptured)
t.is(child.env.X, '1')
t.is(bareOsPipelineRawChunkToText('ab'), 'ab')
t.is(bareOsPipelineRawChunkToText(new Uint8Array([65, 66])), 'AB')
const v = await runWithShellPipelineStageTimeout({}, async () => 7, 'x')
t.is(v, 7)
})
test('shell stmt helpers local-style', async (t) => {
const env = {}
assignShellFunctionPositionalEnv(env, ['fn', 'a', 'b'])
t.is(env['0'], 'fn')
t.is(env['1'], 'a')
t.is(env['2'], 'b')
t.is(env['#'], '2')
const errs = []
const ctx = { console: { error: (m) => errs.push(m) }, exitCode: 0 }
t.ok(tryReportMisplacedReservedStatementStart(ctx, [{ type: 'word', value: 'then' }]))
t.is(ctx.exitCode, 2)
t.alike(casePatternList([{ type: 'word', value: 'x*' }, { type: 'op', value: '|' }, { type: 'word', value: 'y' }], {}), [
'x*',
'y'
])
const br = {
vfs: { env: { A: 'hi' } },
console: { error: () => {} },
exitCode: 99
}
await execDoubleBracketLimited(br, [
{ type: 'word', value: '[[' },
{ type: 'word', value: '$A' },
{ type: 'word', value: '==' },
{ type: 'word', value: 'hi' },
{ type: 'word', value: ']]' }
])
t.is(br.exitCode, 0)
const jobs = { shellBackgroundJobs: { nextId: 9, list: [1] } }
bareOsResetShellIdentityState(jobs)
t.is(jobs.shellBackgroundJobs.nextId, 1)
t.is(jobs.shellBackgroundJobs.list.length, 0)
})
test('vfs route tilde mnt and ro alias', (t) => {
const helpers = createVfsRouteHelpers({
getCwd: () => '/home/g',
normalizeHome: () => '/home/g',
mntRef: {
getMounts: () =>
new Map([['data', { drive: { get: () => 1 }, writable: true }]])
},
systemRoAliasNorm: '/sysro',
systemDrive: { id: 'sys' }
})
t.is(helpers.expandTilde('~/x'), '/home/g/x')
t.is(helpers.resolveLogical('y'), '/home/g/y')
t.ok(helpers.routeMnt('/mnt').virtualMntRoot)
t.is(helpers.routeMnt('/mnt/data/a').path, '/a')
t.ok(helpers.routeSystemRoAlias('/sysro/etc').mntReadOnly)
t.ok(helpers.pseudoMountsText().includes('/mnt/data'))
})
test('vfs route tmp home union snapshots', (t) => {
const personal = { id: 'p' }
const system = { id: 'sys', checkout: (v) => ({ id: 'chk' + v }) }
const helpers = createVfsRouteHelpers({
getCwd: () => '/home/g',
normalizeHome: () => '/home/g',
systemRoAliasNorm: '',
systemDrive: system,
personalDrive: personal,
env: { BARE_OS_VFS_SNAPSHOTS: '1' },
tmpStorageRoot: () => '/.bare-os/tmp/g',
varLogStorageRoot: () => '/.bare-os/var/log/g',
activeHomeBasename: () => 'g',
personalHomeStorageRoot: () => '/.bare-os/home/g',
usePersonalAcctPrefix: () => false
})
t.is(helpers.route('/tmp/x').path, '/.bare-os/tmp/g/x')
t.ok(helpers.route('/var').virtualVarRoot)
t.is(helpers.route('/home/g/a').drive, personal)
t.is(helpers.route('/snapshots/system/3/etc').snapshotReadOnly, true)
t.is(helpers.route('/.bare/holesail/state.json').path, '/.bare/holesail/state.json')
})
test('pathconf and getconf helpers', (t) => {
const m = createBareOsPathconfMethods({
env: { BARE_OS_NPROC: '8', BARE_OS_PIPELINE_MAX_STAGES: '12' },
resolveLogical: (p) => p,
bootHrtimeNowNs: null
})
t.is(m.bareOsPathconf('/etc', 'NAME_MAX'), 255)
t.is(m.bareOsPathconf('/mirror/x', '_PC_CHOWN_RESTRICTED'), 0)
t.is(m.bareOsPathconf('/dev/shm/a', '_PC_NAME_MAX'), 128)
t.is(m.bareOsGetconfSysconf('_SC_NPROCESSORS_ONLN'), '8')
t.is(m.bareOsGetconfSysconf('_SC_PAGESIZE'), '4096')
t.is(m.bareOsGetconfSysconf('_SC_BARE_OS_PIPELINE_MAX_STAGES'), '12')
t.is(m.bareOsGetconfSysconf('_SC_MONOTONIC_CLOCK_RES'), '0')
})
test('logical fd register and sigaction', (t) => {
const ctx = {
bareOsLogicalFds: {},
bareOsLogicalFdFlags: { 9: 1 },
bareOsLogicalSigaction: {},
bareOsSnapshotHandles: [],
...createBareOsLogicalFdMethods()
}
ctx.bareOsRegisterLogicalFd(9, '/tmp/x')
t.is(ctx.bareOsLogicalFds['9'], '/tmp/x')
ctx.bareOsUnregisterLogicalFd(9)
t.is(ctx.bareOsLogicalFds['9'], undefined)
t.is(ctx.bareOsSigaction('INT', 'IGNORE').mode, 'IGNORE')
t.is(ctx.bareOsLogicalSigaction.INT, 'IGNORE')
ctx.bareOsRegisterSnapshotHandle({ id: 's1' })
t.is(ctx.bareOsSnapshotHandles[0].id, 's1')
})
test('consumeInteractiveHeredoc reads until delim', async (t) => {
const lines = ['one', 'two', 'END']
const ctx = {
readLine: async () => lines.shift(),
vfs: { env: {} },
console: { error: () => {} }
}
const r = await consumeInteractiveHeredoc(
ctx,
"cat <<END",
() => {}
)
t.ok(r.ok)
t.is(r.execLine, 'cat')
t.is(ctx.shellHeredocOnce, 'one\ntwo\n')
})
test('posix fd sim pipe write read', (t) => {
const methods = createBareOsPosixFdSimMethods({
env: { BARE_OS_POSIX_FD_SIM: '1' },
bootHrtimeNowNs: null
})
const ctx = {
bareOsLogicalFds: {},
bareOsLogicalFdFlags: {},
bareOsPosixFdSimState: {
nextFd: 10,
nextPairId: 1,
queues: Object.create(null),
byteTotals: Object.create(null)
},
bareOsRegisterLogicalFd(fd, target) {
this.bareOsLogicalFds[String(fd)] = target
},
...methods
}
const pipe = ctx.bareOsPosixFdSimPipe()
t.ok(pipe)
const w = ctx.bareOsPosixFdSimWrite(pipe.writeFd, 'hi')
t.ok(w.ok)
const r = ctx.bareOsPosixFdSimRead(pipe.readFd)
t.ok(r.ok)
t.is(r.data, 'hi')
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)
})