Fixed dd /dev/zero truncation in packages/bare-os-coreutils/src/dd.js

if=/dev/zero with finite count*bs now produces requested size directly (not limited by VFS 64KiB pseudo-file).
Raw stdout now uses bareOsEmitRaw, improving pipeline correctness.
Fixed pipeline capture path for FIFO-relevant commands

echo and cat now emit via bareOsEmitRaw (packages/bare-os-coreutils/src/echo.js, packages/bare-os-coreutils/src/cat.js).
Shell pipeline now injects bareOsBinWrite into child contexts when output is captured, so raw writes are captured (packages/bare-os-booter/lib/shell.js).
Improved xattr behavior in packages/bare-os-coreutils/src/xattr.js

Added -p/--print NAME.
-w now logs written attribute name for visible success feedback.
Roundtrip behaviors preserved with sidecar metadata format.
Improved ACL UX in:

packages/bare-os-coreutils/src/getfacl.js
Explicit source header for sidecar and mode fallback (non-compact mode).
packages/bare-os-coreutils/src/setfacl.js
Added -m/--modify ENTRY in addition to stdin blob and -b.
Implemented dynamic ulimit output in packages/bare-os-coreutils/src/ulimit.js

Reads /proc/bare_os/rlimits.json when available.
Supports -a, -n, -Sn, -Hn.
Added shuf -e support in packages/bare-os-coreutils/src/shuf.js.

Added split byte-suffix parsing (k/K/m/M/g/G) and attached -bSIZE support in packages/bare-os-coreutils/src/split.js.
This commit is contained in:
Raven Scott
2026-04-27 08:15:50 -04:00
parent b795397261
commit beaff551cf
36 changed files with 773 additions and 167 deletions
+26
View File
@@ -1839,6 +1839,19 @@ function bareOsPipelineChildCtx(ctx, env, stdinText, bareOsStdoutCaptured) {
return o
}
/**
* Convert raw binary chunks into shell pipeline capture text.
* Pipeline transport is text-based, so bytes are mapped 1:1 via char codes.
* @param {string | Uint8Array} chunk
*/
function bareOsPipelineRawChunkToText(chunk) {
if (typeof chunk === 'string') return chunk
if (!(chunk instanceof Uint8Array)) return String(chunk)
let s = ''
for (let i = 0; i < chunk.length; i++) s += String.fromCharCode(chunk[i])
return s
}
/**
* Optional timeout guard for potentially stalled pipeline stages.
* @param {Record<string, string>} env
@@ -2120,6 +2133,16 @@ async function execParsedPipeline(ctx, pipeline) {
}
checkCaptureSize(outChunks)
}
/** @param {string | Uint8Array} chunk */
const pushOutRaw = (chunk) => {
const text = bareOsPipelineRawChunkToText(chunk)
outChunks.push(text)
const st = ctx.bareOsSessionStats
if (st && typeof st.pipelineBytesTotal === 'number') {
st.pipelineBytesTotal += text.length
}
checkCaptureSize(outChunks)
}
const pushErr = (...args) => {
const line = args.map(String).join(' ') + '\n'
errChunks.push(line)
@@ -2489,6 +2512,7 @@ async function execParsedPipeline(ctx, pipeline) {
}
} else {
const childCtx = bareOsPipelineChildCtx(ctx, env, stdinText, capOut)
if (capOut) childCtx.bareOsBinWrite = pushOutRaw
await runWithShellPipelineStageTimeout(
env,
() => runBinCommand(childCtx, cargs),
@@ -2793,6 +2817,7 @@ async function execParsedPipeline(ctx, pipeline) {
}
if (testArgv) {
const childCtx = bareOsPipelineChildCtx(ctx, env, stdinText, capOut)
if (capOut) childCtx.bareOsBinWrite = pushOutRaw
try {
await runWithShellPipelineStageTimeout(
env,
@@ -2824,6 +2849,7 @@ async function execParsedPipeline(ctx, pipeline) {
}
} else {
const childCtx = bareOsPipelineChildCtx(ctx, env, stdinText, capOut)
if (capOut) childCtx.bareOsBinWrite = pushOutRaw
try {
await runWithShellPipelineStageTimeout(
env,
+2 -6
View File
@@ -1,10 +1,6 @@
function bareCatOut(ctx, s) {
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, s)
} else {
ctx.console.log(s)
}
if (bareOsEmitRaw(ctx, s)) return
ctx.console.log(s)
}
/** @param {string} line @param {{ showTabs: boolean, showEnds: boolean, showNonprinting: boolean }} o */
+33 -16
View File
@@ -111,32 +111,49 @@ async function run(ctx, argv) {
}
}
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')
const finiteCount = opt.count == null ? null : opt.count
const wantedBytes =
finiteCount == null ? null : Math.max(0, finiteCount * opt.bs)
const skipBytes = Math.max(0, opt.skip * opt.bs)
let chunk
if (opt.if === '/dev/zero' && wantedBytes != null) {
chunk = ctx.b4a.alloc(wantedBytes)
} else if (opt.if === '/dev/urandom' && wantedBytes != null) {
const raw = await ctx.vfs.readFile('/dev/urandom')
if (!raw) {
ctx.console.error('dd: /dev/urandom: unavailable')
ctx.exitCode = 1
return
}
const seed = raw instanceof Uint8Array ? raw : ctx.b4a.from(raw)
chunk = ctx.b4a.alloc(wantedBytes)
for (let i = 0; i < wantedBytes; i++) chunk[i] = seed[i % seed.length]
} else {
src = ctx.b4a.from(bareStdin(ctx), 'utf8')
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(skipBytes, input.byteLength)
const end =
wantedBytes == null
? input.byteLength
: Math.min(input.byteLength, start + wantedBytes)
chunk = input.subarray(start, end)
}
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)
let chunk = input.subarray(start, end)
if (opt.conv.includes('sync')) {
chunk = padToBlock(chunk, opt.bs, ctx.b4a)
}
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 (!bareOsEmitRaw(ctx, chunk)) ctx.console.log(ctx.b4a.toString(chunk))
if (opt.status !== 'none') {
ctx.console.error(`${chunk.byteLength} bytes copied`)
}
+3 -6
View File
@@ -6,10 +6,7 @@ async function run(ctx, argv) {
parts.shift()
}
const s = parts.join(' ')
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, s + (n ? '' : '\n'))
} else {
ctx.console.log(n ? s : s)
}
const out = s + (n ? '' : '\n')
if (bareOsEmitRaw(ctx, out)) return
ctx.console.log(s)
}
@@ -54,6 +54,7 @@ async function run(ctx, argv) {
const raw = await ctx.vfs.readFile(side)
if (raw && raw.byteLength) {
const text = ctx.b4a.toString(raw)
if (!compact) ctx.console.log('# source: ' + side)
ctx.console.log(text.replace(/\n$/, ''))
return
}
@@ -67,6 +68,7 @@ async function run(ctx, argv) {
'# file: ' + path,
'# owner: synthetic',
'# group: synthetic',
'# source: mode-fallback',
'user::' + u,
'group::' + g,
'other::' + o
+50 -8
View File
@@ -6,6 +6,8 @@
async function run(ctx, argv) {
let clear = false
/** @type {string[]} */
const modify = []
/** @type {string[]} */
const files = []
for (let i = 1; i < argv.length; i++) {
const a = argv[i]
@@ -13,9 +15,20 @@ async function run(ctx, argv) {
clear = true
continue
}
if (a === '-m' || a === '--modify') {
const spec = argv[i + 1]
if (!spec) {
ctx.console.error('setfacl: -m requires ACL entry')
ctx.exitCode = 1
return
}
modify.push(spec)
i++
continue
}
if (a === '--help' || a === '-h') {
ctx.console.log(
'usage: setfacl [-b] PATH\nWrites ACL text from stdin when not clearing.'
'usage: setfacl [-b] [-m ENTRY] PATH\nWrites ACL text from stdin when not clearing.'
)
return
}
@@ -48,14 +61,43 @@ async function run(ctx, argv) {
}
return
}
const text = bareStdin(ctx)
if (!text || !String(text).trim()) {
ctx.console.error('setfacl: ACL text required on stdin')
ctx.exitCode = 1
return
let body = ''
if (modify.length) {
const prev = await ctx.vfs.readFile(side)
const lines = prev
? ctx.b4a
.toString(prev)
.split(/\r?\n/)
.map((x) => x.trim())
.filter(Boolean)
: []
const byKey = new Map()
for (const ln of lines) {
const k = ln.split(':', 2).join(':')
byKey.set(k, ln)
}
for (const m of modify) {
const spec = String(m).trim()
if (!spec.includes(':')) {
ctx.console.error('setfacl: invalid ACL entry ' + spec)
ctx.exitCode = 1
return
}
const key = spec.split(':', 2).join(':')
byKey.set(key, spec)
}
body = [...byKey.values()].join('\n')
} else {
const text = bareStdin(ctx)
if (!text || !String(text).trim()) {
ctx.console.error('setfacl: ACL text required on stdin')
ctx.exitCode = 1
return
}
body = String(text)
}
const body = String(text).endsWith('\n') ? String(text) : String(text) + '\n'
await ctx.vfs.writeFile(side, ctx.b4a.from(body))
const finalBody = body.endsWith('\n') ? body : body + '\n'
await ctx.vfs.writeFile(side, ctx.b4a.from(finalBody))
} catch (e) {
ctx.console.error('setfacl: ' + (e && e.message ? e.message : String(e)))
ctx.exitCode = 1
+26 -10
View File
@@ -28,6 +28,7 @@ function seededRandom(seed) {
async function run(ctx, argv) {
const paths = []
let echoMode = false
for (let i = 1; i < argv.length; i++) {
const a = argv[i]
if (a === '-h' || a === '--help') {
@@ -36,6 +37,10 @@ async function run(ctx, argv) {
)
return
}
if (a === '-e') {
echoMode = true
continue
}
if (a.startsWith('-')) {
ctx.console.error('shuf: unsupported option ' + a)
ctx.exitCode = 1
@@ -50,21 +55,32 @@ async function run(ctx, argv) {
const seed = String(ctx.vfs?.env?.BARE_OS_SHUF_SEED || '').trim()
const rand = seed ? seededRandom(seed) : Math.random
const lines = []
for (const p of paths) {
const L = await readLines(ctx, p)
if (L == null) {
ctx.console.error('shuf: ' + p + ': No such file')
ctx.exitCode = 1
return
}
const trim = L.length && L[L.length - 1] === '' ? L.slice(0, -1) : L
for (const ln of trim) {
if (echoMode) {
for (const p of paths) {
if (lines.length >= cap) {
ctx.console.error('shuf: input exceeds line cap')
ctx.exitCode = 1
return
}
lines.push(ln)
lines.push(p)
}
} else {
for (const p of paths) {
const L = await readLines(ctx, p)
if (L == null) {
ctx.console.error('shuf: ' + p + ': No such file')
ctx.exitCode = 1
return
}
const trim = L.length && L[L.length - 1] === '' ? L.slice(0, -1) : L
for (const ln of trim) {
if (lines.length >= cap) {
ctx.console.error('shuf: input exceeds line cap')
ctx.exitCode = 1
return
}
lines.push(ln)
}
}
}
for (let i = lines.length - 1; i > 0; i--) {
+24 -1
View File
@@ -22,6 +22,23 @@ function splitSuffix(prefix, i) {
return prefix + s
}
function parseByteSize(v) {
const s = String(v || '').trim()
const m = /^(\d+)([kKmMgG]?)$/.exec(s)
if (!m) return null
const n = Number.parseInt(m[1], 10)
if (!Number.isFinite(n) || n < 1) return null
const mul =
m[2] === 'k' || m[2] === 'K'
? 1024
: m[2] === 'm' || m[2] === 'M'
? 1024 * 1024
: m[2] === 'g' || m[2] === 'G'
? 1024 * 1024 * 1024
: 1
return n * mul
}
async function run(ctx, argv) {
let lineCount = 1000
let byteCount = null
@@ -47,7 +64,13 @@ async function run(ctx, argv) {
continue
}
if (a === '-b' && argv[i + 1]) {
byteCount = parseInt(argv[++i], 10)
byteCount = parseByteSize(argv[++i])
lineCount = null
if (!Number.isFinite(byteCount) || byteCount < 1) byteCount = 512
continue
}
if (a.startsWith('-b') && a.length > 2) {
byteCount = parseByteSize(a.slice(2))
lineCount = null
if (!Number.isFinite(byteCount) || byteCount < 1) byteCount = 512
continue
+40 -4
View File
@@ -11,18 +11,54 @@ const LIMITS = [
['virtual memory', '-v', 'unlimited']
]
/**
* @param {Record<string, unknown>} ctx
*/
async function readRuntimeLimits(ctx) {
try {
const b = await ctx.vfs.readFile('/proc/bare_os/rlimits.json')
if (!b) return null
const j = JSON.parse(ctx.b4a.toString(b))
return j && typeof j === 'object' ? j : null
} catch {
return null
}
}
async function run(ctx, argv) {
const args = argv.slice(1).filter((a) => a !== '--')
const rt = await readRuntimeLimits(ctx)
const nofileSoft =
String(
rt?.softNoFile ??
rt?.soft_nofile ??
rt?.nofileSoft ??
rt?.nofile_soft ??
256
) || '256'
const nofileHard =
String(
rt?.hardNoFile ??
rt?.hard_nofile ??
rt?.nofileHard ??
rt?.nofile_hard ??
nofileSoft
) || nofileSoft
if (args.length === 0 || (args.length === 1 && args[0] === '-a')) {
for (const [label, flag, val] of LIMITS) {
ctx.console.log(`${label} (${flag}) ${val}`)
const outVal = flag === '-n' ? nofileSoft : val
ctx.console.log(`${label} (${flag}) ${outVal}`)
}
return
}
if (args.length === 1 && args[0] === '-n') {
ctx.console.log('256')
if (args.length === 1 && (args[0] === '-n' || args[0] === '-Sn')) {
ctx.console.log(nofileSoft)
return
}
ctx.console.error('ulimit: only -a and -n are supported in Bare OS')
if (args.length === 1 && args[0] === '-Hn') {
ctx.console.log(nofileHard)
return
}
ctx.console.error('ulimit: only -a, -n, -Sn, and -Hn are supported in Bare OS')
ctx.exitCode = 1
}
+25 -2
View File
@@ -68,6 +68,8 @@ async function run(ctx, argv) {
let delName = null
/** @type {{ name: string, val: string } | null} */
let writePair = null
/** @type {string | null} */
let printName = null
/** @type {string[]} */
const rest = []
for (let i = 1; i < argv.length; i++) {
@@ -88,6 +90,17 @@ async function run(ctx, argv) {
i += 2
continue
}
if (a === '-p' || a === '--print') {
const name = argv[i + 1]
if (!name) {
ctx.console.error('xattr: -p requires NAME')
ctx.exitCode = 1
return
}
printName = name
i++
continue
}
if (a === '-d' || a === '--delete') {
const name = argv[i + 1]
if (!name) {
@@ -100,7 +113,7 @@ async function run(ctx, argv) {
continue
}
if (a === '--help' || a === '-h') {
ctx.console.log('usage: xattr [-l] [-w NAME VALUE] [-d NAME] PATH')
ctx.console.log('usage: xattr [-l] [-p NAME] [-w NAME VALUE] [-d NAME] PATH')
return
}
if (a.startsWith('-')) {
@@ -111,7 +124,7 @@ async function run(ctx, argv) {
rest.push(a)
}
if (rest.length !== 1) {
ctx.console.error('usage: xattr [-l] [-w NAME VALUE] [-d NAME] PATH')
ctx.console.error('usage: xattr [-l] [-p NAME] [-w NAME VALUE] [-d NAME] PATH')
ctx.exitCode = 1
return
}
@@ -132,6 +145,16 @@ async function run(ctx, argv) {
if (writePair) {
obj[writePair.name] = xattrB64Encode(writePair.val)
await writeXattrMap(ctx, path, obj)
ctx.console.log(writePair.name)
return
}
if (printName) {
if (!Object.prototype.hasOwnProperty.call(obj, printName)) {
ctx.console.error('xattr: ' + printName + ': No such xattr')
ctx.exitCode = 1
return
}
ctx.console.log(xattrB64Decode(obj[printName]))
return
}
const keys = Object.keys(obj).sort()
@@ -0,0 +1,111 @@
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`
)
}
test('dd honors large /dev/zero count*bs output', async (t) => {
const run = await loadBin('dd')
/** @type {number[]} */
const rawChunks = []
const ctx = {
b4a,
exitCode: 0,
shellStdin: '',
bareOsBinWrite: (u8) => rawChunks.push(u8.byteLength),
console: { log: () => {}, error: () => {} },
vfs: {
async readFile(p) {
if (p === '/dev/zero') return new Uint8Array(65536)
return null
},
async writeFile() {}
}
}
await run(ctx, ['dd', 'if=/dev/zero', 'bs=1M', 'count=2', 'status=none'])
t.is(ctx.exitCode, 0)
t.is(rawChunks.reduce((a, b) => a + b, 0), 2 * 1024 * 1024)
})
test('shuf -e shuffles operands directly', async (t) => {
const run = await loadBin('shuf')
/** @type {string[]} */
const logs = []
const ctx = {
b4a,
exitCode: 0,
shellStdin: '',
console: { log: (m) => logs.push(String(m)), error: () => {} },
vfs: {
env: { BARE_OS_SHUF_SEED: 'seed' },
async readFile() {
return null
}
}
}
await run(ctx, ['shuf', '-e', 'a', 'b', 'c'])
t.is(ctx.exitCode, 0)
t.is(logs.length, 3)
t.alike([...logs].sort(), ['a', 'b', 'c'])
})
test('split accepts -b1k suffix', async (t) => {
const run = await loadBin('split')
/** @type {Record<string, Uint8Array>} */
const out = {}
const input = b4a.alloc(2050, 120)
const ctx = {
b4a,
exitCode: 0,
shellStdin: '',
console: { log: () => {}, error: () => {} },
vfs: {
env: {},
async readFile(p) {
return p === '/in' ? input : out[p] || null
},
async writeFile(p, body) {
out[p] = body instanceof Uint8Array ? body : b4a.from(body)
}
}
}
await run(ctx, ['split', '-b1k', '/in', '/tmp/x'])
t.is(ctx.exitCode, 0)
t.is(out['/tmp/xaa'].byteLength, 1024)
t.is(out['/tmp/xab'].byteLength, 1024)
t.is(out['/tmp/xac'].byteLength, 2)
})
test('ulimit -Sn reads runtime soft nofile when present', async (t) => {
const run = await loadBin('ulimit')
/** @type {string[]} */
const logs = []
const ctx = {
b4a,
exitCode: 0,
shellStdin: '',
console: { log: (m) => logs.push(String(m)), error: () => {} },
vfs: {
async readFile(p) {
if (p !== '/proc/bare_os/rlimits.json') return null
return b4a.from(JSON.stringify({ soft_nofile: 4096, hard_nofile: 8192 }))
}
}
}
await run(ctx, ['ulimit', '-Sn'])
t.is(ctx.exitCode, 0)
t.is(logs[0], '4096')
})
@@ -93,7 +93,7 @@ test('getfacl synthesizes mode triples and reads sidecar', async (t) => {
const ctxB = createCtx(store)
await runB(ctxB, ['getfacl', '/b'])
t.is(ctxB.exitCode, 0)
t.ok(ctxB._logs[0].includes('user::rw-'))
t.ok(ctxB._logs.join('\n').includes('user::rw-'))
})
test('getfacl documents mask-style sidecars for VFS enforcement', async (t) => {
@@ -116,7 +116,7 @@ test('getfacl documents mask-style sidecars for VFS enforcement', async (t) => {
t.ok(ctx._logs.join('\n').includes('other::---'))
})
test('setfacl -b and stdin write', async (t) => {
test('setfacl -b, -m and stdin write', async (t) => {
const store = {
files: {
'/x': {
@@ -141,6 +141,12 @@ test('setfacl -b and stdin write', async (t) => {
await runSet(ctx2, ['setfacl', '/x'])
t.is(ctx2.exitCode, 0)
t.is(b4a.toString(store.files['/x.bare_acl'].body), 'user::r--\n')
const runMod = await loadBin('setfacl')
const ctx3 = createCtx(store)
await runMod(ctx3, ['setfacl', '-m', 'group::r-x', '/x'])
t.is(ctx3.exitCode, 0)
t.ok(b4a.toString(store.files['/x.bare_acl'].body).includes('group::r-x'))
})
test('xattr -w -l -d round trip', async (t) => {
@@ -156,6 +162,7 @@ test('xattr -w -l -d round trip', async (t) => {
const ctxW = createCtx(store)
await runW(ctxW, ['xattr', '-w', 'user.t', 'hi', '/f'])
t.is(ctxW.exitCode, 0)
t.is(ctxW._logs.join('\n'), 'user.t')
const runL = await loadBin('xattr')
const ctxL = createCtx(store)
@@ -163,6 +170,12 @@ test('xattr -w -l -d round trip', async (t) => {
t.is(ctxL.exitCode, 0)
t.is(ctxL._logs.join('\n'), 'user.t: hi')
const runP = await loadBin('xattr')
const ctxP = createCtx(store)
await runP(ctxP, ['xattr', '-p', 'user.t', '/f'])
t.is(ctxP.exitCode, 0)
t.is(ctxP._logs.join('\n'), 'hi')
const runD = await loadBin('xattr')
const ctxD = createCtx(store)
await runD(ctxD, ['xattr', '-d', 'user.t', '/f'])
+2 -6
View File
@@ -88,12 +88,8 @@ function bareOsEmitRaw(ctx, chunk) {
}
function bareCatOut(ctx, s) {
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, s)
} else {
ctx.console.log(s)
}
if (bareOsEmitRaw(ctx, s)) return
ctx.console.log(s)
}
/** @param {string} line @param {{ showTabs: boolean, showEnds: boolean, showNonprinting: boolean }} o */
+33 -16
View File
@@ -200,32 +200,49 @@ async function run(ctx, argv) {
}
}
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')
const finiteCount = opt.count == null ? null : opt.count
const wantedBytes =
finiteCount == null ? null : Math.max(0, finiteCount * opt.bs)
const skipBytes = Math.max(0, opt.skip * opt.bs)
let chunk
if (opt.if === '/dev/zero' && wantedBytes != null) {
chunk = ctx.b4a.alloc(wantedBytes)
} else if (opt.if === '/dev/urandom' && wantedBytes != null) {
const raw = await ctx.vfs.readFile('/dev/urandom')
if (!raw) {
ctx.console.error('dd: /dev/urandom: unavailable')
ctx.exitCode = 1
return
}
const seed = raw instanceof Uint8Array ? raw : ctx.b4a.from(raw)
chunk = ctx.b4a.alloc(wantedBytes)
for (let i = 0; i < wantedBytes; i++) chunk[i] = seed[i % seed.length]
} else {
src = ctx.b4a.from(bareStdin(ctx), 'utf8')
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(skipBytes, input.byteLength)
const end =
wantedBytes == null
? input.byteLength
: Math.min(input.byteLength, start + wantedBytes)
chunk = input.subarray(start, end)
}
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)
let chunk = input.subarray(start, end)
if (opt.conv.includes('sync')) {
chunk = padToBlock(chunk, opt.bs, ctx.b4a)
}
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 (!bareOsEmitRaw(ctx, chunk)) ctx.console.log(ctx.b4a.toString(chunk))
if (opt.status !== 'none') {
ctx.console.error(`${chunk.byteLength} bytes copied`)
}
+3 -6
View File
@@ -95,10 +95,7 @@ async function run(ctx, argv) {
parts.shift()
}
const s = parts.join(' ')
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, s + (n ? '' : '\n'))
} else {
ctx.console.log(n ? s : s)
}
const out = s + (n ? '' : '\n')
if (bareOsEmitRaw(ctx, out)) return
ctx.console.log(s)
}
@@ -143,6 +143,7 @@ async function run(ctx, argv) {
const raw = await ctx.vfs.readFile(side)
if (raw && raw.byteLength) {
const text = ctx.b4a.toString(raw)
if (!compact) ctx.console.log('# source: ' + side)
ctx.console.log(text.replace(/\n$/, ''))
return
}
@@ -156,6 +157,7 @@ async function run(ctx, argv) {
'# file: ' + path,
'# owner: synthetic',
'# group: synthetic',
'# source: mode-fallback',
'user::' + u,
'group::' + g,
'other::' + o
+50 -8
View File
@@ -95,6 +95,8 @@ function bareOsEmitRaw(ctx, chunk) {
async function run(ctx, argv) {
let clear = false
/** @type {string[]} */
const modify = []
/** @type {string[]} */
const files = []
for (let i = 1; i < argv.length; i++) {
const a = argv[i]
@@ -102,9 +104,20 @@ async function run(ctx, argv) {
clear = true
continue
}
if (a === '-m' || a === '--modify') {
const spec = argv[i + 1]
if (!spec) {
ctx.console.error('setfacl: -m requires ACL entry')
ctx.exitCode = 1
return
}
modify.push(spec)
i++
continue
}
if (a === '--help' || a === '-h') {
ctx.console.log(
'usage: setfacl [-b] PATH\nWrites ACL text from stdin when not clearing.'
'usage: setfacl [-b] [-m ENTRY] PATH\nWrites ACL text from stdin when not clearing.'
)
return
}
@@ -137,14 +150,43 @@ async function run(ctx, argv) {
}
return
}
const text = bareStdin(ctx)
if (!text || !String(text).trim()) {
ctx.console.error('setfacl: ACL text required on stdin')
ctx.exitCode = 1
return
let body = ''
if (modify.length) {
const prev = await ctx.vfs.readFile(side)
const lines = prev
? ctx.b4a
.toString(prev)
.split(/\r?\n/)
.map((x) => x.trim())
.filter(Boolean)
: []
const byKey = new Map()
for (const ln of lines) {
const k = ln.split(':', 2).join(':')
byKey.set(k, ln)
}
for (const m of modify) {
const spec = String(m).trim()
if (!spec.includes(':')) {
ctx.console.error('setfacl: invalid ACL entry ' + spec)
ctx.exitCode = 1
return
}
const key = spec.split(':', 2).join(':')
byKey.set(key, spec)
}
body = [...byKey.values()].join('\n')
} else {
const text = bareStdin(ctx)
if (!text || !String(text).trim()) {
ctx.console.error('setfacl: ACL text required on stdin')
ctx.exitCode = 1
return
}
body = String(text)
}
const body = String(text).endsWith('\n') ? String(text) : String(text) + '\n'
await ctx.vfs.writeFile(side, ctx.b4a.from(body))
const finalBody = body.endsWith('\n') ? body : body + '\n'
await ctx.vfs.writeFile(side, ctx.b4a.from(finalBody))
} catch (e) {
ctx.console.error('setfacl: ' + (e && e.message ? e.message : String(e)))
ctx.exitCode = 1
+26 -10
View File
@@ -117,6 +117,7 @@ function seededRandom(seed) {
async function run(ctx, argv) {
const paths = []
let echoMode = false
for (let i = 1; i < argv.length; i++) {
const a = argv[i]
if (a === '-h' || a === '--help') {
@@ -125,6 +126,10 @@ async function run(ctx, argv) {
)
return
}
if (a === '-e') {
echoMode = true
continue
}
if (a.startsWith('-')) {
ctx.console.error('shuf: unsupported option ' + a)
ctx.exitCode = 1
@@ -139,21 +144,32 @@ async function run(ctx, argv) {
const seed = String(ctx.vfs?.env?.BARE_OS_SHUF_SEED || '').trim()
const rand = seed ? seededRandom(seed) : Math.random
const lines = []
for (const p of paths) {
const L = await readLines(ctx, p)
if (L == null) {
ctx.console.error('shuf: ' + p + ': No such file')
ctx.exitCode = 1
return
}
const trim = L.length && L[L.length - 1] === '' ? L.slice(0, -1) : L
for (const ln of trim) {
if (echoMode) {
for (const p of paths) {
if (lines.length >= cap) {
ctx.console.error('shuf: input exceeds line cap')
ctx.exitCode = 1
return
}
lines.push(ln)
lines.push(p)
}
} else {
for (const p of paths) {
const L = await readLines(ctx, p)
if (L == null) {
ctx.console.error('shuf: ' + p + ': No such file')
ctx.exitCode = 1
return
}
const trim = L.length && L[L.length - 1] === '' ? L.slice(0, -1) : L
for (const ln of trim) {
if (lines.length >= cap) {
ctx.console.error('shuf: input exceeds line cap')
ctx.exitCode = 1
return
}
lines.push(ln)
}
}
}
for (let i = lines.length - 1; i > 0; i--) {
+24 -1
View File
@@ -111,6 +111,23 @@ function splitSuffix(prefix, i) {
return prefix + s
}
function parseByteSize(v) {
const s = String(v || '').trim()
const m = /^(\d+)([kKmMgG]?)$/.exec(s)
if (!m) return null
const n = Number.parseInt(m[1], 10)
if (!Number.isFinite(n) || n < 1) return null
const mul =
m[2] === 'k' || m[2] === 'K'
? 1024
: m[2] === 'm' || m[2] === 'M'
? 1024 * 1024
: m[2] === 'g' || m[2] === 'G'
? 1024 * 1024 * 1024
: 1
return n * mul
}
async function run(ctx, argv) {
let lineCount = 1000
let byteCount = null
@@ -136,7 +153,13 @@ async function run(ctx, argv) {
continue
}
if (a === '-b' && argv[i + 1]) {
byteCount = parseInt(argv[++i], 10)
byteCount = parseByteSize(argv[++i])
lineCount = null
if (!Number.isFinite(byteCount) || byteCount < 1) byteCount = 512
continue
}
if (a.startsWith('-b') && a.length > 2) {
byteCount = parseByteSize(a.slice(2))
lineCount = null
if (!Number.isFinite(byteCount) || byteCount < 1) byteCount = 512
continue
+40 -4
View File
@@ -100,18 +100,54 @@ const LIMITS = [
['virtual memory', '-v', 'unlimited']
]
/**
* @param {Record<string, unknown>} ctx
*/
async function readRuntimeLimits(ctx) {
try {
const b = await ctx.vfs.readFile('/proc/bare_os/rlimits.json')
if (!b) return null
const j = JSON.parse(ctx.b4a.toString(b))
return j && typeof j === 'object' ? j : null
} catch {
return null
}
}
async function run(ctx, argv) {
const args = argv.slice(1).filter((a) => a !== '--')
const rt = await readRuntimeLimits(ctx)
const nofileSoft =
String(
rt?.softNoFile ??
rt?.soft_nofile ??
rt?.nofileSoft ??
rt?.nofile_soft ??
256
) || '256'
const nofileHard =
String(
rt?.hardNoFile ??
rt?.hard_nofile ??
rt?.nofileHard ??
rt?.nofile_hard ??
nofileSoft
) || nofileSoft
if (args.length === 0 || (args.length === 1 && args[0] === '-a')) {
for (const [label, flag, val] of LIMITS) {
ctx.console.log(`${label} (${flag}) ${val}`)
const outVal = flag === '-n' ? nofileSoft : val
ctx.console.log(`${label} (${flag}) ${outVal}`)
}
return
}
if (args.length === 1 && args[0] === '-n') {
ctx.console.log('256')
if (args.length === 1 && (args[0] === '-n' || args[0] === '-Sn')) {
ctx.console.log(nofileSoft)
return
}
ctx.console.error('ulimit: only -a and -n are supported in Bare OS')
if (args.length === 1 && args[0] === '-Hn') {
ctx.console.log(nofileHard)
return
}
ctx.console.error('ulimit: only -a, -n, -Sn, and -Hn are supported in Bare OS')
ctx.exitCode = 1
}
+25 -2
View File
@@ -157,6 +157,8 @@ async function run(ctx, argv) {
let delName = null
/** @type {{ name: string, val: string } | null} */
let writePair = null
/** @type {string | null} */
let printName = null
/** @type {string[]} */
const rest = []
for (let i = 1; i < argv.length; i++) {
@@ -177,6 +179,17 @@ async function run(ctx, argv) {
i += 2
continue
}
if (a === '-p' || a === '--print') {
const name = argv[i + 1]
if (!name) {
ctx.console.error('xattr: -p requires NAME')
ctx.exitCode = 1
return
}
printName = name
i++
continue
}
if (a === '-d' || a === '--delete') {
const name = argv[i + 1]
if (!name) {
@@ -189,7 +202,7 @@ async function run(ctx, argv) {
continue
}
if (a === '--help' || a === '-h') {
ctx.console.log('usage: xattr [-l] [-w NAME VALUE] [-d NAME] PATH')
ctx.console.log('usage: xattr [-l] [-p NAME] [-w NAME VALUE] [-d NAME] PATH')
return
}
if (a.startsWith('-')) {
@@ -200,7 +213,7 @@ async function run(ctx, argv) {
rest.push(a)
}
if (rest.length !== 1) {
ctx.console.error('usage: xattr [-l] [-w NAME VALUE] [-d NAME] PATH')
ctx.console.error('usage: xattr [-l] [-p NAME] [-w NAME VALUE] [-d NAME] PATH')
ctx.exitCode = 1
return
}
@@ -221,6 +234,16 @@ async function run(ctx, argv) {
if (writePair) {
obj[writePair.name] = xattrB64Encode(writePair.val)
await writeXattrMap(ctx, path, obj)
ctx.console.log(writePair.name)
return
}
if (printName) {
if (!Object.prototype.hasOwnProperty.call(obj, printName)) {
ctx.console.error('xattr: ' + printName + ': No such xattr')
ctx.exitCode = 1
return
}
ctx.console.log(xattrB64Decode(obj[printName]))
return
}
const keys = Object.keys(obj).sort()
@@ -1,7 +1,7 @@
{
"schema": 2,
"profileId": "bare-os-posix-like",
"generatedAt": "2026-04-27T12:05:37.921Z",
"generatedAt": "2026-04-27T12:14:28.388Z",
"note": "Sparse POSIX Issue 7 coverage hints for /bin utilities. Omitted command names are not yet profiled here.",
"commandIndex": [
{
@@ -1,6 +1,6 @@
{
"schema": 1,
"atMs": 1777291537920,
"atMs": 1777292068387,
"commands": [
"agent",
"appctl",
File diff suppressed because one or more lines are too long