Expose /proc/bare_os/metrics.prom (kernel counter OpenMetrics) and /proc/bare_os/protomux.json (alias registry snapshot). Extend VFS routing, readdir, pseudo watch, and bare_os_proc_index to schema 8; restore flat /proc entries for syscalls and process_table with sort order matching tests. Add ctx.bareOsAuditLogAppendBatch and bump BARE_OS_CTX_API_VERSION to 1.30.0 with d.ts and compatibility-matrix updates. Harden swarm peer ban backoff with jitter on the exponential window. Refresh conformance matrix, environment appendix, booter package doc, and handbook chapters for proc paths, param expansion V3, vault at rest, and baretop snapshot keys. Align baretop-snapshot with bareOsReadBareTopSnapshot.
257 lines
7.0 KiB
Plaintext
257 lines
7.0 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
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
function parseConvList(v) {
|
|
return String(v || '')
|
|
.split(',')
|
|
.map((x) => x.trim().toLowerCase())
|
|
.filter(Boolean)
|
|
}
|
|
|
|
function padToBlock(u8, bs, b4a) {
|
|
if (bs <= 0) return u8
|
|
const rem = u8.byteLength % bs
|
|
if (rem === 0) return u8
|
|
const pad = bs - rem
|
|
const out = b4a.alloc(u8.byteLength + pad)
|
|
out.set(u8, 0)
|
|
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', conv: [] }
|
|
|
|
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') {
|
|
const list = parseConvList(v)
|
|
const allow = new Set(['notrunc', 'sync', 'noerror', 'nocreat'])
|
|
for (const c of list) {
|
|
if (!allow.has(c)) {
|
|
ctx.console.error('dd: unsupported conv=' + c)
|
|
ctx.exitCode = 1
|
|
return
|
|
}
|
|
}
|
|
opt.conv = list
|
|
} 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)
|
|
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 (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.includes('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`)
|
|
}
|
|
}
|