///|
pub const BINARY_HEADER_SIZE : Int32 = 26
///|
/// Binary cpio layout. The two variants differ in file type semantics, while
/// old and new little-endian headers share the same physical encoding.
pub(all) enum BinaryVariant {
Old
New
} derive(Eq, Debug)
///|
/// 16-bit word byte order used by new binary cpio.
pub(all) enum BinaryByteOrder {
Little
Big
} derive(Eq, Debug)
///|
/// Parsed binary cpio header fields.
pub(all) struct BinaryHeader {
variant : BinaryVariant
dev : UInt64
ino : UInt64
mode : UInt64
uid : UInt64
gid : UInt64
nlink : UInt64
rdev : UInt64
mtime : UInt64
file_size : UInt64
name_size : UInt64
} derive(Eq, Debug)
///|
/// One binary cpio entry.
pub(all) struct BinaryEntry {
name : String
name_bytes : Bytes
header : BinaryHeader
data : Bytes
} derive(Eq, Debug)
///|
/// A successfully parsed binary cpio archive.
pub(all) struct BinaryArchive {
variant : BinaryVariant
byte_order : BinaryByteOrder
entries : Array[BinaryEntry]
} derive(Eq, Debug)
///|
fn binary_padding(length : Int32) -> Int32 {
length % 2
}
///|
fn read_u16(
input : BytesView,
offset : Int32,
byte_order : BinaryByteOrder,
) -> UInt64 {
let first = input[offset].to_uint64()
let second = input[offset + 1].to_uint64()
if byte_order == Little {
first | (second << 8)
} else {
(first << 8) | second
}
}
///|
fn read_u32(
input : BytesView,
offset : Int32,
byte_order : BinaryByteOrder,
) -> UInt64 {
let high = read_u16(input, offset, byte_order)
let low = read_u16(input, offset + 2, byte_order)
(high << 16) | low
}
///|
fn parse_binary_header(
header : BytesView,
variant : BinaryVariant,
byte_order : BinaryByteOrder,
archive_offset : Int32,
) -> Result[BinaryHeader, CpioError] {
if read_u16(header, 0, byte_order) != 0x71C7UL {
return Err(UnsupportedMagic(archive_offset))
}
Ok({
variant,
dev: read_u16(header, 2, byte_order),
ino: read_u16(header, 4, byte_order),
mode: read_u16(header, 6, byte_order),
uid: read_u16(header, 8, byte_order),
gid: read_u16(header, 10, byte_order),
nlink: read_u16(header, 12, byte_order),
rdev: read_u16(header, 14, byte_order),
mtime: read_u32(header, 16, byte_order),
name_size: read_u16(header, 20, byte_order),
file_size: read_u32(header, 22, byte_order),
})
}
///|
fn parse_binary(
input : Bytes,
variant : BinaryVariant,
byte_order : BinaryByteOrder,
) -> Result[BinaryArchive, CpioError] {
let entries : Array[BinaryEntry] = []
let mut offset = 0
while true {
let header_end = offset + BINARY_HEADER_SIZE
if header_end > input.length() {
return Err(TruncatedHeader(offset))
}
let header = match
parse_binary_header(input[offset:header_end], variant, byte_order, offset) {
Ok(header) => header
Err(error) => return Err(error)
}
let name_start = header_end
let remaining = (input.length() - name_start).to_uint64()
if header.name_size == 0UL || header.name_size > remaining {
return Err(InvalidNameSize(offset))
}
let name_size = header.name_size.to_int()
let name_end = name_start + name_size
if input[name_end - 1] != b'\x00' {
return Err(MissingNameTerminator(name_start))
}
let name_view = input[name_start:name_end - 1]
for i = 0; i < name_view.length(); i = i + 1 {
if name_view[i] == b'\x00' {
return Err(EmbeddedNul(name_start + i))
}
}
let name_bytes = name_view.to_owned()
let name = @utf8.decode(name_view) catch {
_ => return Err(InvalidUtf8Name(name_start))
}
let data_start = name_end + binary_padding(name_size)
if data_start > input.length() {
return Err(TruncatedName(name_end))
}
if name == "TRAILER!!!" {
if header.file_size != 0UL {
return Err(InvalidTrailer(offset))
}
return Ok({ variant, byte_order, entries, })
}
let data_remaining = (input.length() - data_start).to_uint64()
if header.file_size > data_remaining {
return Err(TruncatedData(data_start))
}
let file_size = header.file_size.to_int()
let data_end = data_start + file_size
let next_offset = data_end + binary_padding(file_size)
if next_offset > input.length() {
return Err(TruncatedData(data_end))
}
let data = input[data_start:data_end].to_owned()
entries.push({ name, name_bytes, header, data, })
offset = next_offset
} nobreak {
abort("unreachable: parse_binary always returns")
}
}
///|
/// Parses a PWB old binary cpio archive with PDP little-endian words.
pub fn parse_binary_old(input : Bytes) -> Result[BinaryArchive, CpioError] {
parse_binary(input, Old, Little)
}
///|
/// Parses a new binary cpio archive with caller-specified word byte order.
///
/// Auto-detection between old and new layouts is intentionally not attempted:
/// the formats use the same magic and can share identical little-endian bytes.
pub fn parse_binary_new(
input : Bytes,
byte_order : BinaryByteOrder,
) -> Result[BinaryArchive, CpioError] {
parse_binary(input, New, byte_order)
}