Files
bare-operating-system/packages/bare-os-seeder/kernel/bin/patch
T
Raven Scott 346bb71ffe 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.
2026-04-04 21:23:49 -04:00

217 lines
5.7 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
}
/**
* 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
}