use crate::block::BlockDevice;
use crate::fs::archive::{ArchiveEntry, ArchiveIndex, DataLocator, EntryKind, Method};
use crate::{Error, Result};
const HEADER_LEN: u64 = 60;
fn parse_field<T: std::str::FromStr>(field: &[u8], default: T) -> T {
std::str::from_utf8(field)
.ok()
.and_then(|s| s.trim().parse().ok())
.unwrap_or(default)
}
pub fn scan(dev: &mut dyn BlockDevice) -> Result<ArchiveIndex> {
let total = dev.total_size();
let mut magic = [0u8; 8];
dev.read_at(0, &mut magic)?;
if &magic != super::MAGIC {
return Err(Error::InvalidImage(
"ar: missing `!<arch>` global header".into(),
));
}
let mut idx = ArchiveIndex::new("ar");
let mut gnu_table: Option<Vec<u8>> = None;
let mut pos: u64 = 8;
while pos + HEADER_LEN <= total {
let mut hdr = [0u8; 60];
dev.read_at(pos, &mut hdr)?;
if &hdr[58..60] != b"`\n" {
break;
}
let raw_name = std::str::from_utf8(&hdr[0..16])
.map_err(|_| Error::InvalidImage("ar: non-UTF-8 name field".into()))?
.trim_end();
let size: u64 = parse_field(&hdr[48..58], u64::MAX);
if size == u64::MAX {
return Err(Error::InvalidImage("ar: unparseable size field".into()));
}
let mtime: u64 = parse_field(&hdr[16..28], 0);
let uid: u32 = parse_field(&hdr[28..34], 0);
let gid: u32 = parse_field(&hdr[34..40], 0);
let mode: u16 = std::str::from_utf8(&hdr[40..48])
.ok()
.and_then(|s| u32::from_str_radix(s.trim(), 8).ok())
.map(|m| (m & 0o7777) as u16)
.unwrap_or(0o644);
let data_off = pos + HEADER_LEN;
let next = data_off + size + (size & 1);
if raw_name == "//" {
let mut buf = vec![0u8; size as usize];
dev.read_at(data_off, &mut buf)?;
gnu_table = Some(buf);
pos = next;
continue;
}
if raw_name == "/" || raw_name == "/SYM64/" {
pos = next;
continue;
}
let (name, body_off, body_len) = if let Some(rest) = raw_name.strip_prefix("#1/") {
let nlen: u64 = rest
.trim()
.parse()
.map_err(|_| Error::InvalidImage("ar: bad #1/ name length".into()))?;
if nlen > size {
return Err(Error::InvalidImage(
"ar: #1/ name longer than member".into(),
));
}
let mut nbuf = vec![0u8; nlen as usize];
dev.read_at(data_off, &mut nbuf)?;
let nm = String::from_utf8_lossy(&nbuf)
.trim_end_matches('\0')
.to_string();
(nm, data_off + nlen, size - nlen)
} else if let Some(off_str) = raw_name.strip_prefix('/') {
let off: usize = off_str
.trim()
.parse()
.map_err(|_| Error::InvalidImage("ar: bad GNU long-name offset".into()))?;
let table = gnu_table
.as_ref()
.ok_or_else(|| Error::InvalidImage("ar: GNU name ref without // table".into()))?;
if off > table.len() {
return Err(Error::InvalidImage(
"ar: GNU name offset out of range".into(),
));
}
let end = table[off..]
.iter()
.position(|&b| b == b'\n' || b == b'/')
.map(|p| off + p)
.unwrap_or(table.len());
(
String::from_utf8_lossy(&table[off..end]).to_string(),
data_off,
size,
)
} else {
(
raw_name.strip_suffix('/').unwrap_or(raw_name).to_string(),
data_off,
size,
)
};
if name.is_empty() || name.starts_with("__.SYMDEF") {
pos = next;
continue;
}
idx.push(ArchiveEntry {
path: name,
kind: EntryKind::Regular,
mode,
uid,
gid,
mtime,
link_target: None,
device_major: 0,
device_minor: 0,
data: Some(DataLocator {
offset: body_off,
compressed_len: body_len,
uncompressed_len: body_len,
method: Method::Stored,
}),
});
pos = next;
}
Ok(idx)
}