Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support reading AIX big archive format #462

Merged
merged 5 commits into from
Sep 18, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions src/archive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ use crate::pod::Pod;
/// File identification bytes stored at the beginning of the file.
pub const MAGIC: [u8; 8] = *b"!<arch>\n";

/// File identification bytes at the beginning of AIX big archive.
pub const AIX_BIG_MAGIC: [u8; 8] = *b"<bigaf>\n";

/// File identification bytes stored at the beginning of a thin archive.
///
/// A thin archive only contains a symbol table and file names.
Expand Down Expand Up @@ -36,4 +39,57 @@ pub struct Header {
pub terminator: [u8; 2],
}

/// The header at the start of an AIX big archive member, without name.
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct AixHeader {
/// Member size in decimal.
pub size: [u8; 20],
/// Offset of next member in decimal.
pub next_member: [u8; 20],
/// Offset of previous member in decimal.
pub prev_member: [u8; 20],
/// File modification timestamp in decimal.
pub date: [u8; 12],
/// User ID in decimal.
pub uid: [u8; 12],
/// Group ID in decimal.
pub gid: [u8; 12],
/// File mode in octal.
pub mode: [u8; 12],
/// Name length in decimal.
pub name_length: [u8; 4],
}

/// Discriminated union for multiple type headers
#[derive(Debug, Clone, Copy)]
pub enum MemberHeader {
/// GNU or BSD style header
SystemV(Header),
/// AIX style big archive header
AixBig(AixHeader),
}

unsafe_impl_pod!(Header);
unsafe_impl_pod!(AixHeader);

/// The AIX big archive fixed len header.
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct AIXBigFixedHeader {
/// We read the magic number in advance , so don't put this in struct.
/// Offset to member table
pub memoffset: [u8; 20],
/// Offset to Global offset
pub globsymoffset: [u8; 20],
/// Offset to 64 bit Sym
pub globsym64offset: [u8; 20],
/// Offset to first Child
pub firstchildoffset: [u8; 20],
/// Offset to last child
pub lastchildoffset: [u8; 20],
/// Offset to free list
pub freeoffset: [u8; 20],
}

unsafe_impl_pod!(AIXBigFixedHeader);
172 changes: 153 additions & 19 deletions src/read/archive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ pub enum ArchiveKind {
Bsd64,
/// The Windows COFF archive format.
Coff,
/// The AIX big archive format.
AixBig,
}

/// A partially parsed archive file.
Expand All @@ -44,10 +46,48 @@ impl<'data, R: ReadRef<'data>> ArchiveFile<'data, R> {
let magic = data
.read_bytes(&mut tail, archive::MAGIC.len() as u64)
.read_error("Invalid archive size")?;
if magic != &archive::MAGIC[..] {
let mut kind_by_header = ArchiveKind::Unknown;
if magic == &archive::AIX_BIG_MAGIC[..] {
kind_by_header = ArchiveKind::AixBig;
} else if magic != &archive::MAGIC[..] {
return Err(Error("Unsupported archive identifier"));
}

if kind_by_header == ArchiveKind::AixBig {
// Parse the Fix Header to get member offset
let fixedheader = data
.read::<archive::AIXBigFixedHeader>(&mut tail)
.read_error("Invalid AIX big archive fixed header")?;
let firstchildoff = parse_u64_digits(&fixedheader.firstchildoffset, 10)
.read_error("Invalid first child offset")?;

// Move to firstchild
tail = firstchildoff;

let mut file = ArchiveFile {
data,
offset: tail,
len,
kind: kind_by_header,
symbols: (0, 0),
names: &[],
};

// Both the member table and the global symbol table exist as members of the archive and
// are kept at the end of the archive file.
let mut gst64off = parse_u64_digits(&fixedheader.globsym64offset, 10)
.read_error("Invalid global symbol64 table offset")?;
if gst64off == 0 {
// Empty archive has 0 for globsym64offset.
return Ok(file);
}

let member = ArchiveMember::parse(data, &mut gst64off, &[], file.kind)?;
file.symbols = member.file_range();

return Ok(file);
}

let mut file = ArchiveFile {
data,
offset: tail,
Expand All @@ -72,23 +112,23 @@ impl<'data, R: ReadRef<'data>> ArchiveFile<'data, R> {
// BSD may use the extended name for the symbol table. This is handled
// by `ArchiveMember::parse`.
if tail < len {
let member = ArchiveMember::parse(data, &mut tail, &[])?;
let member = ArchiveMember::parse(data, &mut tail, &[], file.kind)?;
if member.name == b"/" {
// GNU symbol table (unless we later determine this is COFF).
file.kind = ArchiveKind::Gnu;
file.symbols = member.file_range();
file.offset = tail;

if tail < len {
let member = ArchiveMember::parse(data, &mut tail, &[])?;
let member = ArchiveMember::parse(data, &mut tail, &[], file.kind)?;
if member.name == b"/" {
// COFF linker member.
file.kind = ArchiveKind::Coff;
file.symbols = member.file_range();
file.offset = tail;

if tail < len {
let member = ArchiveMember::parse(data, &mut tail, &[])?;
let member = ArchiveMember::parse(data, &mut tail, &[], file.kind)?;
if member.name == b"//" {
// COFF names table.
file.names = member.data(data)?;
Expand All @@ -108,7 +148,7 @@ impl<'data, R: ReadRef<'data>> ArchiveFile<'data, R> {
file.offset = tail;

if tail < len {
let member = ArchiveMember::parse(data, &mut tail, &[])?;
let member = ArchiveMember::parse(data, &mut tail, &[], file.kind)?;
if member.name == b"//" {
// GNU names table.
file.names = member.data(data)?;
Expand Down Expand Up @@ -153,6 +193,7 @@ impl<'data, R: ReadRef<'data>> ArchiveFile<'data, R> {
offset: self.offset,
len: self.len,
names: self.names,
kind: self.kind,
}
}
}
Expand All @@ -164,6 +205,7 @@ pub struct ArchiveMemberIterator<'data, R: ReadRef<'data> = &'data [u8]> {
offset: u64,
len: u64,
names: &'data [u8],
kind: ArchiveKind,
}

impl<'data, R: ReadRef<'data>> Iterator for ArchiveMemberIterator<'data, R> {
Expand All @@ -173,7 +215,7 @@ impl<'data, R: ReadRef<'data>> Iterator for ArchiveMemberIterator<'data, R> {
if self.offset >= self.len {
return None;
}
let member = ArchiveMember::parse(self.data, &mut self.offset, self.names);
let member = ArchiveMember::parse(self.data, &mut self.offset, self.names, self.kind);
if member.is_err() {
self.offset = self.len;
}
Expand All @@ -184,17 +226,60 @@ impl<'data, R: ReadRef<'data>> Iterator for ArchiveMemberIterator<'data, R> {
/// A partially parsed archive member.
#[derive(Debug)]
pub struct ArchiveMember<'data> {
header: &'data archive::Header,
header: archive::MemberHeader,
name: &'data [u8],
offset: u64,
size: u64,
}

impl<'data> ArchiveMember<'data> {
/// Parse the archive member header, name, and file data.
///
/// This reads the extended name (if any) and adjusts the file size.
fn parse<R: ReadRef<'data>>(
/// Parse with AIX big archive style.
fn parse_aixbig<R: ReadRef<'data>>(
data: R,
offset: &mut u64,
_names: &'data [u8],
) -> read::Result<Self> {
// The format was described at
// https://www.ibm.com/docs/en/aix/7.3?topic=formats-ar-file-format-big
let header = data
.read::<archive::AixHeader>(offset)
.read_error("Invalid AIX big archive member header")?;
let name_length = parse_u64_digits(&header.name_length, 10)
.read_error("Invalid archive member name length")?;
let name = data
.read_bytes(offset, name_length)
.read_error("Invalid archive member name")?;

// The actual data for a file member begins at the first even-byte boundary beyond the
// member header and continues for the number of bytes specified by the ar_size field. The
// ar command inserts null bytes for padding where necessary.
if *offset & 1 != 0 {
*offset = offset.saturating_add(1);
}
let terminator = data
.read_bytes(offset, 2)
.read_error("Invalid archive head terminator")?;
if terminator != archive::TERMINATOR {
return Err(Error("Invalid archive terminator"));
}
let file_offset = *offset;
let nextmbroff =
parse_u64_digits(&header.next_member, 10).read_error("Invalid next member offset")?;

// Move the offset to next member offset
*offset = nextmbroff;
let file_size =
parse_u64_digits(&header.size, 10).read_error("Invalid archive member size")?;
Ok(ArchiveMember {
header: archive::MemberHeader::AixBig(*header),
name,
offset: file_offset,
size: file_size,
})
}

/// Parse with SystemV style.
fn parse_systemv<R: ReadRef<'data>>(
data: R,
offset: &mut u64,
names: &'data [u8],
Expand All @@ -212,6 +297,7 @@ impl<'data> ArchiveMember<'data> {
*offset = offset
.checked_add(file_size)
.read_error("Archive member size is too large")?;

// Entries are padded to an even number of bytes.
if (file_size & 1) != 0 {
*offset = offset.saturating_add(1);
Expand All @@ -236,18 +322,33 @@ impl<'data> ArchiveMember<'data> {
};

Ok(ArchiveMember {
header,
header: archive::MemberHeader::SystemV(*header),
name,
offset: file_offset,
size: file_size,
})
}

/// Parse the archive member header, name, and file data.
///
/// This reads the extended name (if any) and adjusts the file size.
fn parse<R: ReadRef<'data>>(
data: R,
offset: &mut u64,
names: &'data [u8],
kind: ArchiveKind,
) -> read::Result<Self> {
match kind {
ArchiveKind::AixBig => Self::parse_aixbig(data, offset, &names),
_ => Self::parse_systemv(data, offset, &names),
}
}

/// Return the raw header.
#[inline]
pub fn header(&self) -> &'data archive::Header {
self.header
}
pub fn header(&self) -> &archive::MemberHeader {
&self.header
}

/// Return the parsed file name.
///
Expand All @@ -260,25 +361,37 @@ impl<'data> ArchiveMember<'data> {
/// Parse the file modification timestamp from the header.
#[inline]
pub fn date(&self) -> Option<u64> {
parse_u64_digits(&self.header.date, 10)
match &self.header {
archive::MemberHeader::AixBig(head) => parse_u64_digits(&head.date, 10),
archive::MemberHeader::SystemV(head) => parse_u64_digits(&head.date, 10),
}
}

/// Parse the user ID from the header.
#[inline]
pub fn uid(&self) -> Option<u64> {
parse_u64_digits(&self.header.uid, 10)
match &self.header {
archive::MemberHeader::AixBig(head) => parse_u64_digits(&head.uid, 10),
archive::MemberHeader::SystemV(head) => parse_u64_digits(&head.uid, 10),
}
}

/// Parse the group ID from the header.
#[inline]
pub fn gid(&self) -> Option<u64> {
parse_u64_digits(&self.header.gid, 10)
match &self.header {
archive::MemberHeader::AixBig(head) => parse_u64_digits(&head.gid, 10),
archive::MemberHeader::SystemV(head) => parse_u64_digits(&head.gid, 10),
}
}

/// Parse the file mode from the header.
#[inline]
pub fn mode(&self) -> Option<u64> {
parse_u64_digits(&self.header.mode, 8)
match &self.header {
archive::MemberHeader::AixBig(head) => parse_u64_digits(&head.mode, 8),
archive::MemberHeader::SystemV(head) => parse_u64_digits(&head.mode, 8),
}
}

/// Return the offset and size of the file data.
Expand Down Expand Up @@ -442,6 +555,27 @@ mod tests {
0000";
let archive = ArchiveFile::parse(&data[..]).unwrap();
assert_eq!(archive.kind(), ArchiveKind::Coff);

let data = b"\
<bigaf>\n\
0\x20\x20\x20\x20\x20\x20\x20\
\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x200\x20\x20\x20\
\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\
0\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\
\x20\x20\x20\x200\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\
\x20\x20\x20\x20\x20\x20\x20\x200\x20\x20\x20\x20\x20\x20\x20\
\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20128\x20\
\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\
6\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\
\x20\x20\x20\x20\x30\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\
\x20\x20\x20\x20\x20\x20\x20\x20\x30\x20\x20\x20\x20\x20\x20\x20\
\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0";
let archive = ArchiveFile::parse(&data[..]).unwrap();
assert_eq!(archive.kind(), ArchiveKind::AixBig);
}

#[test]
Expand Down