///|
/// Errors reported while writing deterministic newc archives.
///
/// `FieldOverflow` carries the newc field index: ino(0), mode(1), uid(2),
/// gid(3), mtime(4), file_size(5), or name_size(6).
pub(all) enum WriteError {
EmptyName
EmbeddedNul(Int32)
FieldOverflow(Int32)
} derive(Eq, Debug)
///|
/// Minimal deterministic newc entry input.
pub(all) struct NewcWriteEntry {
name : String
data : Bytes
mode : UInt64
uid : UInt64
gid : UInt64
mtime : UInt64
} derive(Eq, Debug)
///|
pub fn NewcWriteEntry::new(
name : String,
data : Bytes,
mode~ : UInt64 = 0x81A4UL,
uid~ : UInt64 = 0UL,
gid~ : UInt64 = 0UL,
mtime~ : UInt64 = 0UL,
) -> NewcWriteEntry {
{ name, data, mode, uid, gid, mtime, }
}
///|
fn write_hex8(buffer : Buffer, value : UInt64) -> Unit {
buffer.write_string_utf8(value.to_string(radix=16).pad_start(8, '0'))
}
///|
fn write_zero_bytes(buffer : Buffer, count : Int32) -> Unit {
for i = 0; i < count; i = i + 1 {
buffer.write_byte(b'\x00')
}
}
///|
fn check_u32(value : UInt64, field_index : Int32) -> Result[Unit, WriteError] {
if value > 0xFFFFFFFFUL {
Err(FieldOverflow(field_index))
} else {
Ok(())
}
}
///|
fn write_newc_header(
buffer : Buffer,
format : NewcFormat,
ino : UInt64,
mode : UInt64,
uid : UInt64,
gid : UInt64,
nlink : UInt64,
mtime : UInt64,
file_size : UInt64,
name_size : UInt64,
check : UInt64,
) -> Unit {
buffer.write_string_utf8(
if format == NewcFormat::Newc {
"070701"
} else {
"070702"
},
)
write_hex8(buffer, ino)
write_hex8(buffer, mode)
write_hex8(buffer, uid)
write_hex8(buffer, gid)
write_hex8(buffer, nlink)
write_hex8(buffer, mtime)
write_hex8(buffer, file_size)
write_hex8(buffer, 0UL)
write_hex8(buffer, 0UL)
write_hex8(buffer, 0UL)
write_hex8(buffer, 0UL)
write_hex8(buffer, name_size)
write_hex8(buffer, check)
}
///|
/// Writes a deterministic `070701` or `070702` newc archive.
///
/// Inodes are assigned sequentially from 1, device fields are zero, and the
/// caller supplies mode, uid, gid, and mtime. No wall clock or random value is
/// read, so identical inputs always produce identical bytes.
pub fn write_newc(
entries : ArrayView[NewcWriteEntry],
format~ : NewcFormat = NewcFormat::Newc,
) -> Result[Bytes, WriteError] {
let buffer = @buffer.Buffer()
for index = 0; index < entries.length(); index = index + 1 {
let entry = entries[index]
let name_bytes = @utf8.encode(entry.name)
if name_bytes.is_empty() {
return Err(EmptyName)
}
for i = 0; i < name_bytes.length(); i = i + 1 {
if name_bytes[i] == b'\x00' {
return Err(EmbeddedNul(i))
}
}
let name_size = name_bytes.length() + 1
let file_size = entry.data.length()
let ino = (index + 1).to_uint64()
let name_size_u = name_size.to_uint64()
let file_size_u = file_size.to_uint64()
let checks = [
check_u32(ino, 0),
check_u32(entry.mode, 1),
check_u32(entry.uid, 2),
check_u32(entry.gid, 3),
check_u32(entry.mtime, 4),
check_u32(file_size_u, 5),
check_u32(name_size_u, 6),
]
for result in checks {
match result {
Ok(_) => ()
Err(error) => return Err(error)
}
}
let check = if format == NewcFormat::NewcCrc {
newc_checksum(entry.data[:])
} else {
0UL
}
write_newc_header(
buffer,
format,
ino,
entry.mode,
entry.uid,
entry.gid,
1UL,
entry.mtime,
file_size_u,
name_size_u,
check,
)
buffer.write_bytes(name_bytes)
buffer.write_byte(b'\x00')
write_zero_bytes(buffer, newc_name_padding(name_size))
buffer.write_bytes(entry.data)
write_zero_bytes(buffer, padding_for(file_size))
}
write_newc_header(
buffer, format, 0UL, 0UL, 0UL, 0UL, 1UL, 0UL, 0UL, 11UL, 0UL,
)
buffer.write_bytes(b"TRAILER!!!\x00")
write_zero_bytes(buffer, newc_name_padding(11))
Ok(buffer.to_bytes())
}