///|
pub const ODC_HEADER_SIZE : Int32 = 76
///|
/// Parsed `odc` header fields.
pub(all) struct OdcHeader {
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 `odc` archive entry.
pub(all) struct OdcEntry {
name : String
name_bytes : Bytes
header : OdcHeader
data : Bytes
} derive(Eq, Debug)
///|
/// A successfully parsed `odc` archive.
pub(all) struct OdcArchive {
entries : Array[OdcEntry]
} derive(Eq, Debug)
///|
fn octal_value(byte : Byte) -> UInt64? {
if byte >= b'0' && byte <= b'7' {
Some(byte.to_uint64() - b'0'.to_uint64())
} else {
None
}
}
///|
fn parse_octal_field(
header : BytesView,
start : Int32,
width : Int32,
archive_offset : Int32,
) -> Result[UInt64, CpioError] {
let mut value = 0UL
for i = 0; i < width; i = i + 1 {
let offset = start + i
let digit = match octal_value(header[offset]) {
Some(value) => value
None => return Err(InvalidOctal(archive_offset + offset))
}
value = (value << 3) | digit
}
Ok(value)
}
///|
fn parse_odc_header(
header : BytesView,
archive_offset : Int32,
) -> Result[OdcHeader, CpioError] {
if header[0:6] != b"070707" {
return Err(UnsupportedMagic(archive_offset))
}
let layout : Array[(Int32, Int32)] = [
(6, 6),
(12, 6),
(18, 6),
(24, 6),
(30, 6),
(36, 6),
(42, 6),
(48, 11),
(59, 6),
(65, 11),
]
let fields : Array[UInt64] = []
for (start, width) in layout {
let value = match parse_octal_field(header, start, width, archive_offset) {
Ok(value) => value
Err(error) => return Err(error)
}
fields.push(value)
}
Ok({
dev: fields[0],
ino: fields[1],
mode: fields[2],
uid: fields[3],
gid: fields[4],
nlink: fields[5],
rdev: fields[6],
mtime: fields[7],
name_size: fields[8],
file_size: fields[9],
})
}
///|
/// Parses a portable ASCII `odc` cpio archive (`070707`).
///
/// The parser is read-only. `odc` has no padding between the name, data, or
/// following header, so every entry starts immediately after the prior data.
pub fn parse_odc(input : Bytes) -> Result[OdcArchive, CpioError] {
let entries : Array[OdcEntry] = []
let mut offset = 0
while true {
let header_end = offset + ODC_HEADER_SIZE
if header_end > input.length() {
return Err(TruncatedHeader(offset))
}
let header = match parse_odc_header(input[offset:header_end], 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))
}
if name == "TRAILER!!!" {
if header.file_size != 0UL {
return Err(InvalidTrailer(offset))
}
return Ok({ entries, })
}
let data_remaining = (input.length() - name_end).to_uint64()
if header.file_size > data_remaining {
return Err(TruncatedData(name_end))
}
let data_end = name_end + header.file_size.to_int()
let data = input[name_end:data_end].to_owned()
entries.push({ name, name_bytes, header, data, })
offset = data_end
} nobreak {
abort("unreachable: parse_odc always returns")
}
}