(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).
299 lines
7.4 KiB
JavaScript
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
|
|
}
|