#![allow(clippy::write_with_newline)]
use std::collections::{HashMap, HashSet};
use std::convert::TryFrom;
use std::io::{self, Error, ErrorKind, Read, Result, Seek, Write};
use super::*;
use crate::{bail, ensure, err};
impl Header {
fn write_gnu<W>(
&self,
deterministic: bool,
writer: &mut W,
names: &HashMap<Vec<u8>, usize>,
) -> Result<()>
where
W: Write,
{
self.validate()?;
if self.identifier.len() > 15 {
let offset = names[&self.identifier];
write!(writer, "/{:<15}", offset)?;
} else {
writer.write_all(&self.identifier)?;
writer.write_all(b"/")?;
writer.write_all(&vec![b' '; 15 - self.identifier.len()])?;
}
if deterministic {
write!(
writer,
"{:<12}{:<6}{:<6}{:<8o}{:<10}`\n",
0, 0, 0, 0o644, self.size
)?;
} else {
write!(
writer,
"{:<12}{:<6}{:<6}{:<8o}{:<10}`\n",
self.mtime, self.uid, self.gid, self.mode, self.size
)?;
}
Ok(())
}
}
pub struct GnuBuilder<W: Write + Seek> {
writer: W,
deterministic: bool,
short_names: HashSet<Vec<u8>>,
long_names: HashMap<Vec<u8>, usize>,
symbol_table_relocations: Vec<Vec<u64>>,
symbol_index: usize,
}
impl<W: Write + Seek> GnuBuilder<W> {
pub fn new_with_symbol_table(
mut writer: W,
deterministic: bool,
identifiers: Vec<Vec<u8>>,
symbol_table: Vec<Vec<Vec<u8>>>,
) -> Result<GnuBuilder<W>> {
let mut short_names = HashSet::<Vec<u8>>::new();
let mut long_names = HashMap::<Vec<u8>, usize>::new();
let mut name_table_size: usize = 0;
for identifier in identifiers.into_iter() {
let length = identifier.len();
if length > 15 {
long_names.insert(identifier, name_table_size);
name_table_size += length + 2;
} else {
short_names.insert(identifier);
}
}
let name_table_needs_padding = name_table_size % 2 != 0;
if name_table_needs_padding {
name_table_size += 3; }
writer.write_all(GLOBAL_HEADER)?;
let mut symbol_table_relocations: Vec<Vec<u64>> = Vec::with_capacity(symbol_table.len());
if !symbol_table.is_empty() {
let wordsize = std::mem::size_of::<u32>();
let symbol_count: usize = symbol_table.iter().map(|symbols| symbols.len()).sum();
let symbols = symbol_table.iter().flatten();
let mut symbol_table_size: usize = wordsize
+ wordsize * symbol_count
+ symbols.map(|symbol| symbol.len() + 1).sum::<usize>();
let symbol_table_needs_padding = symbol_table_size % 2 != 0;
if symbol_table_needs_padding {
symbol_table_size += 3; }
write!(
writer,
"{:<16}{:<12}{:<6}{:<6}{:<8o}{:<10}`\n",
GNU_SYMBOL_LOOKUP_TABLE_ID, 0, 0, 0, 0, symbol_table_size
)?;
writer.write_all(&u32::to_be_bytes(u32::try_from(symbol_count).map_err(
|_| err!("Too many symbols for 32bit table `{}`", symbol_count),
)?))?;
for symbols in &symbol_table {
let mut sym_rels = Vec::new();
for _symbol in symbols {
sym_rels.push(writer.seek(io::SeekFrom::Current(0))?);
writer.write_all(&u32::to_be_bytes(0xcafebabe))?;
}
symbol_table_relocations.push(sym_rels);
}
for symbol in symbol_table.iter().flatten() {
writer.write_all(symbol)?;
writer.write_all(b"\0")?;
}
if symbol_table_needs_padding {
writer.write_all(b" /\n")?;
}
}
if !long_names.is_empty() {
write!(
writer,
"{:<48}{:<10}`\n",
GNU_NAME_TABLE_ID, name_table_size
)?;
let mut entries: Vec<(usize, &[u8])> = long_names
.iter()
.map(|(id, &start)| (start, id.as_slice()))
.collect();
entries.sort();
for (_, id) in entries {
writer.write_all(id)?;
writer.write_all(b"/\n")?;
}
if name_table_needs_padding {
writer.write_all(b" /\n")?;
}
}
Ok(GnuBuilder {
writer,
deterministic,
short_names,
long_names,
symbol_table_relocations,
symbol_index: 0,
})
}
pub fn append<R: Read>(&mut self, header: &Header, mut data: R) -> Result<()> {
let is_long_name = header.identifier().len() > 15;
let has_name = if is_long_name {
self.long_names.contains_key(header.identifier())
} else {
self.short_names.contains(header.identifier())
};
ensure!(
has_name,
"Identifier `{:?}` was not in the list of identifiers passed to GnuBuilder::new()",
String::from_utf8_lossy(header.identifier())
);
if let Some(relocs) = self.symbol_table_relocations.get(self.symbol_index) {
let entry_offset = self.writer.seek(io::SeekFrom::Current(0))?;
let entry_offset_bytes = u32::to_be_bytes(
u32::try_from(entry_offset).map_err(|_| err!("Archive larger than 4GB"))?,
);
for &reloc_offset in relocs {
self.writer.seek(io::SeekFrom::Start(reloc_offset))?;
self.writer.write_all(&entry_offset_bytes)?;
}
self.writer.seek(io::SeekFrom::Start(entry_offset))?;
self.symbol_index += 1;
}
header.write_gnu(self.deterministic, &mut self.writer, &self.long_names)?;
let actual_size = io::copy(&mut data, &mut self.writer)?;
if actual_size != header.size() {
bail!(
"Wrong file size (header.size() = `{}`, actual = `{}`)",
header.size(),
actual_size
);
}
if actual_size % 2 != 0 {
self.writer.write_all(&[b'\n'])?;
}
Ok(())
}
}