760 lines
27 KiB
Rust
760 lines
27 KiB
Rust
//! Support for archive files.
|
|
//!
|
|
//! ## Example
|
|
//! ```no_run
|
|
//! use object::{Object, ObjectSection};
|
|
//! use std::error::Error;
|
|
//! use std::fs;
|
|
//!
|
|
//! /// Reads an archive and displays the name of each member.
|
|
//! fn main() -> Result<(), Box<dyn Error>> {
|
|
//! # #[cfg(feature = "std")] {
|
|
//! let data = fs::read("path/to/binary")?;
|
|
//! let file = object::read::archive::ArchiveFile::parse(&*data)?;
|
|
//! for member in file.members() {
|
|
//! let member = member?;
|
|
//! println!("{}", String::from_utf8_lossy(member.name()));
|
|
//! }
|
|
//! # }
|
|
//! Ok(())
|
|
//! }
|
|
//! ```
|
|
|
|
use core::convert::TryInto;
|
|
|
|
use crate::archive;
|
|
use crate::read::{self, Bytes, Error, ReadError, ReadRef};
|
|
|
|
/// The kind of archive format.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
#[non_exhaustive]
|
|
pub enum ArchiveKind {
|
|
/// There are no special files that indicate the archive format.
|
|
Unknown,
|
|
/// The GNU (or System V) archive format.
|
|
Gnu,
|
|
/// The GNU (or System V) archive format with 64-bit symbol table.
|
|
Gnu64,
|
|
/// The BSD archive format.
|
|
Bsd,
|
|
/// The BSD archive format with 64-bit symbol table.
|
|
///
|
|
/// This is used for Darwin.
|
|
Bsd64,
|
|
/// The Windows COFF archive format.
|
|
Coff,
|
|
/// The AIX big archive format.
|
|
AixBig,
|
|
}
|
|
|
|
/// The list of members in the archive.
|
|
#[derive(Debug, Clone, Copy)]
|
|
enum Members<'data> {
|
|
Common {
|
|
offset: u64,
|
|
end_offset: u64,
|
|
},
|
|
AixBig {
|
|
index: &'data [archive::AixMemberOffset],
|
|
},
|
|
}
|
|
|
|
/// A partially parsed archive file.
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct ArchiveFile<'data, R: ReadRef<'data> = &'data [u8]> {
|
|
data: R,
|
|
kind: ArchiveKind,
|
|
members: Members<'data>,
|
|
symbols: (u64, u64),
|
|
names: &'data [u8],
|
|
}
|
|
|
|
impl<'data, R: ReadRef<'data>> ArchiveFile<'data, R> {
|
|
/// Parse the archive header and special members.
|
|
pub fn parse(data: R) -> read::Result<Self> {
|
|
let len = data.len().read_error("Unknown archive length")?;
|
|
let mut tail = 0;
|
|
let magic = data
|
|
.read_bytes(&mut tail, archive::MAGIC.len() as u64)
|
|
.read_error("Invalid archive size")?;
|
|
|
|
if magic == archive::AIX_BIG_MAGIC {
|
|
return Self::parse_aixbig(data);
|
|
} else if magic != archive::MAGIC {
|
|
return Err(Error("Unsupported archive identifier"));
|
|
}
|
|
|
|
let mut members_offset = tail;
|
|
let members_end_offset = len;
|
|
|
|
let mut file = ArchiveFile {
|
|
data,
|
|
kind: ArchiveKind::Unknown,
|
|
members: Members::Common {
|
|
offset: 0,
|
|
end_offset: 0,
|
|
},
|
|
symbols: (0, 0),
|
|
names: &[],
|
|
};
|
|
|
|
// The first few members may be special, so parse them.
|
|
// GNU has:
|
|
// - "/" or "/SYM64/": symbol table (optional)
|
|
// - "//": names table (optional)
|
|
// COFF has:
|
|
// - "/": first linker member
|
|
// - "/": second linker member
|
|
// - "//": names table
|
|
// BSD has:
|
|
// - "__.SYMDEF" or "__.SYMDEF SORTED": symbol table (optional)
|
|
// BSD 64-bit has:
|
|
// - "__.SYMDEF_64" or "__.SYMDEF_64 SORTED": symbol table (optional)
|
|
// 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, &[])?;
|
|
if member.name == b"/" {
|
|
// GNU symbol table (unless we later determine this is COFF).
|
|
file.kind = ArchiveKind::Gnu;
|
|
file.symbols = member.file_range();
|
|
members_offset = tail;
|
|
|
|
if tail < len {
|
|
let member = ArchiveMember::parse(data, &mut tail, &[])?;
|
|
if member.name == b"/" {
|
|
// COFF linker member.
|
|
file.kind = ArchiveKind::Coff;
|
|
file.symbols = member.file_range();
|
|
members_offset = tail;
|
|
|
|
if tail < len {
|
|
let member = ArchiveMember::parse(data, &mut tail, &[])?;
|
|
if member.name == b"//" {
|
|
// COFF names table.
|
|
file.names = member.data(data)?;
|
|
members_offset = tail;
|
|
}
|
|
}
|
|
} else if member.name == b"//" {
|
|
// GNU names table.
|
|
file.names = member.data(data)?;
|
|
members_offset = tail;
|
|
}
|
|
}
|
|
} else if member.name == b"/SYM64/" {
|
|
// GNU 64-bit symbol table.
|
|
file.kind = ArchiveKind::Gnu64;
|
|
file.symbols = member.file_range();
|
|
members_offset = tail;
|
|
|
|
if tail < len {
|
|
let member = ArchiveMember::parse(data, &mut tail, &[])?;
|
|
if member.name == b"//" {
|
|
// GNU names table.
|
|
file.names = member.data(data)?;
|
|
members_offset = tail;
|
|
}
|
|
}
|
|
} else if member.name == b"//" {
|
|
// GNU names table.
|
|
file.kind = ArchiveKind::Gnu;
|
|
file.names = member.data(data)?;
|
|
members_offset = tail;
|
|
} else if member.name == b"__.SYMDEF" || member.name == b"__.SYMDEF SORTED" {
|
|
// BSD symbol table.
|
|
file.kind = ArchiveKind::Bsd;
|
|
file.symbols = member.file_range();
|
|
members_offset = tail;
|
|
} else if member.name == b"__.SYMDEF_64" || member.name == b"__.SYMDEF_64 SORTED" {
|
|
// BSD 64-bit symbol table.
|
|
file.kind = ArchiveKind::Bsd64;
|
|
file.symbols = member.file_range();
|
|
members_offset = tail;
|
|
} else {
|
|
// TODO: This could still be a BSD file. We leave this as unknown for now.
|
|
}
|
|
}
|
|
file.members = Members::Common {
|
|
offset: members_offset,
|
|
end_offset: members_end_offset,
|
|
};
|
|
Ok(file)
|
|
}
|
|
|
|
fn parse_aixbig(data: R) -> read::Result<Self> {
|
|
let mut tail = 0;
|
|
|
|
let file_header = data
|
|
.read::<archive::AixFileHeader>(&mut tail)
|
|
.read_error("Invalid AIX big archive file header")?;
|
|
// Caller already validated this.
|
|
debug_assert_eq!(file_header.magic, archive::AIX_BIG_MAGIC);
|
|
|
|
let mut file = ArchiveFile {
|
|
data,
|
|
kind: ArchiveKind::AixBig,
|
|
members: Members::AixBig { index: &[] },
|
|
symbols: (0, 0),
|
|
names: &[],
|
|
};
|
|
|
|
// Read the span of symbol table.
|
|
let symtbl64 = parse_u64_digits(&file_header.gst64off, 10)
|
|
.read_error("Invalid offset to 64-bit symbol table in AIX big archive")?;
|
|
if symtbl64 > 0 {
|
|
// The symbol table is also a file with header.
|
|
let member = ArchiveMember::parse_aixbig(data, symtbl64)?;
|
|
file.symbols = member.file_range();
|
|
} else {
|
|
let symtbl = parse_u64_digits(&file_header.gstoff, 10)
|
|
.read_error("Invalid offset to symbol table in AIX big archive")?;
|
|
if symtbl > 0 {
|
|
// The symbol table is also a file with header.
|
|
let member = ArchiveMember::parse_aixbig(data, symtbl)?;
|
|
file.symbols = member.file_range();
|
|
}
|
|
}
|
|
|
|
// Big archive member index table lists file entries with offsets and names.
|
|
// To avoid potential infinite loop (members are double-linked list), the
|
|
// iterator goes through the index instead of real members.
|
|
let member_table_offset = parse_u64_digits(&file_header.memoff, 10)
|
|
.read_error("Invalid offset for member table of AIX big archive")?;
|
|
if member_table_offset == 0 {
|
|
// The offset would be zero if archive contains no file.
|
|
return Ok(file);
|
|
}
|
|
|
|
// The member index table is also a file with header.
|
|
let member = ArchiveMember::parse_aixbig(data, member_table_offset)?;
|
|
let mut member_data = Bytes(member.data(data)?);
|
|
|
|
// Structure of member index table:
|
|
// Number of entries (20 bytes)
|
|
// Offsets of each entry (20*N bytes)
|
|
// Names string table (the rest of bytes to fill size defined in header)
|
|
let members_count_bytes = member_data
|
|
.read_slice::<u8>(20)
|
|
.read_error("Missing member count in AIX big archive")?;
|
|
let members_count = parse_u64_digits(members_count_bytes, 10)
|
|
.and_then(|size| size.try_into().ok())
|
|
.read_error("Invalid member count in AIX big archive")?;
|
|
let index = member_data
|
|
.read_slice::<archive::AixMemberOffset>(members_count)
|
|
.read_error("Member count overflow in AIX big archive")?;
|
|
file.members = Members::AixBig { index };
|
|
|
|
Ok(file)
|
|
}
|
|
|
|
/// Return the archive format.
|
|
#[inline]
|
|
pub fn kind(&self) -> ArchiveKind {
|
|
self.kind
|
|
}
|
|
|
|
/// Iterate over the members of the archive.
|
|
///
|
|
/// This does not return special members.
|
|
#[inline]
|
|
pub fn members(&self) -> ArchiveMemberIterator<'data, R> {
|
|
ArchiveMemberIterator {
|
|
data: self.data,
|
|
members: self.members,
|
|
names: self.names,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An iterator over the members of an archive.
|
|
#[derive(Debug)]
|
|
pub struct ArchiveMemberIterator<'data, R: ReadRef<'data> = &'data [u8]> {
|
|
data: R,
|
|
members: Members<'data>,
|
|
names: &'data [u8],
|
|
}
|
|
|
|
impl<'data, R: ReadRef<'data>> Iterator for ArchiveMemberIterator<'data, R> {
|
|
type Item = read::Result<ArchiveMember<'data>>;
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
match &mut self.members {
|
|
Members::Common {
|
|
ref mut offset,
|
|
ref mut end_offset,
|
|
} => {
|
|
if *offset >= *end_offset {
|
|
return None;
|
|
}
|
|
let member = ArchiveMember::parse(self.data, offset, self.names);
|
|
if member.is_err() {
|
|
*offset = *end_offset;
|
|
}
|
|
Some(member)
|
|
}
|
|
Members::AixBig { ref mut index } => match **index {
|
|
[] => None,
|
|
[ref first, ref rest @ ..] => {
|
|
*index = rest;
|
|
let member = ArchiveMember::parse_aixbig_index(self.data, first);
|
|
if member.is_err() {
|
|
*index = &[];
|
|
}
|
|
Some(member)
|
|
}
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An archive member header.
|
|
#[derive(Debug, Clone, Copy)]
|
|
enum MemberHeader<'data> {
|
|
/// Common header used by many formats.
|
|
Common(&'data archive::Header),
|
|
/// AIX big archive header
|
|
AixBig(&'data archive::AixHeader),
|
|
}
|
|
|
|
/// A partially parsed archive member.
|
|
#[derive(Debug)]
|
|
pub struct ArchiveMember<'data> {
|
|
header: MemberHeader<'data>,
|
|
name: &'data [u8],
|
|
offset: u64,
|
|
size: u64,
|
|
}
|
|
|
|
impl<'data> ArchiveMember<'data> {
|
|
/// Parse the member header, name, and file data in an archive with the common format.
|
|
///
|
|
/// 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],
|
|
) -> read::Result<Self> {
|
|
let header = data
|
|
.read::<archive::Header>(offset)
|
|
.read_error("Invalid archive member header")?;
|
|
if header.terminator != archive::TERMINATOR {
|
|
return Err(Error("Invalid archive terminator"));
|
|
}
|
|
|
|
let mut file_offset = *offset;
|
|
let mut file_size =
|
|
parse_u64_digits(&header.size, 10).read_error("Invalid archive member size")?;
|
|
*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);
|
|
}
|
|
|
|
let name = if header.name[0] == b'/' && (header.name[1] as char).is_ascii_digit() {
|
|
// Read file name from the names table.
|
|
parse_sysv_extended_name(&header.name[1..], names)
|
|
.read_error("Invalid archive extended name offset")?
|
|
} else if &header.name[..3] == b"#1/" && (header.name[3] as char).is_ascii_digit() {
|
|
// Read file name from the start of the file data.
|
|
parse_bsd_extended_name(&header.name[3..], data, &mut file_offset, &mut file_size)
|
|
.read_error("Invalid archive extended name length")?
|
|
} else if header.name[0] == b'/' {
|
|
let name_len = memchr::memchr(b' ', &header.name).unwrap_or(header.name.len());
|
|
&header.name[..name_len]
|
|
} else {
|
|
let name_len = memchr::memchr(b'/', &header.name)
|
|
.or_else(|| memchr::memchr(b' ', &header.name))
|
|
.unwrap_or(header.name.len());
|
|
&header.name[..name_len]
|
|
};
|
|
|
|
Ok(ArchiveMember {
|
|
header: MemberHeader::Common(header),
|
|
name,
|
|
offset: file_offset,
|
|
size: file_size,
|
|
})
|
|
}
|
|
|
|
/// Parse a member index entry in an AIX big archive,
|
|
/// and then parse the member header, name, and file data.
|
|
fn parse_aixbig_index<R: ReadRef<'data>>(
|
|
data: R,
|
|
index: &archive::AixMemberOffset,
|
|
) -> read::Result<Self> {
|
|
let offset = parse_u64_digits(&index.0, 10)
|
|
.read_error("Invalid AIX big archive file member offset")?;
|
|
Self::parse_aixbig(data, offset)
|
|
}
|
|
|
|
/// Parse the member header, name, and file data in an AIX big archive.
|
|
fn parse_aixbig<R: ReadRef<'data>>(data: R, mut offset: u64) -> 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>(&mut offset)
|
|
.read_error("Invalid AIX big archive member header")?;
|
|
let name_length = parse_u64_digits(&header.namlen, 10)
|
|
.read_error("Invalid AIX big archive member name length")?;
|
|
let name = data
|
|
.read_bytes(&mut offset, name_length)
|
|
.read_error("Invalid AIX big 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);
|
|
}
|
|
// Because of the even-byte boundary, we have to read and check terminator after header.
|
|
let terminator = data
|
|
.read_bytes(&mut offset, 2)
|
|
.read_error("Invalid AIX big archive terminator")?;
|
|
if terminator != archive::TERMINATOR {
|
|
return Err(Error("Invalid AIX big archive terminator"));
|
|
}
|
|
|
|
let size = parse_u64_digits(&header.size, 10)
|
|
.read_error("Invalid archive member size in AIX big archive")?;
|
|
Ok(ArchiveMember {
|
|
header: MemberHeader::AixBig(header),
|
|
name,
|
|
offset,
|
|
size,
|
|
})
|
|
}
|
|
|
|
/// Return the raw header that is common to many archive formats.
|
|
///
|
|
/// Returns `None` if this archive does not use the common header format.
|
|
#[inline]
|
|
pub fn header(&self) -> Option<&'data archive::Header> {
|
|
match self.header {
|
|
MemberHeader::Common(header) => Some(header),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// Return the raw header for AIX big archives.
|
|
///
|
|
/// Returns `None` if this is not an AIX big archive.
|
|
#[inline]
|
|
pub fn aix_header(&self) -> Option<&'data archive::AixHeader> {
|
|
match self.header {
|
|
MemberHeader::AixBig(header) => Some(header),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// Return the parsed file name.
|
|
///
|
|
/// This may be an extended file name.
|
|
#[inline]
|
|
pub fn name(&self) -> &'data [u8] {
|
|
self.name
|
|
}
|
|
|
|
/// Parse the file modification timestamp from the header.
|
|
#[inline]
|
|
pub fn date(&self) -> Option<u64> {
|
|
match &self.header {
|
|
MemberHeader::Common(header) => parse_u64_digits(&header.date, 10),
|
|
MemberHeader::AixBig(header) => parse_u64_digits(&header.date, 10),
|
|
}
|
|
}
|
|
|
|
/// Parse the user ID from the header.
|
|
#[inline]
|
|
pub fn uid(&self) -> Option<u64> {
|
|
match &self.header {
|
|
MemberHeader::Common(header) => parse_u64_digits(&header.uid, 10),
|
|
MemberHeader::AixBig(header) => parse_u64_digits(&header.uid, 10),
|
|
}
|
|
}
|
|
|
|
/// Parse the group ID from the header.
|
|
#[inline]
|
|
pub fn gid(&self) -> Option<u64> {
|
|
match &self.header {
|
|
MemberHeader::Common(header) => parse_u64_digits(&header.gid, 10),
|
|
MemberHeader::AixBig(header) => parse_u64_digits(&header.gid, 10),
|
|
}
|
|
}
|
|
|
|
/// Parse the file mode from the header.
|
|
#[inline]
|
|
pub fn mode(&self) -> Option<u64> {
|
|
match &self.header {
|
|
MemberHeader::Common(header) => parse_u64_digits(&header.mode, 8),
|
|
MemberHeader::AixBig(header) => parse_u64_digits(&header.mode, 8),
|
|
}
|
|
}
|
|
|
|
/// Return the offset and size of the file data.
|
|
pub fn file_range(&self) -> (u64, u64) {
|
|
(self.offset, self.size)
|
|
}
|
|
|
|
/// Return the file data.
|
|
#[inline]
|
|
pub fn data<R: ReadRef<'data>>(&self, data: R) -> read::Result<&'data [u8]> {
|
|
data.read_bytes_at(self.offset, self.size)
|
|
.read_error("Archive member size is too large")
|
|
}
|
|
}
|
|
|
|
// Ignores bytes starting from the first space.
|
|
fn parse_u64_digits(digits: &[u8], radix: u32) -> Option<u64> {
|
|
if let [b' ', ..] = digits {
|
|
return None;
|
|
}
|
|
let mut result: u64 = 0;
|
|
for &c in digits {
|
|
if c == b' ' {
|
|
return Some(result);
|
|
} else {
|
|
let x = (c as char).to_digit(radix)?;
|
|
result = result
|
|
.checked_mul(u64::from(radix))?
|
|
.checked_add(u64::from(x))?;
|
|
}
|
|
}
|
|
Some(result)
|
|
}
|
|
|
|
fn parse_sysv_extended_name<'data>(digits: &[u8], names: &'data [u8]) -> Result<&'data [u8], ()> {
|
|
let offset = parse_u64_digits(digits, 10).ok_or(())?;
|
|
let offset = offset.try_into().map_err(|_| ())?;
|
|
let name_data = names.get(offset..).ok_or(())?;
|
|
let name = match memchr::memchr2(b'/', b'\0', name_data) {
|
|
Some(len) => &name_data[..len],
|
|
None => name_data,
|
|
};
|
|
Ok(name)
|
|
}
|
|
|
|
/// Modifies `data` to start after the extended name.
|
|
fn parse_bsd_extended_name<'data, R: ReadRef<'data>>(
|
|
digits: &[u8],
|
|
data: R,
|
|
offset: &mut u64,
|
|
size: &mut u64,
|
|
) -> Result<&'data [u8], ()> {
|
|
let len = parse_u64_digits(digits, 10).ok_or(())?;
|
|
*size = size.checked_sub(len).ok_or(())?;
|
|
let name_data = data.read_bytes(offset, len)?;
|
|
let name = match memchr::memchr(b'\0', name_data) {
|
|
Some(len) => &name_data[..len],
|
|
None => name_data,
|
|
};
|
|
Ok(name)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn kind() {
|
|
let data = b"!<arch>\n";
|
|
let archive = ArchiveFile::parse(&data[..]).unwrap();
|
|
assert_eq!(archive.kind(), ArchiveKind::Unknown);
|
|
|
|
let data = b"\
|
|
!<arch>\n\
|
|
/ 4 `\n\
|
|
0000";
|
|
let archive = ArchiveFile::parse(&data[..]).unwrap();
|
|
assert_eq!(archive.kind(), ArchiveKind::Gnu);
|
|
|
|
let data = b"\
|
|
!<arch>\n\
|
|
// 4 `\n\
|
|
0000";
|
|
let archive = ArchiveFile::parse(&data[..]).unwrap();
|
|
assert_eq!(archive.kind(), ArchiveKind::Gnu);
|
|
|
|
let data = b"\
|
|
!<arch>\n\
|
|
/ 4 `\n\
|
|
0000\
|
|
// 4 `\n\
|
|
0000";
|
|
let archive = ArchiveFile::parse(&data[..]).unwrap();
|
|
assert_eq!(archive.kind(), ArchiveKind::Gnu);
|
|
|
|
let data = b"\
|
|
!<arch>\n\
|
|
/SYM64/ 4 `\n\
|
|
0000";
|
|
let archive = ArchiveFile::parse(&data[..]).unwrap();
|
|
assert_eq!(archive.kind(), ArchiveKind::Gnu64);
|
|
|
|
let data = b"\
|
|
!<arch>\n\
|
|
/SYM64/ 4 `\n\
|
|
0000\
|
|
// 4 `\n\
|
|
0000";
|
|
let archive = ArchiveFile::parse(&data[..]).unwrap();
|
|
assert_eq!(archive.kind(), ArchiveKind::Gnu64);
|
|
|
|
let data = b"\
|
|
!<arch>\n\
|
|
__.SYMDEF 4 `\n\
|
|
0000";
|
|
let archive = ArchiveFile::parse(&data[..]).unwrap();
|
|
assert_eq!(archive.kind(), ArchiveKind::Bsd);
|
|
|
|
let data = b"\
|
|
!<arch>\n\
|
|
#1/9 13 `\n\
|
|
__.SYMDEF0000";
|
|
let archive = ArchiveFile::parse(&data[..]).unwrap();
|
|
assert_eq!(archive.kind(), ArchiveKind::Bsd);
|
|
|
|
let data = b"\
|
|
!<arch>\n\
|
|
#1/16 20 `\n\
|
|
__.SYMDEF SORTED0000";
|
|
let archive = ArchiveFile::parse(&data[..]).unwrap();
|
|
assert_eq!(archive.kind(), ArchiveKind::Bsd);
|
|
|
|
let data = b"\
|
|
!<arch>\n\
|
|
__.SYMDEF_64 4 `\n\
|
|
0000";
|
|
let archive = ArchiveFile::parse(&data[..]).unwrap();
|
|
assert_eq!(archive.kind(), ArchiveKind::Bsd64);
|
|
|
|
let data = b"\
|
|
!<arch>\n\
|
|
#1/12 16 `\n\
|
|
__.SYMDEF_640000";
|
|
let archive = ArchiveFile::parse(&data[..]).unwrap();
|
|
assert_eq!(archive.kind(), ArchiveKind::Bsd64);
|
|
|
|
let data = b"\
|
|
!<arch>\n\
|
|
#1/19 23 `\n\
|
|
__.SYMDEF_64 SORTED0000";
|
|
let archive = ArchiveFile::parse(&data[..]).unwrap();
|
|
assert_eq!(archive.kind(), ArchiveKind::Bsd64);
|
|
|
|
let data = b"\
|
|
!<arch>\n\
|
|
/ 4 `\n\
|
|
0000\
|
|
/ 4 `\n\
|
|
0000\
|
|
// 4 `\n\
|
|
0000";
|
|
let archive = ArchiveFile::parse(&data[..]).unwrap();
|
|
assert_eq!(archive.kind(), ArchiveKind::Coff);
|
|
|
|
let data = b"\
|
|
<bigaf>\n\
|
|
0 0 \
|
|
0 0 \
|
|
0 128 \
|
|
6 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\0\0";
|
|
let archive = ArchiveFile::parse(&data[..]).unwrap();
|
|
assert_eq!(archive.kind(), ArchiveKind::AixBig);
|
|
}
|
|
|
|
#[test]
|
|
fn gnu_names() {
|
|
let data = b"\
|
|
!<arch>\n\
|
|
// 18 `\n\
|
|
0123456789abcdef/\n\
|
|
s p a c e/ 0 0 0 644 4 `\n\
|
|
0000\
|
|
0123456789abcde/0 0 0 644 3 `\n\
|
|
odd\n\
|
|
/0 0 0 0 644 4 `\n\
|
|
even";
|
|
let data = &data[..];
|
|
let archive = ArchiveFile::parse(data).unwrap();
|
|
assert_eq!(archive.kind(), ArchiveKind::Gnu);
|
|
let mut members = archive.members();
|
|
|
|
let member = members.next().unwrap().unwrap();
|
|
assert_eq!(member.name(), b"s p a c e");
|
|
assert_eq!(member.data(data).unwrap(), &b"0000"[..]);
|
|
|
|
let member = members.next().unwrap().unwrap();
|
|
assert_eq!(member.name(), b"0123456789abcde");
|
|
assert_eq!(member.data(data).unwrap(), &b"odd"[..]);
|
|
|
|
let member = members.next().unwrap().unwrap();
|
|
assert_eq!(member.name(), b"0123456789abcdef");
|
|
assert_eq!(member.data(data).unwrap(), &b"even"[..]);
|
|
|
|
assert!(members.next().is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn bsd_names() {
|
|
let data = b"\
|
|
!<arch>\n\
|
|
0123456789abcde 0 0 0 644 3 `\n\
|
|
odd\n\
|
|
#1/16 0 0 0 644 20 `\n\
|
|
0123456789abcdefeven";
|
|
let data = &data[..];
|
|
let archive = ArchiveFile::parse(data).unwrap();
|
|
assert_eq!(archive.kind(), ArchiveKind::Unknown);
|
|
let mut members = archive.members();
|
|
|
|
let member = members.next().unwrap().unwrap();
|
|
assert_eq!(member.name(), b"0123456789abcde");
|
|
assert_eq!(member.data(data).unwrap(), &b"odd"[..]);
|
|
|
|
let member = members.next().unwrap().unwrap();
|
|
assert_eq!(member.name(), b"0123456789abcdef");
|
|
assert_eq!(member.data(data).unwrap(), &b"even"[..]);
|
|
|
|
assert!(members.next().is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn aix_names() {
|
|
let data = b"\
|
|
<bigaf>\n\
|
|
396 0 0 \
|
|
128 262 0 \
|
|
4 262 0 \
|
|
1662610370 223 1 644 16 \
|
|
0123456789abcdef`\nord\n\
|
|
4 396 128 \
|
|
1662610374 223 1 644 16 \
|
|
fedcba9876543210`\nrev\n\
|
|
94 0 262 \
|
|
0 0 0 0 0 \
|
|
`\n2 128 \
|
|
262 0123456789abcdef\0fedcba9876543210\0";
|
|
let data = &data[..];
|
|
let archive = ArchiveFile::parse(data).unwrap();
|
|
assert_eq!(archive.kind(), ArchiveKind::AixBig);
|
|
let mut members = archive.members();
|
|
|
|
let member = members.next().unwrap().unwrap();
|
|
assert_eq!(member.name(), b"0123456789abcdef");
|
|
assert_eq!(member.data(data).unwrap(), &b"ord\n"[..]);
|
|
|
|
let member = members.next().unwrap().unwrap();
|
|
assert_eq!(member.name(), b"fedcba9876543210");
|
|
assert_eq!(member.data(data).unwrap(), &b"rev\n"[..]);
|
|
|
|
assert!(members.next().is_none());
|
|
}
|
|
}
|