///|
/// Random-write output: sequential body followed by one 96-byte header patch.
priv struct BuildOutput {
mut position : Int
write : (Int, Bytes) -> Unit raise SquashError
}
///|
fn BuildOutput::length(self : BuildOutput) -> Int {
self.position
}
///|
fn BuildOutput::append(
self : BuildOutput,
bytes : Bytes,
) -> Unit raise SquashError {
guard bytes.length() <= 2147483647 - self.position else {
raise Limit("image exceeds 2 GiB profile")
}
(self.write)(self.position, bytes)
self.position += bytes.length()
}
///|
fn BuildOutput::uint(
self : BuildOutput,
value : UInt,
) -> Unit raise SquashError {
let b : Array[Byte] = []
put32(b, value)
self.append(Bytes::from_array(b))
}
///|
fn BuildOutput::uint64(
self : BuildOutput,
value : UInt64,
) -> Unit raise SquashError {
self.append(value.to_le_bytes())
}
///|
/// Build a materialized image with a 128 MiB aggregate payload limit.
pub fn build_image(
entries : Array[BuildEntry],
block_size? : Int = 131072,
created? : UInt = 0U,
attributes? : Map[String, BuildMetadata] = Map([]),
compression? : Int = 1,
encode? : ((Int, Bytes) -> Bytes raise SquashError)? = None,
fragments? : Bool = true,
exportable? : Bool = true,
metadata_compression? : Bool = true,
) -> Bytes raise SquashError {
let bytes : Array[Byte] = []
let output : BuildOutput = {
position: 0,
write: (at, data) => {
if at == bytes.length() {
append_bytes(bytes, data)
} else {
guard at == 0 && data.length() == 96 && bytes.length() >= 96 else {
raise Invalid(at, "invalid header patch")
}
for i in 0..<96 {
bytes[i] = data[i]
}
}
},
}
ignore(
build_internal(
entries,
output,
None,
134217728UL,
block_size~,
created~,
attributes~,
compression~,
encode~,
fragments~,
exportable~,
metadata_compression~,
),
)
Bytes::from_array(bytes)
}
///|
/// Build to a random-write sink without retaining file payload or image bytes.
/// External(size) entries are read by image path, offset and requested length.
/// Sources must remain stable; callbacks may fail. Publish only after success.
pub fn build_to(
entries : Array[BuildEntry],
write : (Int, Bytes) -> Unit raise SquashError,
read : (String, Int, Int) -> Bytes raise SquashError,
block_size? : Int = 131072,
created? : UInt = 0U,
attributes? : Map[String, BuildMetadata] = Map([]),
compression? : Int = 1,
encode? : ((Int, Bytes) -> Bytes raise SquashError)? = None,
fragments? : Bool = true,
exportable? : Bool = true,
metadata_compression? : Bool = true,
max_data? : UInt64 = 4294967296UL,
) -> Superblock raise SquashError {
let output : BuildOutput = { position: 0, write, }
build_internal(
entries,
output,
Some(read),
max_data,
block_size~,
created~,
attributes~,
compression~,
encode~,
fragments~,
exportable~,
metadata_compression~,
)
}
///|
fn payload_size(content : BuildContent) -> Int {
match content {
Regular(bytes) => bytes.length()
External(size) => size
_ => 0
}
}
///|
fn flush_fragment(
output : BuildOutput,
tail : Array[Byte],
records : Array[Byte],
compression : Int,
encode : ((Int, Bytes) -> Bytes raise SquashError)?,
) -> Unit raise SquashError {
if tail.length() == 0 {
return
}
let at = output.length()
let descriptor = store_block(
output,
Bytes::from_array(tail),
compression,
encode,
)
put64(records, at.to_uint64())
put32(records, descriptor)
put32(records, 0U)
}