use byteorder::{BigEndian, ReadBytesExt};
use elf::strtab;
use std::io::{self, Read, Seek, Cursor};
use std::io::SeekFrom::{Start, Current};
use std::usize;
use std::collections::HashMap;
pub const SIZEOF_MAGIC: usize = 8;
pub const MAGIC: &'static [u8; SIZEOF_MAGIC] = b"!<arch>\x0A";
const SIZEOF_FILE_IDENTIFER: usize = 16;
#[derive(Debug, Clone, PartialEq)]
pub struct Header {
pub identifier: String,
pub timestamp: [u8; 12],
pub owner_id: [u8; 6],
pub group_id: [u8; 6],
pub mode: [u8; 8],
pub size: usize,
pub terminator: [u8; 2],
}
const SIZEOF_FILE_SIZE: usize = 10;
pub const SIZEOF_HEADER: usize = SIZEOF_FILE_IDENTIFER + 12 + 6 + 6 + 8 + SIZEOF_FILE_SIZE + 2;
impl Header {
pub fn parse<R: Read + Seek>(cursor: &mut R) -> io::Result<Header> {
let mut file_identifier = vec![0u8; SIZEOF_FILE_IDENTIFER];
try!(cursor.read_exact(&mut file_identifier));
let file_identifier = unsafe { String::from_utf8_unchecked(file_identifier) };
let mut file_modification_timestamp = [0u8; 12];
try!(cursor.read(&mut file_modification_timestamp));
let mut owner_id = [0u8; 6];
try!(cursor.read(&mut owner_id));
let mut group_id = [0u8; 6];
try!(cursor.read(&mut group_id));
let mut file_mode = [0u8; 8];
try!(cursor.read(&mut file_mode));
let mut file_size = [0u8; SIZEOF_FILE_SIZE];
let file_size_pos = try!(cursor.seek(Current(0)));
try!(cursor.read(&mut file_size));
let mut terminator = [0u8; 2];
try!(cursor.read(&mut terminator));
let string = unsafe { ::std::str::from_utf8_unchecked(&file_size) };
let file_size = match usize::from_str_radix(string.trim_right(), 10) {
Ok(file_size) => file_size,
Err(err) => return io_error!("Err: {:?} Bad file_size {:?} at offset 0x{:X}: {:?} {:?} {:?} {:?} {:?} {:?}",
err, &file_size, file_size_pos, file_identifier, file_modification_timestamp, owner_id, group_id,
file_mode, file_size),
};
Ok(Header {
identifier: file_identifier,
timestamp: file_modification_timestamp.clone(),
owner_id: owner_id.clone(),
group_id: group_id.clone(),
mode: file_mode.clone(),
size: file_size,
terminator: terminator,
})
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Member {
header: Header,
pub offset: u64,
}
impl Member {
pub fn parse<R: Read + Seek>(cursor: &mut R) -> io::Result<Self> {
let header = try!(Header::parse(cursor));
let mut offset = try!(cursor.seek(Current(0)));
if offset & 1 == 1 {
offset = try!(cursor.seek(Current(1)));
}
Ok(Member {
header: header,
offset: offset,
})
}
pub fn size(&self) -> usize {
self.header.size
}
fn trim(name: &str) -> &str {
name.trim_right_matches(' ').trim_right_matches('/')
}
pub fn name(&self) -> &str {
&self.header.identifier
}
}
#[derive(Debug, Default)]
pub struct Index {
pub size: usize,
pub symbol_indexes: Vec<u32>,
pub strtab: Vec<String>,
}
const INDEX_NAME: &'static str = "/ ";
const NAME_INDEX_NAME: &'static str = "// ";
impl Index {
pub fn parse<'c, R: Read + Seek>(cursor: &'c mut R, size: usize) -> io::Result<Index> {
let sizeof_table = try!(cursor.read_u32::<BigEndian>()) as usize;
let mut indexes = Vec::with_capacity(sizeof_table);
for _ in 0..sizeof_table {
indexes.push(try!(cursor.read_u32::<BigEndian>()));
}
let sizeof_strtab = size - ((sizeof_table * 4) + 4);
let offset = try!(cursor.seek(Current(0)));
let strtab = try!(strtab::Strtab::parse(cursor, offset as usize, sizeof_strtab, 0x0));
Ok (Index {
size: sizeof_table,
symbol_indexes: indexes,
strtab: strtab.to_vec(), })
}
}
#[derive(Debug, Default)]
struct NameIndex {
strtab: strtab::Strtab<'static>
}
impl NameIndex {
pub fn parse<R: Read + Seek> (cursor: &mut R, offset: usize, size: usize) -> io::Result<Self> {
let strtab = try!(strtab::Strtab::parse(cursor, offset-1, size+1, '\n' as u8));
Ok (NameIndex {
strtab: strtab
})
}
pub fn get(&self, name: &str) -> io::Result<&str> {
let idx = name.trim_left_matches('/').trim_right();
match usize::from_str_radix(idx, 10) {
Ok(idx) => {
let name = &self.strtab[idx+1];
if name != "" {
Ok(name.trim_right_matches('/'))
} else {
return io_error!(format!("Could not find {:?} in index", name))
}
},
Err (_) => {
return io_error!(format!("Bad name index: {:?}", name))
}
}
}
}
#[derive(Debug)]
pub struct Archive {
index: Index,
extended_names: NameIndex,
member_array: Vec<Member>,
members: HashMap<String, usize>,
symbol_index: HashMap<String, usize>
}
impl Archive {
pub fn parse<R: Read + Seek>(mut cursor: &mut R, size: usize) -> io::Result<Archive> {
try!(cursor.seek(Start(0)));
let mut magic = [0; SIZEOF_MAGIC];
try!(cursor.read_exact(&mut magic));
if &magic != MAGIC {
return io_error!("Invalid Archive magic number: {:?}", &magic);
}
let mut member_array = Vec::new();
let size = size as u64;
let mut pos = 0u64;
let mut index = Index::default();
let mut extended_names = NameIndex::default();
loop {
if pos >= size { break }
if pos & 1 == 1 {
try!(cursor.seek(Current(1)));
}
let member = try!(Member::parse(&mut cursor));
if member.name() == INDEX_NAME {
let mut data = vec![0u8; member.size()];
try!(cursor.seek(Start(member.offset)));
try!(cursor.read_exact(&mut data));
let mut data = Cursor::new(&data);
index = try!(Index::parse(&mut data, member.size()));
pos = try!(cursor.seek(Current(0)));
} else if member.name() == NAME_INDEX_NAME {
extended_names = try!(NameIndex::parse(cursor, member.offset as usize, member.size()));
pos = try!(cursor.seek(Current(0)));
} else {
pos = try!(cursor.seek(Current(member.size() as i64)));
member_array.push(member);
}
}
let mut members = HashMap::new();
for (i, member) in member_array.iter().enumerate() {
let key = {
let name = member.name();
if name.starts_with("/") {
try!(extended_names.get(name))
} else {
Member::trim(name)
}}.to_owned();
members.insert(key, i);
}
let mut symbol_index = HashMap::new();
let mut last_symidx = 0u32;
let mut last_member = 0usize;
for (i, symidx) in index.symbol_indexes.iter().enumerate() {
let name = index.strtab[i].to_owned();
if *symidx == last_symidx {
symbol_index.insert(name, last_member);
} else {
for (memidx, member) in member_array.iter().enumerate() {
if *symidx == (member.offset - SIZEOF_HEADER as u64) as u32 {
symbol_index.insert(name, memidx);
last_symidx = *symidx;
last_member = memidx;
break
}
}
}
}
let archive = Archive {
index: index,
member_array: member_array,
extended_names: extended_names,
members: members,
symbol_index: symbol_index,
};
Ok(archive)
}
fn get (&self, member: &str) -> Option<&Member> {
if let Some(idx) = self.members.get(member) {
Some(&self.member_array[*idx])
} else {
None
}
}
pub fn extract<R: Read + Seek> (&self, member: &str, cursor: &mut R) -> io::Result<Vec<u8>> {
if let Some(member) = self.get(member) {
let mut bytes = vec![0u8; member.size()];
try!(cursor.seek(Start(member.offset)));
try!(cursor.read_exact(&mut bytes));
Ok(bytes)
} else {
io_error!(format!("Cannot extract member {}, not found", member))
}
}
pub fn member_of_symbol (&self, symbol: &str) -> Option<&str> {
if let Some(idx) = self.symbol_index.get(symbol) {
Some (Member::trim(self.member_array[*idx].name()))
} else {
None
}
}
}
#[no_mangle]
pub extern fn wow_so_meta_doge_symbol() { println!("wow_so_meta_doge_symbol")}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
use std::path::Path;
use std::fs::File;
use super::super::elf;
#[test]
fn parse_file_header() {
let file_header: [u8; SIZEOF_HEADER] = [0x2f, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30,
0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x38,
0x32, 0x34, 0x34, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x60, 0x0a];
let mut cursor = Cursor::new(&file_header[..]);
match Header::parse(&mut cursor) {
Err(_) => assert!(false),
Ok(file_header2) => {
let file_header = Header { identifier: "/ ".to_owned(),
timestamp: [48, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32],
owner_id: [48, 32, 32, 32, 32, 32],
group_id: [48, 32, 32, 32, 32, 32],
mode: [48, 32, 32, 32, 32, 32, 32, 32],
size: 8244, terminator:
[96, 10] };
assert_eq!(file_header, file_header2)
}
}
}
#[test]
fn parse_archive() {
let crt1a: Vec<u8> = include!("../../etc/crt1a.rs");
const START: &'static str = "_start";
let mut cursor = Cursor::new(&crt1a);
match Archive::parse(&mut cursor, crt1a.len()) {
Ok(archive) => {
assert_eq!(archive.member_of_symbol(START), Some("crt1.o"));
if let Some(member) = archive.get("crt1.o") {
assert_eq!(member.offset, 194);
assert_eq!(member.size(), 1928)
} else {
assert!(false)
}
},
Err(_) => assert!(false),
};
}
#[test]
fn parse_self_wow_so_meta_doge() {
const GOBLIN: &'static str = "goblin.0.o";
let path = Path::new("target").join("debug").join("libgoblin.rlib");
match File::open(path) {
Ok(mut fd) => {
let size = fd.metadata().unwrap().len();
match Archive::parse(&mut fd, size as usize) {
Ok(archive) => {
assert_eq!(archive.member_of_symbol("wow_so_meta_doge_symbol"), Some(GOBLIN));
match archive.extract(GOBLIN, &mut fd) {
Ok(bytes) => {
match elf::Elf::parse(&mut Cursor::new(&bytes)) {
Ok(elf) => {
assert!(elf.entry == 0);
assert!(elf.bias == 0);
},
Err(_) => assert!(false)
}
},
Err(_) => assert!(false)
}
},
Err(err) => {println!("{:?}", err); assert!(false)}
}
},
Err(_) => assert!(false)
}
}
}