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.
239 lines
6.3 KiB
Plaintext
239 lines
6.3 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
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
}
|
|
}
|