This commit is contained in:
Raven Scott
2026-04-04 19:08:51 -04:00
parent 92368de550
commit b458c6031d
61 changed files with 4669 additions and 446 deletions
+143
View File
@@ -0,0 +1,143 @@
function parseSize(v) {
const s = String(v || '').trim().toLowerCase()
const m = /^(\d+)([kmg]?)$/.exec(s)
if (!m) return null
const n = Number.parseInt(m[1], 10)
if (!Number.isFinite(n) || n < 0) return null
const mul = m[2] === 'k' ? 1024 : m[2] === 'm' ? 1024 * 1024 : m[2] === 'g' ? 1024 * 1024 * 1024 : 1
return n * mul
}
function concatU8(b4a, chunks) {
let total = 0
for (const c of chunks) total += c.byteLength
const out = b4a.alloc(total)
let off = 0
for (const c of chunks) {
out.set(c, off)
off += c.byteLength
}
return out
}
async function run(ctx, argv) {
/** @type {{ if?: string, of?: string, bs: number, count?: number, skip: number, seek: number, status: string, conv?: string }} */
const opt = { bs: 512, skip: 0, seek: 0, status: 'progress' }
for (let i = 1; i < argv.length; i++) {
const a = argv[i]
if (a === '--') continue
const eq = a.indexOf('=')
if (eq <= 0) {
ctx.console.error('dd: expected OPERAND=VALUE: ' + a)
ctx.exitCode = 1
return
}
const k = a.slice(0, eq)
const v = a.slice(eq + 1)
if (k === 'if') opt.if = v
else if (k === 'of') opt.of = v
else if (k === 'bs') {
const n = parseSize(v)
if (n == null || n <= 0) {
ctx.console.error('dd: invalid bs=' + v)
ctx.exitCode = 1
return
}
opt.bs = n
} else if (k === 'count') {
const n = Number.parseInt(v, 10)
if (!Number.isFinite(n) || n < 0) {
ctx.console.error('dd: invalid count=' + v)
ctx.exitCode = 1
return
}
opt.count = n
} else if (k === 'skip') {
const n = Number.parseInt(v, 10)
if (!Number.isFinite(n) || n < 0) {
ctx.console.error('dd: invalid skip=' + v)
ctx.exitCode = 1
return
}
opt.skip = n
} else if (k === 'seek') {
const n = Number.parseInt(v, 10)
if (!Number.isFinite(n) || n < 0) {
ctx.console.error('dd: invalid seek=' + v)
ctx.exitCode = 1
return
}
opt.seek = n
} else if (k === 'status') {
if (v !== 'none' && v !== 'progress') {
ctx.console.error('dd: unsupported status=' + v)
ctx.exitCode = 1
return
}
opt.status = v
} else if (k === 'conv') {
if (v !== 'notrunc') {
ctx.console.error('dd: unsupported conv=' + v)
ctx.exitCode = 1
return
}
opt.conv = v
} else {
ctx.console.error('dd: unsupported operand ' + k)
ctx.exitCode = 1
return
}
}
let src
if (opt.if) {
src = await ctx.vfs.readFile(opt.if)
if (!src) {
ctx.console.error('dd: ' + opt.if + ': No such file or directory')
ctx.exitCode = 1
return
}
} else {
src = ctx.b4a.from(bareStdin(ctx), 'utf8')
}
const input = src instanceof Uint8Array ? src : ctx.b4a.from(src)
const start = Math.min(opt.skip * opt.bs, input.byteLength)
const end =
opt.count == null
? input.byteLength
: Math.min(input.byteLength, start + opt.count * opt.bs)
const chunk = input.subarray(start, end)
if (!opt.of) {
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') w.call(globalThis.process.stdout, chunk)
else ctx.console.log(ctx.b4a.toString(chunk))
if (opt.status !== 'none') {
ctx.console.error(`${chunk.byteLength} bytes copied`)
}
return
}
const outOff = opt.seek * opt.bs
let prev = null
try {
prev = await ctx.vfs.readFile(opt.of)
} catch {
prev = null
}
const prevU8 = prev ? (prev instanceof Uint8Array ? prev : ctx.b4a.from(prev)) : ctx.b4a.alloc(0)
let out
if (opt.conv === 'notrunc') {
const want = Math.max(prevU8.byteLength, outOff + chunk.byteLength)
out = ctx.b4a.alloc(want)
if (prevU8.byteLength) out.set(prevU8, 0)
out.set(chunk, outOff)
} else {
out = concatU8(ctx.b4a, [ctx.b4a.alloc(outOff), chunk])
}
await ctx.vfs.writeFile(opt.of, out)
if (opt.status !== 'none') {
ctx.console.error(`${chunk.byteLength} bytes copied`)
}
}
+8 -1
View File
@@ -35,7 +35,14 @@ const CONF = {
/** Max input lines `shuf` will hold in memory (override with BARE_OS_SHUF_MAX_LINES). */
BARE_OS_SHUF_MAX_LINES: '50000',
/** Max output chunk files `split` may create (override with BARE_OS_SPLIT_MAX_FILES). */
BARE_OS_SPLIT_MAX_FILES: '10000'
BARE_OS_SPLIT_MAX_FILES: '10000',
/** Comma-separated `ctx.bareOsSyscall` op names implemented in stock booter. */
BARE_OS_SYSCALL_OPS:
'readFile,writeFile,readdir,mkdir,stat,unlink,chmod,chdir,getcwd,readlink,symlink,exists,lstat,rmdir,mount,umount,kill',
/** Encodings accepted by `/bin/iconv` (subset). */
BARE_OS_ICONV_ENCODINGS: 'UTF-8',
/** Synthetic process table JSON path (logical VFS). */
BARE_OS_PROC_PROCESS_TABLE: '/proc/bare_os/process_table.json'
}
async function run(ctx, argv) {
+74
View File
@@ -0,0 +1,74 @@
/**
* Minimal iconv(1): UTF-8 pass-through only (Bare userland text is UTF-8).
*/
function normEnc(s) {
const t = String(s || '')
.trim()
.toLowerCase()
.replace(/[-_]/g, '')
if (t === 'utf8' || t === 'utf8bom') return 'utf-8'
return t
}
async function run(ctx, argv) {
let fromEnc = 'utf-8'
let toEnc = 'utf-8'
let file = null
for (let i = 1; i < argv.length; i++) {
const a = argv[i]
if (a === '--') continue
if (a === '-f' || a === '--from-code') {
i++
if (i >= argv.length) {
ctx.console.error('iconv: option requires an argument')
ctx.exitCode = 1
return
}
fromEnc = normEnc(argv[i])
continue
}
if (a === '-t' || a === '--to-code') {
i++
if (i >= argv.length) {
ctx.console.error('iconv: option requires an argument')
ctx.exitCode = 1
return
}
toEnc = normEnc(argv[i])
continue
}
if (a.startsWith('-')) {
ctx.console.error('iconv: unsupported option: ' + a)
ctx.exitCode = 1
return
}
file = a
break
}
if (fromEnc !== 'utf-8' || toEnc !== 'utf-8') {
ctx.console.error(
'iconv: only UTF-8 to UTF-8 is supported in this build (see getconf BARE_OS_ICONV_ENCODINGS)'
)
ctx.exitCode = 1
return
}
let u8
if (file) {
const raw = await ctx.vfs.readFile(file)
if (raw == null) {
ctx.console.error('iconv: ' + file + ': cannot read')
ctx.exitCode = 1
return
}
u8 = raw instanceof Uint8Array ? raw : ctx.b4a.from(raw)
} else {
u8 = ctx.b4a.from(bareStdin(ctx), 'utf8')
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') w.call(globalThis.process.stdout, u8)
else ctx.console.log(ctx.b4a.toString(u8, 'utf8'))
}
+57
View File
@@ -0,0 +1,57 @@
async function run(ctx, argv) {
const args = argv.slice(1)
let signal = 'TERM'
/** @type {string[]} */
const targets = []
for (let i = 0; i < args.length; i++) {
const a = args[i]
if (a === '--') {
for (let j = i + 1; j < args.length; j++) targets.push(args[j])
break
}
if (a === '-l' || a === '--list') {
ctx.console.log('HUP INT KILL TERM 0')
return
}
if (a === '-s' || a === '--signal') {
const n = args[i + 1]
if (!n) {
ctx.console.error('kill: option requires an argument: ' + a)
ctx.exitCode = 1
return
}
signal = n
i++
continue
}
if (/^-[A-Za-z0-9]+$/.test(a) && a !== '-') {
signal = a.slice(1)
continue
}
targets.push(a)
}
if (targets.length === 0) {
ctx.console.error('usage: kill [-s SIGNAL | -SIGNAL] <pid|name>...')
ctx.exitCode = 1
return
}
const send = ctx.bareOsSendSignal
if (typeof send !== 'function') {
ctx.console.error('kill: signal API unavailable in this runtime')
ctx.exitCode = 1
return
}
let failed = 0
for (const t of targets) {
try {
send.call(ctx, t, signal)
} catch (e) {
failed++
ctx.console.error('kill: ' + t + ': ' + (e?.message || String(e)))
}
}
if (failed > 0) ctx.exitCode = 1
}
+69
View File
@@ -0,0 +1,69 @@
function parseLoggerArgv(argv) {
let tag = 'logger'
let prio = 'user.notice'
let i = 1
const msg = []
while (i < argv.length) {
const a = argv[i]
if (a === '--') {
msg.push(...argv.slice(i + 1))
break
}
if (a === '-t') {
i++
if (i >= argv.length) return { err: 'logger: option requires an argument -- t' }
tag = String(argv[i] || '').trim().slice(0, 64) || 'logger'
i++
continue
}
if (a === '-p') {
i++
if (i >= argv.length) return { err: 'logger: option requires an argument -- p' }
prio = String(argv[i] || '').trim().slice(0, 64) || 'user.notice'
i++
continue
}
if (a.startsWith('-')) return { err: 'logger: unsupported option ' + a }
msg.push(...argv.slice(i))
break
}
return { tag, prio, text: msg.join(' ') }
}
async function run(ctx, argv) {
const p = parseLoggerArgv(argv)
if (p.err) {
ctx.console.error(p.err)
ctx.exitCode = 1
return
}
let text = String(p.text || '')
if (!text) text = bareStdin(ctx).trim()
if (!text) {
ctx.console.error('logger: empty message')
ctx.exitCode = 1
return
}
const rec = {
ts: new Date().toISOString(),
tag: p.tag,
priority: p.prio,
message: text
}
const line = JSON.stringify(rec) + '\n'
const maxRaw = Number.parseInt(String(ctx.vfs?.env?.BARE_OS_LOGGER_MAX_BYTES || '1048576'), 10)
const maxBytes = Number.isFinite(maxRaw) && maxRaw > 1024 ? Math.min(maxRaw, 8 * 1024 * 1024) : 1048576
let prev = ctx.b4a.from('')
try {
const cur = await ctx.vfs.readFile('/var/log/messages')
if (cur) prev = cur instanceof Uint8Array ? cur : ctx.b4a.from(cur)
} catch {
/* new file */
}
let next = ctx.b4a.concat([prev, ctx.b4a.from(line, 'utf8')])
if (next.byteLength > maxBytes) {
next = next.subarray(next.byteLength - maxBytes)
}
await ctx.vfs.writeFile('/var/log/messages', next)
}
+116
View File
@@ -0,0 +1,116 @@
async function printMountTable(ctx) {
try {
const buf = await ctx.vfs.readFile('/proc/mounts')
if (buf) {
const s = ctx.b4a.toString(buf)
if (s) {
ctx.console.log(s.endsWith('\n') ? s.slice(0, -1) : s)
return
}
}
} catch {
/* fall through */
}
ctx.console.log('(no mounts)')
}
async function run(ctx, argv) {
const args = argv.slice(1)
if (!args.length) {
await printMountTable(ctx)
return
}
let type = 'hyperdrive'
let opts = ''
/** @type {string[]} */
const pos = []
for (let i = 0; i < args.length; i++) {
const a = args[i]
if (a === '-t') {
i++
if (i >= args.length) {
ctx.console.error('mount: option requires an argument -- t')
ctx.exitCode = 1
return
}
type = String(args[i] || '')
continue
}
if (a === '-o') {
i++
if (i >= args.length) {
ctx.console.error('mount: option requires an argument -- o')
ctx.exitCode = 1
return
}
opts = String(args[i] || '')
continue
}
if (a.startsWith('-')) {
ctx.console.error('mount: unsupported option ' + a)
ctx.exitCode = 1
return
}
pos.push(a)
}
if (type !== 'hyperdrive') {
ctx.console.error('mount: only -t hyperdrive is supported')
ctx.exitCode = 1
return
}
if (pos.length < 2) {
ctx.console.error(
'usage: mount [-t hyperdrive] [-o ro] <source-key|local> </mnt/label>'
)
ctx.exitCode = 1
return
}
const source = String(pos[0] || '').trim()
const target = String(pos[1] || '').trim()
const m = /^\/mnt\/([a-zA-Z0-9][a-zA-Z0-9._-]{0,62})$/.exec(target)
if (!m) {
ctx.console.error('mount: target must be /mnt/<label>')
ctx.exitCode = 1
return
}
const label = m[1]
const ro = opts
.split(',')
.map((x) => x.trim())
.filter(Boolean)
.includes('ro')
if (typeof ctx.bareOsSyscall === 'function') {
try {
const out = await ctx.bareOsSyscall('mount', {
source,
target,
label,
readOnly: ro
})
if (out && typeof out === 'object' && out.note) {
ctx.console.log(String(out.note))
}
return
} catch (e) {
ctx.console.error('mount: ' + (e?.message || String(e)))
ctx.exitCode = 1
return
}
}
if (typeof ctx.runHdms !== 'function') {
ctx.console.error('mount: hdms bridge unavailable')
ctx.exitCode = 1
return
}
try {
if (source === 'local') await ctx.runHdms(['hdms', 'create', label])
else await ctx.runHdms(['hdms', 'add', label, source])
} catch (e) {
ctx.console.error('mount: ' + (e?.message || String(e)))
ctx.exitCode = 1
}
}
@@ -0,0 +1,35 @@
async function run(ctx, argv) {
void argv
let buf = null
try {
buf = await ctx.vfs.readFile('/proc/bare_os/process_table.json')
} catch {
buf = null
}
if (!buf) {
ctx.console.error('procstat: process table unavailable')
ctx.exitCode = 1
return
}
let table
try {
table = JSON.parse(ctx.b4a.toString(buf))
} catch {
ctx.console.error('procstat: invalid process table payload')
ctx.exitCode = 1
return
}
const rows = Array.isArray(table.processes) ? table.processes : []
ctx.console.log(
JSON.stringify(
{
schema: 1,
atMs: Date.now(),
processCount: rows.length,
processes: rows
},
null,
2
)
)
}
+34
View File
@@ -0,0 +1,34 @@
function pad(v, n) {
const s = String(v == null ? '' : v)
return s.length >= n ? s : s + ' '.repeat(n - s.length)
}
async function run(ctx, argv) {
void argv
const read = ctx.bareOsReadProcessTable
if (typeof read !== 'function') {
ctx.console.error('ps: process table API unavailable in this runtime')
ctx.exitCode = 1
return
}
let snap
try {
snap = read.call(ctx) || {}
} catch (e) {
ctx.console.error('ps: unable to read process table: ' + (e?.message || e))
ctx.exitCode = 1
return
}
const rows = Array.isArray(snap.processes) ? snap.processes : []
ctx.console.log(
`${pad('PID', 6)}${pad('PPID', 6)}${pad('STATE', 10)}${pad('NAME', 18)}SID`
)
for (const r of rows) {
ctx.console.log(
`${pad(r.pid ?? '', 6)}${pad(r.ppid ?? '', 6)}${pad(
r.state ?? '',
10
)}${pad(r.name ?? '', 18)}${r.sessionId || ''}`
)
}
}
+28
View File
@@ -0,0 +1,28 @@
/**
* Subset of ulimit(1): reports soft limits aligned with getconf(1) caps (no host rlimit).
*/
const LIMITS = [
['open files', '-n', '256'],
['max user processes', '-u', '32'],
['pipe size', '-p', '512'],
['file size', '-f', 'unlimited'],
['cpu time', '-t', 'unlimited'],
['virtual memory', '-v', 'unlimited']
]
async function run(ctx, argv) {
const args = argv.slice(1).filter((a) => a !== '--')
if (args.length === 0 || (args.length === 1 && args[0] === '-a')) {
for (const [label, flag, val] of LIMITS) {
ctx.console.log(`${label} (${flag}) ${val}`)
}
return
}
if (args.length === 1 && args[0] === '-n') {
ctx.console.log('256')
return
}
ctx.console.error('ulimit: only -a and -n are supported in Bare OS')
ctx.exitCode = 1
}
+39
View File
@@ -0,0 +1,39 @@
async function run(ctx, argv) {
const args = argv.slice(1).filter((a) => a !== '--')
if (!args.length) {
ctx.console.error('usage: umount </mnt/label>')
ctx.exitCode = 1
return
}
const target = String(args[0] || '').trim()
const m = /^\/mnt\/([a-zA-Z0-9][a-zA-Z0-9._-]{0,62})$/.exec(target)
if (!m) {
ctx.console.error('umount: target must be /mnt/<label>')
ctx.exitCode = 1
return
}
const label = m[1]
if (typeof ctx.bareOsSyscall === 'function') {
try {
await ctx.bareOsSyscall('umount', { target, label })
return
} catch (e) {
ctx.console.error('umount: ' + (e?.message || String(e)))
ctx.exitCode = 1
return
}
}
if (typeof ctx.runHdms !== 'function') {
ctx.console.error('umount: hdms bridge unavailable')
ctx.exitCode = 1
return
}
try {
await ctx.runHdms(['hdms', 'remove', label])
} catch (e) {
ctx.console.error('umount: ' + (e?.message || String(e)))
ctx.exitCode = 1
}
}