use std::fs::File;
use std::io::{self, Read, Seek, SeekFrom};
use std::path::Path;
use onelf_format::{EntryKind, FOOTER_SIZE, Flags, Footer, HostLibsPolicy, Manifest};
pub fn info(path: &Path) -> io::Result<()> {
let (footer, manifest) = read_footer_and_manifest(path)?;
println!("onelf binary: {}", path.display());
println!();
println!("Format version: {}", footer.format_version);
println!("Flags: {:?}", footer.flags);
println!(
"Host libs: {}",
HostLibsPolicy::from_flags(footer.flags).as_str()
);
println!(
"Cache: {}",
if footer.flags.contains(Flags::CACHE_PREFERRED) {
"preferred over every other mode"
} else if footer.flags.contains(Flags::CACHE_REQUESTED) {
"requested as last resort"
} else {
"only on request"
}
);
println!();
println!("Manifest:");
println!(" Offset: {}", footer.manifest_offset);
println!(" Compressed: {} bytes", footer.manifest_compressed);
println!(" Original: {} bytes", footer.manifest_original);
println!(
" Checksum: {:02x}{:02x}{:02x}{:02x}",
footer.manifest_checksum[0],
footer.manifest_checksum[1],
footer.manifest_checksum[2],
footer.manifest_checksum[3]
);
println!();
println!("Payload:");
println!(
" Format: {}",
if footer.is_stipple() {
"stipplefs image"
} else {
"blocks"
}
);
println!(" Offset: {}", footer.payload_offset);
println!(" Size: {} bytes", footer.payload_size);
println!();
if footer.dict_size > 0 {
println!("Dictionary:");
println!(" Offset: {}", footer.dict_offset);
println!(" Size: {} bytes", footer.dict_size);
println!();
}
let file_count = manifest
.entries
.iter()
.filter(|e| e.kind == EntryKind::File)
.count();
let dir_count = manifest
.entries
.iter()
.filter(|e| e.kind == EntryKind::Dir)
.count();
let symlink_count = manifest
.entries
.iter()
.filter(|e| e.kind == EntryKind::Symlink)
.count();
println!("Package ID: {}", hex(&manifest.header.package_id));
println!(
"Entries: {} ({} dirs, {} files, {} symlinks)",
manifest.header.entry_count, dir_count, file_count, symlink_count
);
println!();
if let Some(info) = read_package_info(path, &footer, &manifest)? {
println!("Metadata:");
for line in info.lines() {
println!(" {line}");
}
println!();
}
println!("Provenance:");
match read_metadata_string(
path,
&footer,
&manifest,
crate::bundle::sysroot::PROVENANCE_FILE,
)? {
None => println!(" none recorded (built from a host scan)"),
Some(text) => match parse_provenance(&text) {
Ok((platform, packages)) => {
println!(" Platform: {platform}");
for (name, version) in packages {
println!(" {name} {version}");
}
}
Err(e) => println!(" malformed record: {e}"),
},
}
if let Some(text) = read_metadata_string(
path,
&footer,
&manifest,
crate::bundle::sysroot::PLATFORM_FILE,
)? {
println!(" GL build:");
for line in text.lines() {
println!(" {line}");
}
}
if let Some(text) =
read_metadata_string(path, &footer, &manifest, crate::bundle::sysroot::SETS_FILE)?
{
println!(" Shared sets:");
for line in text.lines().filter(|l| !l.is_empty()) {
println!(" {line}");
}
}
println!();
if let Some(url) = read_metadata_string(path, &footer, &manifest, ".onelf/update-url")? {
let unsigned =
read_metadata_bytes(path, &footer, &manifest, ".onelf/update-unsigned")?.is_some();
let embedded_key = read_metadata_bytes(path, &footer, &manifest, ".onelf/update-key")?;
let key_desc = match embedded_key.as_deref().map(|b| b.len() / 32) {
None if unsigned => "none (updates are not signature-verified)".to_string(),
None => "none".to_string(),
Some(n) if n <= 1 => "embedded".to_string(),
Some(n) => format!("keyring of {n} keys"),
};
println!("Update:");
println!(" URL: {url}");
if let Some(rev) = read_metadata_string(path, &footer, &manifest, ".onelf/update-revision")?
{
println!(" Revision: {}", rev.trim());
}
println!(" Signing key: {key_desc}");
let updater = if footer.flags.contains(Flags::EXTERNAL_UPDATER) {
"external (this package does not update itself)".to_string()
} else if unsigned && embedded_key.is_some() {
"embedded, but refuses to update: a signing key and the \
unsigned marker contradict each other"
.to_string()
} else if unsigned {
"embedded (installs updates without signature verification)".to_string()
} else if embedded_key.is_some() {
"embedded".to_string()
} else {
"embedded, but disabled without a signing key".to_string()
};
println!(" Updater: {updater}");
println!();
}
println!("Entrypoints:");
for (i, ep) in manifest.entrypoints.iter().enumerate() {
let name = manifest.get_string(ep.name);
let target_path = manifest.entry_path(ep.target_entry as usize);
let args = manifest.get_string(ep.args);
let default_marker = if i == manifest.header.default_entrypoint as usize {
" (default)"
} else {
""
};
let memfd = if ep.is_memfd_eligible() {
" [memfd]"
} else {
""
};
print!(" {}{}{}: {}", name, default_marker, memfd, target_path);
if !args.is_empty() {
print!(" args={}", args.replace('\x1f', " "));
}
println!();
}
let total_original: u64 = manifest
.entries
.iter()
.filter(|e| e.kind == EntryKind::File)
.map(|e| e.blocks.iter().map(|b| b.original_size).sum::<u64>())
.sum();
println!();
println!(
"Total original size: {} bytes ({:.1} MB)",
total_original,
total_original as f64 / 1_048_576.0
);
println!(
"Total compressed size: {} bytes ({:.1} MB)",
footer.payload_size,
footer.payload_size as f64 / 1_048_576.0
);
if total_original > 0 {
let ratio = footer.payload_size as f64 / total_original as f64 * 100.0;
println!("Compression ratio: {:.1}%", ratio);
}
Ok(())
}
fn read_manifest<R: Read + Seek>(reader: &mut R, footer: &Footer) -> io::Result<Manifest> {
reader.seek(SeekFrom::Start(footer.manifest_offset))?;
let mut compressed = vec![0u8; footer.manifest_compressed as usize];
reader.read_exact(&mut compressed)?;
let bytes =
zstd::bulk::decompress(&compressed, footer.manifest_original as usize).map_err(|e| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("manifest decompression failed: {e}"),
)
})?;
if onelf_format::manifest::manifest_checksum(&bytes)? != footer.manifest_checksum {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"manifest checksum mismatch",
));
}
Manifest::deserialize(&bytes)
}
pub fn read_footer_and_manifest(path: &Path) -> io::Result<(Footer, Manifest)> {
let mut file = File::open(path)?;
let file_size = file.metadata()?.len();
if file_size < FOOTER_SIZE as u64 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"file too small for onelf footer",
));
}
file.seek(SeekFrom::End(-(FOOTER_SIZE as i64)))?;
let mut footer_buf = [0u8; FOOTER_SIZE];
file.read_exact(&mut footer_buf)?;
let footer = Footer::from_bytes(&footer_buf)?;
onelf_format::reader::validate_footer(&footer, file_size)?;
let manifest = read_manifest(&mut file, &footer)?;
Ok((footer, manifest))
}
fn parse_provenance(text: &str) -> Result<(String, Vec<(String, String)>), String> {
let value: toml::Value = toml::from_str(text).map_err(|e| e.to_string())?;
let platform = value
.get("platform")
.and_then(toml::Value::as_str)
.ok_or("no platform label")?
.to_string();
let mut packages = Vec::new();
if let Some(list) = value.get("package") {
let list = list.as_array().ok_or("package is not a list")?;
for entry in list {
let name = entry
.get("name")
.and_then(toml::Value::as_str)
.ok_or("package without a name")?;
let version = entry
.get("version")
.and_then(toml::Value::as_str)
.ok_or("package without a version")?;
packages.push((name.to_string(), version.to_string()));
}
}
Ok((platform, packages))
}
fn hex(bytes: &[u8]) -> String {
bytes.iter().map(|b| format!("{b:02x}")).collect()
}
fn read_metadata_string(
path: &Path,
footer: &Footer,
manifest: &Manifest,
entry_path: &str,
) -> io::Result<Option<String>> {
Ok(read_metadata_bytes(path, footer, manifest, entry_path)?
.map(|data| String::from_utf8_lossy(&data).trim().to_string()))
}
fn read_metadata_bytes(
path: &Path,
footer: &Footer,
manifest: &Manifest,
entry_path: &str,
) -> io::Result<Option<Vec<u8>>> {
use crate::extract::decompress_verified;
let idx = (0..manifest.entries.len()).find(|&i| {
manifest.entries[i].kind == EntryKind::File && manifest.entry_path(i) == entry_path
});
let Some(idx) = idx else { return Ok(None) };
let mut file = File::open(path)?;
let codec = onelf_format::payload::Codec::open(&file, footer)?;
Ok(Some(decompress_verified(
&mut file,
footer,
&manifest.entries[idx],
&codec,
)?))
}
fn read_package_info(
path: &Path,
footer: &Footer,
manifest: &Manifest,
) -> io::Result<Option<String>> {
use crate::extract::decompress_verified;
let idx = (0..manifest.entries.len()).find(|&i| {
manifest.entries[i].kind == EntryKind::File
&& manifest.entry_path(i) == ".onelf/package-info.toml"
});
let Some(idx) = idx else { return Ok(None) };
let mut file = File::open(path)?;
let codec = onelf_format::payload::Codec::open(&file, footer)?;
let data = decompress_verified(&mut file, footer, &manifest.entries[idx], &codec)?;
let text = String::from_utf8(data).unwrap_or_default();
Ok(Some(text))
}