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.
223 lines
6.6 KiB
Plaintext
223 lines
6.6 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
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
if (st.type === 'symlink') {
|
|
if (!followSymlink) {
|
|
await ctx.vfs.symlink(await ctx.vfs.readlink(from), to)
|
|
return true
|
|
}
|
|
const fst = await ctx.vfs.stat(from)
|
|
if (fst.type === 'file') {
|
|
const buf = await ctx.vfs.readFile(from)
|
|
if (!buf) return false
|
|
await ctx.vfs.writeFile(to, buf)
|
|
return true
|
|
}
|
|
if (fst.type === 'directory') {
|
|
if (!recursive) return false
|
|
await ctx.vfs.mkdir(to, { recursive: true })
|
|
const names = await ctx.vfs.readdir(from)
|
|
for (const n of names) {
|
|
if (n === '.bareos_empty') continue
|
|
const f = from.replace(/\/+$/, '') + '/' + n
|
|
const t = to.replace(/\/+$/, '') + '/' + n
|
|
if (!(await mvCopyPath(ctx, f, t, true, followSymlink))) return false
|
|
}
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
if (st.type === 'file') {
|
|
const buf = await ctx.vfs.readFile(from)
|
|
if (!buf) return false
|
|
await ctx.vfs.writeFile(to, buf)
|
|
return true
|
|
}
|
|
if (st.type === 'directory') {
|
|
if (!recursive) return false
|
|
await ctx.vfs.mkdir(to, { recursive: true })
|
|
const names = await ctx.vfs.readdir(from)
|
|
for (const n of names) {
|
|
if (n === '.bareos_empty') continue
|
|
const f = from.replace(/\/+$/, '') + '/' + n
|
|
const t = to.replace(/\/+$/, '') + '/' + n
|
|
if (!(await mvCopyPath(ctx, f, t, true, followSymlink))) return false
|
|
}
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
async function run(ctx, argv) {
|
|
let followSymlink = false
|
|
const paths = []
|
|
for (let i = 1; i < argv.length; i++) {
|
|
const a = argv[i]
|
|
if (a === '-L' || a === '--dereference') {
|
|
followSymlink = true
|
|
continue
|
|
}
|
|
if (a === '-P' || a === '--no-dereference') {
|
|
followSymlink = false
|
|
continue
|
|
}
|
|
if (a === '--') {
|
|
paths.push(...argv.slice(i + 1))
|
|
break
|
|
}
|
|
if (a.startsWith('-')) {
|
|
ctx.console.error('mv: unsupported option ' + a)
|
|
ctx.exitCode = 1
|
|
return
|
|
}
|
|
paths.push(a)
|
|
}
|
|
if (paths.length < 2) {
|
|
ctx.console.error('usage: mv [-L|-P] SOURCE... DEST')
|
|
ctx.exitCode = 1
|
|
return
|
|
}
|
|
const dest = paths.pop()
|
|
const sources = paths
|
|
let destIsDir = false
|
|
try {
|
|
const dst = await ctx.vfs.lstat(dest)
|
|
destIsDir = !!(dst && dst.type === 'directory')
|
|
} catch {
|
|
destIsDir = false
|
|
}
|
|
if (sources.length > 1 && !destIsDir) {
|
|
ctx.console.error('mv: target is not a directory')
|
|
ctx.exitCode = 1
|
|
return
|
|
}
|
|
for (const src of sources) {
|
|
const base = src.replace(/\/+$/, '').split('/').pop() || src
|
|
const target =
|
|
destIsDir || sources.length > 1
|
|
? dest.replace(/\/+$/, '') + '/' + base
|
|
: dest
|
|
try {
|
|
const st = await ctx.vfs.lstat(src)
|
|
let followReg = st && st.type === 'file'
|
|
if (!followReg && followSymlink && st && st.type === 'symlink') {
|
|
const fs = await ctx.vfs.stat(src).catch(() => null)
|
|
followReg = !!(fs && fs.type === 'file')
|
|
}
|
|
const singleFileToFile = sources.length === 1 && !destIsDir && followReg
|
|
if (singleFileToFile) {
|
|
const buf = await ctx.vfs.readFile(src)
|
|
if (!buf) throw new Error('cannot read source')
|
|
await ctx.vfs.writeFile(target, buf)
|
|
await ctx.vfs.unlink(src)
|
|
continue
|
|
}
|
|
if (!(await mvCopyPath(ctx, src, target, true, followSymlink))) {
|
|
throw new Error('cannot copy')
|
|
}
|
|
await ctx.vfs.rm(src, { recursive: true, force: true })
|
|
} catch (e) {
|
|
ctx.console.error('mv: ' + src + ': ' + ((e && e.message) || e))
|
|
ctx.exitCode = 1
|
|
}
|
|
}
|
|
}
|