Complete the internal “100 task” roadmap: coreutils and shell parity (xargs,

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.
This commit is contained in:
Raven Scott
2026-04-04 21:23:49 -04:00
parent c29ec13cf9
commit 346bb71ffe
177 changed files with 8087 additions and 876 deletions
+5 -4
View File
@@ -1,8 +1,9 @@
async function run(ctx, argv) {
const silent = argv.includes('-s') || argv.includes('--silent')
const args = argv.filter((a) => !a.startsWith('-'))
const a = args[0]
const b = args[1]
const rest = argv.slice(1)
const silent = rest.includes('-s') || rest.includes('--silent')
const paths = rest.filter((a) => !a.startsWith('-'))
const a = paths[0]
const b = paths[1]
if (!a || !b) {
ctx.console.error('usage: cmp [-s] file1 file2')
ctx.exitCode = 2
+111
View File
@@ -0,0 +1,111 @@
/**
* Minimal diff(1) for Bare OS: compares two text files (VFS paths).
* Supports -q (quiet, exit status only), -s (report when identical), -u (single-hunk unified).
*/
/**
* @param {string} a
* @param {string} b
*/
function splitLines(a) {
const s = a.replace(/\r\n/g, '\n')
const parts = s.split('\n')
if (parts.length && parts[parts.length - 1] === '') parts.pop()
return parts
}
/**
* @param {string} pathA
* @param {string} pathB
* @param {string} sa
* @param {string} sb
* @returns {string | null} unified hunk or null if identical
*/
function unifiedOneHunk(pathA, pathB, sa, sb) {
const la = splitLines(sa)
const lb = splitLines(sb)
let i = 0
const n = Math.min(la.length, lb.length)
while (i < n && la[i] === lb[i]) i++
if (i === la.length && i === lb.length) return null
const oldLine = la[i] != null ? la[i] : ''
const newLine = lb[i] != null ? lb[i] : ''
return (
`--- ${pathA}\n+++ ${pathB}\n@@ -${i + 1},1 +${i + 1},1 @@\n-${oldLine}\n+${newLine}\n`
)
}
async function run(ctx, argv) {
let quiet = false
let sameReport = false
let unified = false
/** @type {string[]} */
const files = []
for (let i = 1; i < argv.length; i++) {
const a = argv[i]
if (a === '--') {
files.push(...argv.slice(i + 1))
break
}
if (a === '-q') {
quiet = true
continue
}
if (a === '-s') {
sameReport = true
continue
}
if (a === '-u' || a === '-U0' || a === '--unified') {
unified = true
continue
}
if (a.startsWith('-')) {
ctx.console.error('diff: unsupported option ' + a)
ctx.exitCode = 2
return
}
files.push(a)
}
if (files.length !== 2) {
ctx.console.error('usage: diff [-q] [-s] [-u] FILE1 FILE2')
ctx.exitCode = 2
return
}
const [f1, f2] = files
let b1
let b2
try {
b1 = await ctx.vfs.readFile(f1)
b2 = await ctx.vfs.readFile(f2)
} catch (e) {
ctx.console.error('diff: ' + ((e && e.message) || String(e)))
ctx.exitCode = 2
return
}
const s1 = b1 ? ctx.b4a.toString(b1) : ''
const s2 = b2 ? ctx.b4a.toString(b2) : ''
if (s1 === s2) {
if (sameReport) ctx.console.log(`Files ${f1} and ${f2} are identical`)
ctx.exitCode = 0
return
}
if (quiet) {
ctx.exitCode = 1
return
}
if (unified) {
const u = unifiedOneHunk(f1, f2, s1, s2)
if (u) ctx.console.log(u.replace(/\n$/, ''))
} else {
const la = splitLines(s1)
const lb = splitLines(s2)
let i = 0
const n = Math.min(la.length, lb.length)
while (i < n && la[i] === lb[i]) i++
ctx.console.log(`${i + 1}c${i + 1}`)
ctx.console.log(`< ${la[i] != null ? la[i] : ''}`)
ctx.console.log('---')
ctx.console.log(`> ${lb[i] != null ? lb[i] : ''}`)
}
ctx.exitCode = 1
}
+75
View File
@@ -40,6 +40,57 @@ function findMatchMtime(st, spec) {
return days >= spec.n && days < spec.n + 1
}
/**
* Device id sketch for `find -xdev` (do not cross `/mnt/<label>` boundaries vs system/personal).
* @param {Record<string, unknown>} ctx
* @param {string} absLogical
*/
function findRouteDevId(ctx, absLogical) {
const a = String(absLogical).replace(/\/+$/, '') || '/'
if (a.startsWith('/mnt/')) {
const label = a.slice(5).split('/').filter(Boolean)[0]
return label ? `mnt:${label}` : 'mnt:'
}
if (a === '/mnt') return 'mnt-root'
const vfs = ctx.vfs
if (!vfs || typeof vfs.route !== 'function') return 'unknown'
const r = vfs.route(absLogical)
if (r.virtualPseudo) return 'pseudo'
if (r.virtualHomeDir) return 'personal-home'
if (r.virtualVarRoot) return 'personal-var'
if (a.startsWith('/tmp')) return 'personal'
if (a.startsWith('/home/')) return 'personal'
if (a.startsWith('/var/')) return 'personal'
return 'system'
}
/** @param {string} s */
function parsePermSpec(s) {
const t = String(s).trim()
if (t.startsWith('/')) return null
let body = t
let allBitsSet = false
if (t.startsWith('-')) {
allBitsSet = true
body = t.slice(1)
}
if (!/^[0-7]{1,4}$/.test(body)) return null
const mask = Number.parseInt(body, 8) & 0o7777
return { allBitsSet, mask }
}
/** @param {Record<string, unknown>} st @param {{ allBitsSet: boolean, mask: number }} spec */
function findMatchPerm(st, spec) {
const mode =
typeof st.mode === 'number'
? st.mode & 0o7777
: typeof st.mode === 'string'
? Number.parseInt(String(st.mode), 8) & 0o7777
: 0
if (spec.allBitsSet) return (mode & spec.mask) === spec.mask
return mode === spec.mask
}
async function findIsEmpty(ctx, path, st) {
if (st.type === 'file' || st.type === 'symlink') return (st.size || 0) === 0
if (st.type !== 'directory') return false
@@ -77,6 +128,7 @@ async function walk(ctx, dir, o, curDepth) {
const nameOk = !o.nameRe || o.nameRe.test(n)
let match = pathOk && nameOk && (!o.wantType || st.type === o.wantType)
if (match && o.mtimeSpec) match = match && findMatchMtime(st, o.mtimeSpec)
if (match && o.permSpec) match = match && findMatchPerm(st, o.permSpec)
if (match && o.newerThanMs != null)
match = match && st.mtimeMs > o.newerThanMs
if (match && o.wantEmpty) {
@@ -141,6 +193,10 @@ async function walk(ctx, dir, o, curDepth) {
}
}
if (st.type !== 'directory') continue
if (o.xdevRootDev != null) {
const subDev = findRouteDevId(ctx, path)
if (subDev !== o.xdevRootDev) continue
}
const absPath = ctx.vfs.resolveLogical(path)
if (o.pruneAbs && absPath === o.pruneAbs) continue
await walk(ctx, path, o, curDepth + 1)
@@ -160,6 +216,9 @@ async function run(ctx, argv) {
let print0 = false
/** @type {{ op: string, n: number } | null} */
let mtimeSpec = null
/** @type {{ allBitsSet: boolean, mask: number } | null} */
let permSpec = null
let xdev = false
/** @type {string | null} */
let newerPath = null
/** @type {string | null} */
@@ -217,6 +276,19 @@ async function run(ctx, argv) {
}
continue
}
if (a === '-perm' && argv[i + 1]) {
permSpec = parsePermSpec(argv[++i])
if (!permSpec) {
ctx.console.error('find: invalid -perm')
ctx.exitCode = 1
return
}
continue
}
if (a === '-xdev') {
xdev = true
continue
}
if (a === '-newer' && argv[i + 1]) {
newerPath = argv[++i]
continue
@@ -336,6 +408,7 @@ async function run(ctx, argv) {
if (Number.isFinite(n)) execMax = Math.min(4096, Math.max(1, n))
}
const abs = ctx.vfs.resolveLogical(root)
const xdevRootDev = xdev ? findRouteDevId(ctx, abs) : null
const o = {
maxDepth,
minDepth,
@@ -344,6 +417,8 @@ async function run(ctx, argv) {
wantType,
print0,
mtimeSpec,
permSpec,
xdevRootDev,
newerThanMs,
pruneAbs,
wantEmpty,
+40 -6
View File
@@ -1,6 +1,7 @@
/**
* Subset of POSIX.1-2017 getconf — fixed values for Bare OS (no host sysconf).
* Unknown names exit with status 1 (matches common getconf for invalid var).
* Live pathconf: **`getconf NAME /absolute/path`** delegates to **`ctx.bareOsPathconf`** when set.
*/
const CONF = {
@@ -41,20 +42,31 @@ const CONF = {
_PC_NAME_MAX: '255',
/** 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,rename,link,access,utimes,truncate,ftruncate,pathconf',
'readFile,writeFile,readdir,mkdir,stat,unlink,chmod,chdir,getcwd,readlink,symlink,exists,lstat,rmdir,mount,umount,kill,rename,link,access,utimes,truncate,ftruncate,fsync,fdatasync,pathconf',
/** Encodings accepted by `/bin/iconv` (subset; case-insensitive names). */
BARE_OS_ICONV_ENCODINGS: 'UTF-8,ISO-8859-1,UTF-16LE,UTF-16BE',
/** Synthetic process table JSON path (logical VFS). */
BARE_OS_PROC_PROCESS_TABLE: '/proc/bare_os/process_table.json'
BARE_OS_PROC_PROCESS_TABLE: '/proc/bare_os/process_table.json',
/** Sidecar suffix for synthetic POSIX ACL text (`getfacl` / `setfacl`). */
BARE_OS_ACL_SIDECAR_SUFFIX: '.bare_acl',
/** Sidecar suffix for extended-attribute JSON (`xattr`). */
BARE_OS_XATTR_SIDECAR_SUFFIX: '.bare_xattr.json',
/** Incremental kernel.ext.d reload after boot (`ctx.bareOsReloadKernelExtDropinsSafe`); 0/1 hint only. */
BARE_OS_KERNEL_EXT_D_HOT_RELOAD: '0',
/** Operator hint for hyperblob-style dedup in host pipelines; guest VFS does not enable automatically. */
BARE_OS_VFS_HYPERBLOBS_DEDUP: '0',
/** This binary: fixed catalog. Use `getconf NAME /path` + `ctx.bareOsPathconf` for live pathconf. */
BARE_OS_GETCONF_SOURCE: 'static_catalog'
}
async function run(ctx, argv) {
const args = argv.slice(1).filter((a) => a !== '--')
let dumpAll = false
let name = null
/** @type {string[]} */
const positional = []
for (const a of args) {
if (a === '-a') dumpAll = true
else if (!a.startsWith('-')) name = a
else if (!a.startsWith('-')) positional.push(a)
else {
ctx.console.error('getconf: unknown option: ' + a)
ctx.exitCode = 1
@@ -70,12 +82,34 @@ async function run(ctx, argv) {
return
}
if (!name) {
ctx.console.error('usage: getconf [-a] system_var')
if (positional.length === 2) {
const varName = positional[0]
const pathSpec = positional[1]
if (
typeof ctx.bareOsPathconf === 'function' &&
pathSpec.startsWith('/')
) {
try {
const v = ctx.bareOsPathconf(pathSpec, varName)
ctx.console.log(String(v))
ctx.exitCode = 0
return
} catch (e) {
ctx.console.error('getconf: ' + (e?.message || String(e)))
ctx.exitCode = 1
return
}
}
}
if (positional.length !== 1) {
ctx.console.error('usage: getconf [-a] system_var [path_for_pathconf]')
ctx.exitCode = 1
return
}
const name = positional[0]
if (Object.prototype.hasOwnProperty.call(CONF, name)) {
ctx.console.log(CONF[name])
ctx.exitCode = 0
+81
View File
@@ -0,0 +1,81 @@
/**
* getfacl — show POSIX-style ACL text for a VFS path.
* When PATH.bare_acl exists, prints that file; otherwise synthesizes user/group/other
* triples from the path mode bits (see setfacl / handbook).
*/
/**
* @param {number} mode
* @param {number} shift
*/
function tripleFromMode(mode, shift) {
const b = (mode >> shift) & 7
const r = b & 4 ? 'r' : '-'
const w = b & 2 ? 'w' : '-'
const x = b & 1 ? 'x' : '-'
return r + w + x
}
async function run(ctx, argv) {
let compact = false
/** @type {string[]} */
const files = []
for (let i = 1; i < argv.length; i++) {
const a = argv[i]
if (a === '-c' || a === '--compact') {
compact = true
continue
}
if (a === '--help' || a === '-h') {
ctx.console.log('usage: getfacl [-c] FILE')
return
}
if (a.startsWith('-')) {
ctx.console.error('getfacl: unsupported option ' + a)
ctx.exitCode = 1
return
}
files.push(a)
}
if (files.length !== 1) {
ctx.console.error('usage: getfacl [-c] FILE')
ctx.exitCode = 1
return
}
const path = files[0]
const side = path + '.bare_acl'
try {
const st = await ctx.vfs.stat(path)
if (!st) {
ctx.console.error('getfacl: ' + path + ': No such file')
ctx.exitCode = 1
return
}
const raw = await ctx.vfs.readFile(side)
if (raw && raw.byteLength) {
const text = ctx.b4a.toString(raw)
ctx.console.log(text.replace(/\n$/, ''))
return
}
const mode = (st.mode || 0) & 0o777
const u = tripleFromMode(mode, 6)
const g = tripleFromMode(mode, 3)
const o = tripleFromMode(mode, 0)
const lines = compact
? ['user::' + u, 'group::' + g, 'other::' + o]
: [
'# file: ' + path,
'# owner: synthetic',
'# group: synthetic',
'user::' + u,
'group::' + g,
'other::' + o
]
ctx.console.log(lines.join('\n'))
} catch (e) {
ctx.console.error(
'getfacl: ' + path + ': ' + (e && e.message ? e.message : String(e))
)
ctx.exitCode = 1
}
}
@@ -8,8 +8,8 @@ async function readUtf8(ctx, path) {
}
async function run(ctx, argv) {
const a = argv[2]
const b = argv[3]
const a = argv[1]
const b = argv[2]
if (!a || !b) {
ctx.console.error('Usage: kernel-boot-diff FILE1 FILE2')
ctx.console.error('Compares NDJSON or line-oriented boot checkpoint dumps.')
+9
View File
@@ -1,3 +1,12 @@
/**
* Move/rename via copy + delete. Hyperdrive has no single-key rename across paths, so
* directory trees and cross-location moves are duplicated then removed. A single regular
* file to a new non-directory path uses read + write + unlink when detected below.
*
* Documented limitations: cross-volume moves always copy+delete; EXDEV-style behavior is
* implicit. Busy targets, partial copy failures, and union read-only trees surface as
* generic errors from the VFS. Prefer same-directory renames for smallest blast radius.
*/
async function mvCopyPath(ctx, from, to, recursive, followSymlink) {
const st = await ctx.vfs.lstat(from)
if (!st) return false
+127
View File
@@ -0,0 +1,127 @@
/**
* Minimal patch(1): applies a single unified diff from stdin (ctx.shellStdin).
* Supports -pNUM strip, --dry-run. Intended for diffs emitted by Bare OS diff -u.
*/
/**
* @param {string} path
* @param {number} p
*/
function stripPath(path, p) {
const segs = path.replace(/\\/g, '/').split('/').filter(Boolean)
const rest = segs.slice(Math.min(p, segs.length))
return rest.length ? rest.join('/') : segs[segs.length - 1] || path
}
async function run(ctx, argv) {
let p = 0
let dry = false
/** @type {string | null} */
let overrideFile = null
for (let i = 1; i < argv.length; i++) {
const a = argv[i]
if (a === '--dry-run' || a === '--check') {
dry = true
continue
}
if (a === '-p' || a === '--strip') {
const n = argv[i + 1]
if (n == null || !/^\d+$/.test(n)) {
ctx.console.error('patch: -p requires a number')
ctx.exitCode = 2
return
}
p = Number(n)
i++
continue
}
if (a.startsWith('-p') && a.length > 2 && /^\d+$/.test(a.slice(2))) {
p = Number(a.slice(2))
continue
}
if (a.startsWith('-')) {
ctx.console.error('patch: unsupported option ' + a)
ctx.exitCode = 2
return
}
overrideFile = a
break
}
const raw = bareStdin(ctx) || ''
const lines = raw.replace(/\r\n/g, '\n').split('\n')
let minus = ''
let plus = ''
for (let i = 0; i < lines.length; i++) {
const ln = lines[i]
if (ln.startsWith('--- ')) {
minus = ln.slice(4).trim().split(/\s+/)[0]
continue
}
if (ln.startsWith('+++ ')) {
plus = ln.slice(4).trim().split(/\s+/)[0]
continue
}
}
const targetRaw = overrideFile || plus || minus
if (!targetRaw) {
ctx.console.error('patch: could not determine path from patch')
ctx.exitCode = 2
return
}
const target = stripPath(targetRaw.replace(/^b\//, ''), p)
let hunkStart = lines.findIndex((l) => /^@@/.test(l))
if (hunkStart < 0) {
ctx.console.error('patch: missing @@ hunk')
ctx.exitCode = 2
return
}
/** @type {string[]} */
const oldParts = []
/** @type {string[]} */
const newParts = []
for (let i = hunkStart + 1; i < lines.length; i++) {
const l = lines[i]
if (l.startsWith('@@')) break
if (l.startsWith('-')) oldParts.push(l.slice(1))
else if (l.startsWith('+')) newParts.push(l.slice(1))
else if (l.startsWith(' ')) {
const body = l.slice(1)
oldParts.push(body)
newParts.push(body)
}
}
let cur = ''
try {
const buf = await ctx.vfs.readFile(target)
cur = buf ? ctx.b4a.toString(buf) : ''
} catch (e) {
ctx.console.error('patch: ' + ((e && e.message) || String(e)))
ctx.exitCode = 2
return
}
const cl = splitLinesKeep(cur)
const expectOld = oldParts.join('\n')
const gotOld = cl.join('\n')
if (expectOld !== gotOld) {
ctx.console.error('patch: file content does not match hunk (try correct -p)')
ctx.exitCode = 1
return
}
const out = newParts.join('\n') + (newParts.length ? '\n' : '')
if (!dry) await ctx.vfs.writeFile(target, ctx.b4a.from(out))
ctx.exitCode = 0
}
/** @param {string} s */
function splitLinesKeep(s) {
const t = s.replace(/\r\n/g, '\n')
const parts = t.split('\n')
if (parts.length && parts[parts.length - 1] === '') parts.pop()
return parts
}
+25 -5
View File
@@ -1,3 +1,27 @@
/** Unescape \\n \\t \\r \\\\ and octal \\ddd inside FORMAT (POSIX-style subset). */
function barePrintfUnescapeFormat(fmt) {
let o = ''
for (let i = 0; i < fmt.length; i++) {
if (fmt[i] !== '\\') {
o += fmt[i]
continue
}
const c = fmt[++i]
if (c === undefined) break
if (c === 'n') o += '\n'
else if (c === 't') o += '\t'
else if (c === 'r') o += '\r'
else if (c === '\\') o += '\\'
else if (c >= '0' && c <= '7') {
let oct = c
while (i + 1 < fmt.length && /[0-7]/.test(fmt[i + 1]) && oct.length < 3)
oct += fmt[++i]
o += String.fromCharCode(Number.parseInt(oct, 8) & 0xff)
} else o += c
}
return o
}
/** @param {string} s */
function barePrintfBackslashArg(s) {
let o = ''
@@ -66,10 +90,6 @@ async function run(ctx, argv) {
}
const fmt = argv[1]
const args = argv.slice(2)
let unescaped = fmt
unescaped = unescaped.replace(/\\n/g, '\n')
unescaped = unescaped.replace(/\\t/g, '\t')
unescaped = unescaped.replace(/\\r/g, '\r')
unescaped = unescaped.replace(/\\\\/g, '\\')
const unescaped = barePrintfUnescapeFormat(fmt)
ctx.console.log(barePrintfFormat(unescaped, args))
}
+63
View File
@@ -0,0 +1,63 @@
/**
* setfacl — store or clear synthetic ACL sidecar text (PATH.bare_acl).
* Without -b, reads ACL lines from ctx.shellStdin (same pattern as patch).
*/
async function run(ctx, argv) {
let clear = false
/** @type {string[]} */
const files = []
for (let i = 1; i < argv.length; i++) {
const a = argv[i]
if (a === '-b' || a === '--remove-all') {
clear = true
continue
}
if (a === '--help' || a === '-h') {
ctx.console.log(
'usage: setfacl [-b] PATH\nWrites ACL text from stdin when not clearing.'
)
return
}
if (a.startsWith('-')) {
ctx.console.error('setfacl: unsupported option ' + a)
ctx.exitCode = 1
return
}
files.push(a)
}
if (files.length !== 1) {
ctx.console.error('usage: setfacl [-b] PATH')
ctx.exitCode = 1
return
}
const path = files[0]
const side = path + '.bare_acl'
try {
const st = await ctx.vfs.stat(path)
if (!st) {
ctx.console.error('setfacl: ' + path + ': No such file')
ctx.exitCode = 1
return
}
if (clear) {
try {
await ctx.vfs.unlink(side)
} catch {
/* ignore missing sidecar */
}
return
}
const text = bareStdin(ctx)
if (!text || !String(text).trim()) {
ctx.console.error('setfacl: ACL text required on stdin')
ctx.exitCode = 1
return
}
const body = String(text).endsWith('\n') ? String(text) : String(text) + '\n'
await ctx.vfs.writeFile(side, ctx.b4a.from(body))
} catch (e) {
ctx.console.error('setfacl: ' + (e && e.message ? e.message : String(e)))
ctx.exitCode = 1
}
}
+41
View File
@@ -0,0 +1,41 @@
async function run(ctx, argv) {
const script = argv[1]
if (script == null) {
ctx.console.error('usage: sh SCRIPT')
ctx.exitCode = 2
return
}
if (typeof ctx.execLine !== 'function') {
ctx.console.error('sh: execLine is not available (requires a Bare OS booter session)')
ctx.exitCode = 1
return
}
let buf
try {
buf = await ctx.vfs.readFile(script)
} catch (e) {
ctx.console.error('sh: ' + ((e && e.message) || String(e)))
ctx.exitCode = 1
return
}
if (buf == null) {
ctx.console.error('sh: ' + script + ': not found')
ctx.exitCode = 127
return
}
let text = ctx.b4a.toString(buf)
if (text.charCodeAt(0) === 0xfeff) text = text.slice(1)
text = text.replace(/^\ufeff/, '')
if (text.startsWith('#!')) {
const nl = text.indexOf('\n')
text = nl === -1 ? '' : text.slice(nl + 1)
}
const lines = text.split(/\r?\n/)
for (let i = 0; i < lines.length; i++) {
const trimmed = lines[i].trim()
if (!trimmed || trimmed.startsWith('#')) continue
await ctx.execLine(trimmed)
if ((Number(ctx.exitCode) || 0) !== 0) return
}
ctx.exitCode = 0
}
+40 -3
View File
@@ -46,6 +46,41 @@ function sortKeyObj(o) {
return { n: 0, raw: keyText }
}
/**
* @param {Record<string, unknown> | undefined} ctx
*/
function sortLocaleTag(ctx) {
const env =
ctx && ctx.vfs && ctx.vfs.env && typeof ctx.vfs.env === 'object'
? ctx.vfs.env
: ctx && ctx.env && typeof ctx.env === 'object'
? ctx.env
: {}
const t = String(
/** @type {Record<string, string>} */ (env).LC_ALL ||
/** @type {Record<string, string>} */ (env).LC_COLLATE ||
'C'
).trim()
return t || 'C'
}
/**
* @param {Record<string, unknown> | undefined} ctx
* @param {string} a
* @param {string} b
*/
function sortLocaleCompareRaw(ctx, a, b) {
const tag = sortLocaleTag(ctx)
if (tag === 'C' || tag === 'POSIX') {
return a < b ? -1 : a > b ? 1 : 0
}
try {
return String(a).localeCompare(String(b), tag, { sensitivity: 'variant' })
} catch {
return a < b ? -1 : a > b ? 1 : 0
}
}
/**
* @param {string} x
* @param {string} y
@@ -56,6 +91,7 @@ function sortKeyObj(o) {
* @param {number | null} opts.keyStart
* @param {number | null} opts.keyEnd
* @param {string | null} opts.dForKey
* @param {Record<string, unknown> | undefined} [opts.ctx]
* @returns {number}
*/
function sortCompareLines(x, y, opts) {
@@ -74,8 +110,8 @@ function sortCompareLines(x, y, opts) {
return opts.reverse ? -ord : ord
}
}
const cmp =
kx.raw < ky.raw ? -1 : kx.raw > ky.raw ? 1 : x < y ? -1 : x > y ? 1 : 0
let cmp = sortLocaleCompareRaw(opts.ctx, kx.raw, ky.raw)
if (cmp === 0) cmp = sortLocaleCompareRaw(opts.ctx, x, y)
return opts.reverse ? -cmp : cmp
}
@@ -290,7 +326,8 @@ async function run(ctx, argv) {
fold,
keyStart,
keyEnd,
dForKey
dForKey,
ctx
}
if (checkMode !== 'off') {
+38
View File
@@ -1,3 +1,32 @@
/** @param {Record<string, unknown>} ctx */
function testParseEuidEgid(ctx) {
const e = (ctx.vfs && ctx.vfs.env) || ctx.env || {}
const uid = Number.parseInt(String(e.UID != null ? e.UID : '1000'), 10)
const gid = Number.parseInt(String(e.GID != null ? e.GID : '1000'), 10)
return {
euid: Number.isFinite(uid) ? uid : 1000,
egid: Number.isFinite(gid) ? gid : 1000
}
}
/**
* User/group/other permission triplet (07) for the effective uid/gid.
* @param {Record<string, unknown>} st
* @param {number} euid
* @param {number} egid
*/
function testEffPermTriplet(st, euid, egid) {
const mode =
typeof st.mode === 'number'
? st.mode & 0o777
: Number.parseInt(String(st.mode || '644'), 8) & 0o777
const fuid = st.uid != null ? Number(st.uid) : 0
const fgid = st.gid != null ? Number(st.gid) : 0
if (euid === fuid) return (mode >> 6) & 7
if (egid === fgid) return (mode >> 3) & 7
return mode & 7
}
async function evalTest(ctx, args) {
if (!args.length) return false
if (args[0] === '!') {
@@ -38,10 +67,19 @@ async function evalTest(ctx, args) {
const st = await ctx.vfs.lstat(p)
return st != null && st.type === 'symlink'
}
const { euid, egid } = testParseEuidEgid(ctx)
const st = await ctx.vfs.stat(p)
if (op === '-e' || op === '-a') return st != null
if (op === '-f') return st != null && st.type === 'file'
if (op === '-d') return st != null && st.type === 'directory'
if (op === '-r')
return st != null && (testEffPermTriplet(st, euid, egid) & 4) !== 0
if (op === '-w')
return st != null && (testEffPermTriplet(st, euid, egid) & 2) !== 0
if (op === '-x')
return st != null && (testEffPermTriplet(st, euid, egid) & 1) !== 0
if (op === '-s')
return st != null && st.type === 'file' && Number(st.size) > 0
if (op === '-z') return p.length === 0
if (op === '-n') return p.length > 0
return false
+64 -26
View File
@@ -3,14 +3,27 @@
* 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 is accepted; stock booter runs sequentially; N is capped at MAX_P_FLAG (4).
* -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 MAX_P_FLAG = 4
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') {
@@ -26,6 +39,7 @@ async function run(ctx, argv) {
/** @type {string | null} */
let repl = null
let nExplicit = false
const envPCap = maxParallelFromEnv(ctx)
let pCap = 1
let i = 0
@@ -48,14 +62,16 @@ async function run(ctx, argv) {
return
}
const raw = Number(n)
pCap = Math.min(MAX_P_FLAG, Math.max(1, raw))
if (raw > MAX_P_FLAG) {
pCap = Math.min(envPCap, Math.max(1, raw))
if (raw > envPCap) {
ctx.console.error(
'xargs: -P ' +
raw +
' exceeds Bare OS cap ' +
MAX_P_FLAG +
' (parallelism hint only; sequential execution)'
' exceeds cap ' +
envPCap +
' (raise BARE_OS_XARGS_MAX_PROCS up to ' +
ABS_P_CAP +
')'
)
}
i += 2
@@ -63,12 +79,14 @@ async function run(ctx, argv) {
}
if (a.startsWith('-P') && a.length > 2 && /^\d+$/.test(a.slice(2))) {
const raw = Number(a.slice(2))
pCap = Math.min(MAX_P_FLAG, Math.max(1, raw))
if (raw > MAX_P_FLAG) {
pCap = Math.min(envPCap, Math.max(1, raw))
if (raw > envPCap) {
ctx.console.error(
'xargs: -P exceeds Bare OS cap ' +
MAX_P_FLAG +
' (parallelism hint only; sequential execution)'
'xargs: -P exceeds cap ' +
envPCap +
' (raise BARE_OS_XARGS_MAX_PROCS up to ' +
ABS_P_CAP +
')'
)
}
i++
@@ -113,9 +131,9 @@ async function run(ctx, argv) {
ctx.console.error(
'xargs: Bare OS supports: -0/--null, -n N (max ' +
MAX_PER_INVOCATION +
' per run), -I repl, -P N (max ' +
MAX_P_FLAG +
', sequential)'
' per run), -I repl, -P N (parallel batches, cap ' +
envPCap +
')'
)
ctx.exitCode = 1
return
@@ -124,7 +142,6 @@ async function run(ctx, argv) {
/** @type {string[]} */
let cmd = args.slice(i)
if (cmd.length === 0) cmd = ['echo']
void pCap
let text = bareStdin(ctx) || ''
if (text.length > MAX_STDIN) {
@@ -152,9 +169,10 @@ async function run(ctx, argv) {
}
/**
* @param {Record<string, unknown>} target
* @param {string[]} batch
*/
const runOne = async (batch) => {
const runOne = async (target, batch) => {
const subst = batch.join(' ')
/** @type {string[]} */
const toRun =
@@ -169,29 +187,49 @@ async function run(ctx, argv) {
}
return o
})
await ctx.runBinCommand(toRun)
await target.runBinCommand(toRun)
}
if (pieces.length === 0) {
await runOne([])
await runOne(ctx, [])
return
}
let invocations = 0
/** @type {string[][]} */
const batches = []
for (let o = 0; o < pieces.length; o += maxBatch) {
if (++invocations > MAX_INVOCATIONS) {
if (batches.length >= MAX_INVOCATIONS) {
ctx.console.error(
'xargs: exceeded ' + MAX_INVOCATIONS + ' invocations (Bare OS limit)'
)
ctx.exitCode = 125
return
}
const batch = pieces.slice(o, o + maxBatch)
await runOne(batch)
const ec = Number(ctx.exitCode) || 0
if (ec !== 0) {
ctx.exitCode = ec
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
}
+149
View File
@@ -0,0 +1,149 @@
/**
* xattr — extended attributes via sidecar JSON (PATH.bare_xattr.json).
* Values are stored base64-encoded UTF-8 strings for JSON safety.
*/
/**
* @param {string} val
*/
function xattrB64Encode(val) {
const s = String(val)
if (typeof globalThis.Buffer !== 'undefined') {
return globalThis.Buffer.from(s, 'utf8').toString('base64')
}
const u8 = new TextEncoder().encode(s)
let bin = ''
for (let i = 0; i < u8.length; i++) bin += String.fromCharCode(u8[i])
if (typeof globalThis.btoa === 'function') return globalThis.btoa(bin)
throw new Error('xattr: base64 encode unavailable')
}
/**
* @param {string} b64
*/
function xattrB64Decode(b64) {
const t = String(b64).replace(/\s+/g, '')
if (typeof globalThis.Buffer !== 'undefined') {
return globalThis.Buffer.from(t, 'base64').toString('utf8')
}
if (typeof globalThis.atob === 'function') {
const bin = globalThis.atob(t)
let out = ''
for (let i = 0; i < bin.length; i++) out += bin[i]
return out
}
throw new Error('xattr: base64 decode unavailable')
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} path
*/
async function readXattrMap(ctx, path) {
const side = path + '.bare_xattr.json'
const raw = await ctx.vfs.readFile(side)
if (!raw || !raw.byteLength) return {}
try {
const o = JSON.parse(ctx.b4a.toString(raw))
return o && typeof o === 'object' && !Array.isArray(o) ? o : {}
} catch {
return {}
}
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} path
* @param {Record<string, string>} obj
*/
async function writeXattrMap(ctx, path, obj) {
const side = path + '.bare_xattr.json'
const text = JSON.stringify(obj) + '\n'
await ctx.vfs.writeFile(side, ctx.b4a.from(text))
}
async function run(ctx, argv) {
let list = false
/** @type {string | null} */
let delName = null
/** @type {{ name: string, val: string } | null} */
let writePair = null
/** @type {string[]} */
const rest = []
for (let i = 1; i < argv.length; i++) {
const a = argv[i]
if (a === '-l' || a === '--list') {
list = true
continue
}
if (a === '-w' || a === '--write') {
const name = argv[i + 1]
const val = argv[i + 2]
if (!name || val == null) {
ctx.console.error('xattr: -w requires NAME VALUE')
ctx.exitCode = 1
return
}
writePair = { name, val }
i += 2
continue
}
if (a === '-d' || a === '--delete') {
const name = argv[i + 1]
if (!name) {
ctx.console.error('xattr: -d requires NAME')
ctx.exitCode = 1
return
}
delName = name
i++
continue
}
if (a === '--help' || a === '-h') {
ctx.console.log('usage: xattr [-l] [-w NAME VALUE] [-d NAME] PATH')
return
}
if (a.startsWith('-')) {
ctx.console.error('xattr: unsupported option ' + a)
ctx.exitCode = 1
return
}
rest.push(a)
}
if (rest.length !== 1) {
ctx.console.error('usage: xattr [-l] [-w NAME VALUE] [-d NAME] PATH')
ctx.exitCode = 1
return
}
const path = rest[0]
try {
const st = await ctx.vfs.stat(path)
if (!st) {
ctx.console.error('xattr: ' + path + ': No such file')
ctx.exitCode = 1
return
}
const obj = /** @type {Record<string, string>} */ (await readXattrMap(ctx, path))
if (delName) {
delete obj[delName]
await writeXattrMap(ctx, path, obj)
return
}
if (writePair) {
obj[writePair.name] = xattrB64Encode(writePair.val)
await writeXattrMap(ctx, path, obj)
return
}
const keys = Object.keys(obj).sort()
for (const k of keys) {
if (list) {
ctx.console.log(k + ': ' + xattrB64Decode(obj[k]))
} else {
ctx.console.log(k)
}
}
} catch (e) {
ctx.console.error('xattr: ' + (e && e.message ? e.message : String(e)))
ctx.exitCode = 1
}
}