Files
bare-operating-system/packages/bare-os-booter/lib/tar-cli.js
T
Raven Scott 8f2e3cceb0 Move editable kernel bulk from kernel/init-main.js to kernel/lib/init/
(staged as /lib/init/init-main.js); point bundle-kernel-init and verify
scripts at the new path.

Wire curl, wget, openssl, ssh-keygen, and tar through coreutils and
booter host delegates with booter-side CLI helpers; refresh related
bins, bare manifest, shell completion, and man DB (kernel + seeder).

Add booter support modules for ACL evaluation, audit chain, secret
handles, peer admission, replication priority, process table, swarm
lifecycle, boot-graph proc, metrics, monotonic time, protomux alias
registry, and swarm peer policy; extend extension resolver, VFS,
swarm connection managers, IPC, identity-account, and initd.

Harden bare-os-bare-libs build on esbuild failure; add verify scripts
for extension manifest schema and runtime incomplete markers; extend
ctx API typings, gen-ctx-client-stub, and verify-ctx-dts.

Update boot hook fragment, bundled init.js, handbook and reference
docs (incl. kernel security and VFS path classes).
2026-04-04 17:51:47 -04:00

299 lines
7.4 KiB
JavaScript

/**
* Minimal POSIX ustar tar for VFS paths: create / list / extract.
* Supported:
* tar -cf ARCHIVE PATH [PATH...]
* tar -tf ARCHIVE
* tar -xf ARCHIVE
* GNU extensions / compression are not supported.
*/
import b4a from 'b4a'
const BLK = 512
function utf8Encode(str) {
return b4a.from(String(str), 'utf8')
}
function utf8Decode(buf) {
return b4a.toString(buf, 'utf8')
}
/**
* @param {Uint8Array} buf
* @param {number} off
* @param {number} len
*/
function parseOctalField(buf, off, len) {
let s = ''
for (let i = 0; i < len; i++) {
const c = buf[off + i]
if (c === 0 || c === 32) break
s += String.fromCharCode(c)
}
const n = Number.parseInt(s.trim(), 8)
return Number.isFinite(n) ? n : 0
}
/**
* @param {string} oct
* @param {number} len
*/
function octalPad(oct, len) {
const o = oct + '\0'
const b = new Uint8Array(len)
const enc = utf8Encode(o)
b.set(enc.slice(0, len - 1))
return b
}
/**
* @param {Record<string, unknown>} header
*/
function encodeUstarHeader(header) {
const h = new Uint8Array(BLK)
const name = String(header.name || '').slice(0, 100)
const mode = String(header.mode || 0o644).slice(0, 7)
const uid = String(header.uid || 0).slice(0, 7)
const gid = String(header.gid || 0).slice(0, 7)
const size = String(header.size || 0).slice(0, 11)
const mtime = String(header.mtime || 0).slice(0, 11)
const typeflag = header.typeflag || '0'
const w = (str, off, max) => {
const enc = utf8Encode(str)
h.set(enc.slice(0, max), off)
}
w(name, 0, 100)
h.set(octalPad(mode, 8), 100)
h.set(octalPad(uid, 8), 108)
h.set(octalPad(gid, 8), 116)
h.set(octalPad(size, 12), 124)
h.set(octalPad(mtime, 12), 136)
// ustar checksum field (sum of header bytes; standard layout)
for (let i = 148; i < 156; i++) h[i] = 32
h[156] = typeflag.charCodeAt(0)
w('ustar\0', 257, 6)
w('00', 263, 2)
let sum = 0
for (let i = 0; i < BLK; i++) {
if (i >= 148 && i < 156) sum += 32
else sum += h[i]
}
const cks = octalPad(sum.toString(8), 8)
h.set(cks, 148)
return h
}
/**
* @param {Uint8Array} block
*/
function parseUstarBlock(block) {
const name = utf8Decode(block.subarray(0, 100)).replace(/\0.*$/, '')
const size = parseOctalField(block, 124, 12)
const typeflag = String.fromCharCode(block[156] || 48)
return { name, size, typeflag }
}
/**
* @param {Record<string, unknown>} ctx
* @param {string[]} argv
*/
export async function runTarCli(ctx, argv) {
const args = argv.slice(1).filter((a) => a !== '--')
if (args.length === 0 || args.includes('-h') || args.includes('--help')) {
ctx.console.log(
'usage: tar -cf ARCHIVE PATH...\n' +
' tar -tf ARCHIVE\n' +
' tar -xf ARCHIVE\n' +
'Bare OS: ustar only, VFS paths.\n'
)
return
}
let create = false
let list = false
let extract = false
/** @type {string[]} */
const rest = []
for (let i = 0; i < args.length; i++) {
const a = args[i]
if (a === '-cf' || a === '-fc') {
create = true
rest.push(args[++i] || '')
continue
}
if (a === '-tf' || a === '-ft') {
list = true
rest.push(args[++i] || '')
continue
}
if (a === '-xf' || a === '-fx') {
extract = true
rest.push(args[++i] || '')
continue
}
if (a.startsWith('-')) {
ctx.console.error('tar: unsupported flag ' + a)
ctx.exitCode = 1
return
}
rest.push(a)
}
const vfs = ctx.vfs
const b4 = ctx.b4a
if (!vfs || !b4 || typeof vfs.readFile !== 'function') {
ctx.console.error('tar: vfs unavailable')
ctx.exitCode = 1
return
}
if (create) {
const archive = rest.shift()
if (!archive || rest.length === 0) {
ctx.console.error('tar: -cf needs archive and at least one path')
ctx.exitCode = 1
return
}
/** @type {Uint8Array[]} */
const parts = []
for (const p of rest) {
let abs
try {
abs = vfs.resolveLogical(String(p))
} catch {
ctx.console.error('tar: bad path ' + p)
ctx.exitCode = 1
return
}
let data
try {
data = await vfs.readFile(abs)
} catch (e) {
ctx.console.error(
'tar: ' + ((e && /** @type {Error} */ (e).message) || String(e))
)
ctx.exitCode = 1
return
}
const u8 = b4a.isBuffer(data) ? new Uint8Array(data) : new Uint8Array(data)
const base = abs.includes('/') ? abs.split('/').pop() : abs
const name = base || 'file'
if (name.length > 99) {
ctx.console.error('tar: path name too long for ustar: ' + name)
ctx.exitCode = 1
return
}
const hdr = encodeUstarHeader({
name,
size: u8.length,
mode: 0o644,
mtime: Math.floor(Date.now() / 1000)
})
parts.push(hdr)
parts.push(u8)
const pad = (BLK - (u8.length % BLK)) % BLK
if (pad) parts.push(new Uint8Array(pad))
}
parts.push(new Uint8Array(BLK * 2))
let total = 0
for (const q of parts) total += q.length
const out = new Uint8Array(total)
let o = 0
for (const q of parts) {
out.set(q, o)
o += q.length
}
let absA
try {
absA = vfs.resolveLogical(String(archive))
} catch {
ctx.console.error('tar: bad archive path')
ctx.exitCode = 1
return
}
try {
await vfs.writeFile(absA, b4.from(out))
} catch (e) {
ctx.console.error(
'tar: ' + ((e && /** @type {Error} */ (e).message) || String(e))
)
ctx.exitCode = 1
}
return
}
if (list || extract) {
const archive = rest[0]
if (!archive) {
ctx.console.error('tar: missing archive')
ctx.exitCode = 1
return
}
let absA
try {
absA = vfs.resolveLogical(String(archive))
} catch {
ctx.console.error('tar: bad archive path')
ctx.exitCode = 1
return
}
let raw
try {
raw = await vfs.readFile(absA)
} catch (e) {
ctx.console.error(
'tar: ' + ((e && /** @type {Error} */ (e).message) || String(e))
)
ctx.exitCode = 1
return
}
const buf = b4a.isBuffer(raw) ? new Uint8Array(raw) : new Uint8Array(raw)
let off = 0
while (off + BLK <= buf.length) {
const block = buf.subarray(off, off + BLK)
if (block.every((b) => b === 0)) break
off += BLK
const rec = parseUstarBlock(block)
const { name, size, typeflag } = rec
if (off + size > buf.length) {
ctx.console.error('tar: corrupt archive (short read)')
ctx.exitCode = 1
return
}
const body = buf.subarray(off, off + size)
off += size
off += (BLK - (size % BLK)) % BLK
if (!name) continue
if (typeflag === '0' || typeflag === '\0' || typeflag === '') {
if (list) ctx.console.log(name)
if (extract) {
const cwd = vfs.resolveLogical('.')
const target =
cwd === '/' ? '/' + name : cwd.replace(/\/$/, '') + '/' + name
try {
await vfs.writeFile(target, b4.from(body))
} catch (e) {
ctx.console.error(
'tar: extract ' +
name +
': ' +
((e && /** @type {Error} */ (e).message) || String(e))
)
ctx.exitCode = 1
return
}
}
}
}
return
}
ctx.console.error('tar: specify -cf, -tf, or -xf')
ctx.exitCode = 1
}