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.
505 lines
12 KiB
Plaintext
505 lines
12 KiB
Plaintext
/* BARE_OS_BIN_API 1.0.0 — bump when staged /bin script semantics change (see developer guide). */
|
|
/** Shared helpers for drive-resident /bin scripts (prepended before each command). */
|
|
function bareStdin(ctx) {
|
|
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
|
|
}
|
|
|
|
/** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */
|
|
function bareFormatModeString(mode, type) {
|
|
const typeChar = type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-'
|
|
const perm = mode & 0o777
|
|
const r = (bit) => (perm & bit ? 'r' : '-')
|
|
const w = (bit) => (perm & bit ? 'w' : '-')
|
|
const x = (bit) => (perm & bit ? 'x' : '-')
|
|
return (
|
|
typeChar +
|
|
r(0o400) +
|
|
w(0o200) +
|
|
x(0o100) +
|
|
r(0o040) +
|
|
w(0o020) +
|
|
x(0o010) +
|
|
r(0o004) +
|
|
w(0o002) +
|
|
x(0o001)
|
|
)
|
|
}
|
|
|
|
/** @param {number} mtimeMs @param {number} [nowMs] */
|
|
function bareFormatLsMtime(mtimeMs, nowMs) {
|
|
const now = nowMs != null ? nowMs : Date.now()
|
|
const d = new Date(mtimeMs)
|
|
const months = [
|
|
'Jan',
|
|
'Feb',
|
|
'Mar',
|
|
'Apr',
|
|
'May',
|
|
'Jun',
|
|
'Jul',
|
|
'Aug',
|
|
'Sep',
|
|
'Oct',
|
|
'Nov',
|
|
'Dec'
|
|
]
|
|
const mon = months[d.getMonth()]
|
|
const day = String(d.getDate()).padStart(2, ' ')
|
|
const sixMo = 180 * 24 * 3600 * 1000
|
|
if (Math.abs(now - mtimeMs) > sixMo) {
|
|
const yr = String(d.getFullYear()).padStart(4, ' ')
|
|
return mon + ' ' + day + ' ' + yr
|
|
}
|
|
const hh = String(d.getHours()).padStart(2, '0')
|
|
const mm = String(d.getMinutes()).padStart(2, '0')
|
|
return mon + ' ' + day + ' ' + hh + ':' + mm
|
|
}
|
|
|
|
/** @param {number} size */
|
|
function barePosixBlocks(size) {
|
|
return Math.ceil(Number(size) / 512) || 0
|
|
}
|
|
|
|
/**
|
|
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
|
|
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
|
|
* @param {Record<string, unknown>} ctx
|
|
* @param {string | Uint8Array} chunk
|
|
* @returns {boolean}
|
|
*/
|
|
function bareOsEmitRaw(ctx, chunk) {
|
|
if (typeof ctx.bareOsBinWrite === 'function') {
|
|
const b4 = ctx.b4a
|
|
const u8 =
|
|
typeof chunk === 'string'
|
|
? b4 && typeof b4.from === 'function'
|
|
? b4.from(chunk)
|
|
: new TextEncoder().encode(chunk)
|
|
: chunk
|
|
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
|
|
return true
|
|
}
|
|
const w = globalThis.process?.stdout?.write
|
|
if (typeof w === 'function') {
|
|
w.call(globalThis.process.stdout, chunk)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
/**
|
|
* @param {string} line
|
|
* @param {string | null} delim
|
|
* @param {number} start1
|
|
* @param {number | null} end1
|
|
*/
|
|
function sortKeySlice(line, delim, start1, end1) {
|
|
/** @type {string[]} */
|
|
let fields
|
|
if (delim != null) {
|
|
if (delim === '') fields = line.split('')
|
|
else fields = line.split(delim)
|
|
} else {
|
|
const t = line.trim()
|
|
fields = t.length ? t.split(/\s+/) : []
|
|
}
|
|
const i0 = Math.max(0, start1 - 1)
|
|
const i1 = end1 != null ? end1 : fields.length
|
|
const slice = fields.slice(i0, i1)
|
|
if (delim != null) return slice.join(delim)
|
|
return slice.join(' ')
|
|
}
|
|
|
|
/**
|
|
* @param {object} o
|
|
* @param {string} o.line
|
|
* @param {boolean} o.numeric
|
|
* @param {boolean} o.fold
|
|
* @param {number | null} o.keyStart
|
|
* @param {number | null} o.keyEnd
|
|
* @param {string | null} o.dForKey
|
|
*/
|
|
function sortKeyObj(o) {
|
|
const keyText =
|
|
o.keyStart != null
|
|
? sortKeySlice(o.line, o.dForKey, o.keyStart, o.keyEnd)
|
|
: o.line
|
|
if (o.numeric) {
|
|
const m = String(keyText).match(
|
|
/^\s*(-?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?)/
|
|
)
|
|
if (m) return { n: Number(m[1]), raw: keyText }
|
|
return { n: Number.POSITIVE_INFINITY, raw: keyText }
|
|
}
|
|
if (o.fold) return { n: 0, raw: keyText.toLowerCase() }
|
|
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
|
|
* @param {object} opts
|
|
* @param {boolean} opts.numeric
|
|
* @param {boolean} opts.reverse
|
|
* @param {boolean} opts.fold
|
|
* @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) {
|
|
const base = {
|
|
numeric: opts.numeric,
|
|
fold: opts.fold,
|
|
keyStart: opts.keyStart,
|
|
keyEnd: opts.keyEnd,
|
|
dForKey: opts.dForKey
|
|
}
|
|
const kx = sortKeyObj({ ...base, line: x })
|
|
const ky = sortKeyObj({ ...base, line: y })
|
|
if (opts.numeric) {
|
|
if (kx.n !== ky.n) {
|
|
const ord = kx.n < ky.n ? -1 : 1
|
|
return opts.reverse ? -ord : ord
|
|
}
|
|
}
|
|
let cmp = sortLocaleCompareRaw(opts.ctx, kx.raw, ky.raw)
|
|
if (cmp === 0) cmp = sortLocaleCompareRaw(opts.ctx, x, y)
|
|
return opts.reverse ? -cmp : cmp
|
|
}
|
|
|
|
async function run(ctx, argv) {
|
|
const vfs = ctx.vfs
|
|
let numeric = false
|
|
let reverse = false
|
|
let uniq = false
|
|
let fold = false
|
|
/** @type {'off' | 'verbose' | 'quiet'} */
|
|
let checkMode = 'off'
|
|
let stable = false
|
|
/** @type {string | null} */
|
|
let outFile = null
|
|
/** @type {string | undefined} */
|
|
let delim
|
|
/** @type {number | null} */
|
|
let keyStart = null
|
|
/** @type {number | null} */
|
|
let keyEnd = null
|
|
let i = 1
|
|
while (i < argv.length) {
|
|
const a = argv[i]
|
|
if (a === '--') {
|
|
i++
|
|
break
|
|
}
|
|
if (a === '--check' || a === '--check=diagnose-first') {
|
|
checkMode = 'verbose'
|
|
i++
|
|
continue
|
|
}
|
|
if (a === '--check=silent' || a === '--check=quiet') {
|
|
checkMode = 'quiet'
|
|
i++
|
|
continue
|
|
}
|
|
if (a === '-C') {
|
|
checkMode = 'quiet'
|
|
i++
|
|
continue
|
|
}
|
|
if (a === '-c') {
|
|
checkMode = 'verbose'
|
|
i++
|
|
continue
|
|
}
|
|
if (a === '-s' || a === '--stable') {
|
|
stable = true
|
|
i++
|
|
continue
|
|
}
|
|
if (a === '-o' || a === '--output') {
|
|
const f = argv[++i]
|
|
if (f === undefined) {
|
|
ctx.console.error('sort: option requires an argument -- output')
|
|
ctx.exitCode = 1
|
|
return
|
|
}
|
|
outFile = f
|
|
i++
|
|
continue
|
|
}
|
|
if (a.startsWith('--output=')) {
|
|
outFile = a.slice('--output='.length) || null
|
|
if (!outFile) {
|
|
ctx.console.error('sort: option requires an argument -- output')
|
|
ctx.exitCode = 1
|
|
return
|
|
}
|
|
i++
|
|
continue
|
|
}
|
|
if (a === '-n' || a === '--numeric-sort' || a === '-g') {
|
|
numeric = true
|
|
i++
|
|
continue
|
|
}
|
|
if (a === '-r' || a === '--reverse') {
|
|
reverse = true
|
|
i++
|
|
continue
|
|
}
|
|
if (a === '-u' || a === '--unique') {
|
|
uniq = true
|
|
i++
|
|
continue
|
|
}
|
|
if (a === '-f' || a === '--ignore-case') {
|
|
fold = true
|
|
i++
|
|
continue
|
|
}
|
|
if (a === '-t' || a === '--field-separator') {
|
|
const d = argv[++i]
|
|
if (d === undefined) {
|
|
ctx.console.error('sort: option requires an argument -- t')
|
|
ctx.exitCode = 1
|
|
return
|
|
}
|
|
delim = d === '\\t' ? '\t' : d
|
|
i++
|
|
continue
|
|
}
|
|
if (a.startsWith('-t') && a.length > 2) {
|
|
delim = a.slice(2) === 't' ? '\t' : a.slice(2)
|
|
i++
|
|
continue
|
|
}
|
|
if (a === '-k' || a === '--key') {
|
|
const spec = argv[++i]
|
|
if (spec === undefined) {
|
|
ctx.console.error('sort: option requires an argument -- k')
|
|
ctx.exitCode = 1
|
|
return
|
|
}
|
|
const parts = String(spec).split(',')
|
|
keyStart = Number.parseInt(parts[0], 10)
|
|
keyEnd =
|
|
parts[1] !== undefined && parts[1] !== ''
|
|
? Number.parseInt(parts[1], 10)
|
|
: null
|
|
if (!Number.isFinite(keyStart) || keyStart < 1) {
|
|
ctx.console.error('sort: invalid key specification')
|
|
ctx.exitCode = 1
|
|
return
|
|
}
|
|
if (
|
|
keyEnd != null &&
|
|
(!Number.isFinite(keyEnd) || keyEnd < keyStart)
|
|
) {
|
|
ctx.console.error('sort: invalid key specification')
|
|
ctx.exitCode = 1
|
|
return
|
|
}
|
|
i++
|
|
continue
|
|
}
|
|
if (/^-k\d/.test(a)) {
|
|
const spec = a.slice(2)
|
|
const parts = String(spec).split(',')
|
|
keyStart = Number.parseInt(parts[0], 10)
|
|
keyEnd =
|
|
parts[1] !== undefined && parts[1] !== ''
|
|
? Number.parseInt(parts[1], 10)
|
|
: null
|
|
if (!Number.isFinite(keyStart) || keyStart < 1) {
|
|
ctx.console.error('sort: invalid key specification')
|
|
ctx.exitCode = 1
|
|
return
|
|
}
|
|
i++
|
|
continue
|
|
}
|
|
if (a.startsWith('-') && a.length > 1) {
|
|
let j = 1
|
|
let ok = true
|
|
while (j < a.length) {
|
|
const c = a[j++]
|
|
if (c === 'n') numeric = true
|
|
else if (c === 'r') reverse = true
|
|
else if (c === 'u') uniq = true
|
|
else if (c === 'f') fold = true
|
|
else if (c === 'c') checkMode = 'verbose'
|
|
else if (c === 'C') checkMode = 'quiet'
|
|
else if (c === 's') stable = true
|
|
else {
|
|
ok = false
|
|
break
|
|
}
|
|
}
|
|
if (ok) {
|
|
i++
|
|
continue
|
|
}
|
|
}
|
|
break
|
|
}
|
|
|
|
const files = argv.slice(i).filter((x) => x !== '--')
|
|
/** @type {string[]} */
|
|
let lines = []
|
|
if (!files.length) {
|
|
const s = bareStdin(ctx)
|
|
lines = s.length ? s.split('\n') : []
|
|
if (lines.length && lines[lines.length - 1] === '') lines.pop()
|
|
} else {
|
|
for (const f of files) {
|
|
if (f === '-') {
|
|
const s = bareStdin(ctx)
|
|
const part = s.length ? s.split('\n') : []
|
|
if (part.length && part[part.length - 1] === '') part.pop()
|
|
lines.push(...part)
|
|
continue
|
|
}
|
|
const buf = await vfs.readFile(f)
|
|
if (!buf) {
|
|
ctx.console.error('sort: ' + f + ': No such file')
|
|
ctx.exitCode = 1
|
|
continue
|
|
}
|
|
const part = ctx.b4a.toString(buf).split('\n')
|
|
if (part.length && part[part.length - 1] === '') part.pop()
|
|
lines.push(...part)
|
|
}
|
|
}
|
|
|
|
const dForKey = delim === undefined ? null : delim
|
|
const cmpOpts = {
|
|
numeric,
|
|
reverse,
|
|
fold,
|
|
keyStart,
|
|
keyEnd,
|
|
dForKey,
|
|
ctx
|
|
}
|
|
|
|
if (checkMode !== 'off') {
|
|
const checkLabel = files.length === 1 ? files[0] : '-'
|
|
for (let li = 0; li < lines.length - 1; li++) {
|
|
const aLine = lines[li]
|
|
const bLine = lines[li + 1]
|
|
const c = sortCompareLines(aLine, bLine, cmpOpts)
|
|
if (uniq && c === 0) {
|
|
ctx.exitCode = 1
|
|
if (checkMode === 'verbose') {
|
|
ctx.console.error(
|
|
'sort: ' +
|
|
checkLabel +
|
|
':' +
|
|
(li + 2) +
|
|
': disorder: ' +
|
|
bLine
|
|
)
|
|
}
|
|
return
|
|
}
|
|
if (c > 0) {
|
|
ctx.exitCode = 1
|
|
if (checkMode === 'verbose') {
|
|
ctx.console.error(
|
|
'sort: ' +
|
|
checkLabel +
|
|
':' +
|
|
(li + 2) +
|
|
': disorder: ' +
|
|
bLine
|
|
)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
ctx.exitCode = 0
|
|
return
|
|
}
|
|
|
|
/** @param {string} x
|
|
* @param {string} y */
|
|
function cmpPair(x, y) {
|
|
const primary = sortCompareLines(x, y, cmpOpts)
|
|
if (primary !== 0 || !stable) return primary
|
|
return 0
|
|
}
|
|
|
|
if (stable) {
|
|
const decorated = lines.map((line, idx) => ({ line, idx }))
|
|
decorated.sort((a, b) => {
|
|
const p = sortCompareLines(a.line, b.line, cmpOpts)
|
|
if (p !== 0) return p
|
|
return a.idx - b.idx
|
|
})
|
|
lines = decorated.map((d) => d.line)
|
|
} else {
|
|
lines.sort(cmpPair)
|
|
}
|
|
|
|
if (uniq) {
|
|
const out = []
|
|
let prev = null
|
|
for (const ln of lines) {
|
|
if (ln !== prev) out.push(ln)
|
|
prev = ln
|
|
}
|
|
lines = out
|
|
}
|
|
|
|
const body = lines.length ? lines.join('\n') + '\n' : ''
|
|
|
|
if (outFile != null) {
|
|
try {
|
|
await vfs.writeFile(outFile, ctx.b4a.from(body))
|
|
} catch (e) {
|
|
ctx.console.error('sort: ' + outFile + ': ' + (e.message || e))
|
|
ctx.exitCode = 1
|
|
return
|
|
}
|
|
} else if (lines.length) {
|
|
ctx.console.log(lines.join('\n'))
|
|
}
|
|
}
|