use std::cell::{Cell, RefCell};
use std::cmp;
use std::fs;
use std::io;
use std::io::prelude::*;
use std::marker;
use std::path::Path;
use crate::entry::{EntryFields, EntryIo};
use crate::error::TarError;
use crate::other;
use crate::pax::pax_extensions_size;
use crate::{Entry, GnuExtSparseHeader, GnuSparseHeader, Header};
pub struct Archive<R: ?Sized + Read> {
inner: ArchiveInner<R>,
}
pub struct ArchiveInner<R: ?Sized> {
pos: Cell<u64>,
unpack_xattrs: bool,
preserve_permissions: bool,
preserve_mtime: bool,
overwrite: bool,
ignore_zeros: bool,
obj: RefCell<R>,
}
pub struct Entries<'a, R: 'a + Read> {
fields: EntriesFields<'a>,
_ignored: marker::PhantomData<&'a Archive<R>>,
}
struct EntriesFields<'a> {
archive: &'a Archive<dyn Read + 'a>,
next: u64,
done: bool,
raw: bool,
}
impl<R: Read> Archive<R> {
pub fn new(obj: R) -> Archive<R> {
Archive {
inner: ArchiveInner {
unpack_xattrs: false,
preserve_permissions: false,
preserve_mtime: true,
overwrite: true,
ignore_zeros: false,
obj: RefCell::new(obj),
pos: Cell::new(0),
},
}
}
pub fn into_inner(self) -> R {
self.inner.obj.into_inner()
}
pub fn entries(&mut self) -> io::Result<Entries<R>> {
let me: &mut Archive<dyn Read> = self;
me._entries().map(|fields| Entries {
fields: fields,
_ignored: marker::PhantomData,
})
}
pub fn unpack<P: AsRef<Path>>(&mut self, dst: P) -> io::Result<()> {
let me: &mut Archive<dyn Read> = self;
me._unpack(dst.as_ref())
}
pub fn set_unpack_xattrs(&mut self, unpack_xattrs: bool) {
self.inner.unpack_xattrs = unpack_xattrs;
}
pub fn set_preserve_permissions(&mut self, preserve: bool) {
self.inner.preserve_permissions = preserve;
}
pub fn set_overwrite(&mut self, overwrite: bool) {
self.inner.overwrite = overwrite;
}
pub fn set_preserve_mtime(&mut self, preserve: bool) {
self.inner.preserve_mtime = preserve;
}
pub fn set_ignore_zeros(&mut self, ignore_zeros: bool) {
self.inner.ignore_zeros = ignore_zeros;
}
}
impl<'a> Archive<dyn Read + 'a> {
fn _entries(&mut self) -> io::Result<EntriesFields> {
if self.inner.pos.get() != 0 {
return Err(other(
"cannot call entries unless archive is at \
position 0",
));
}
Ok(EntriesFields {
archive: self,
done: false,
next: 0,
raw: false,
})
}
fn _unpack(&mut self, dst: &Path) -> io::Result<()> {
if dst.symlink_metadata().is_err() {
fs::create_dir_all(&dst)
.map_err(|e| TarError::new(&format!("failed to create `{}`", dst.display()), e))?;
}
let dst = &dst.canonicalize().unwrap_or(dst.to_path_buf());
for entry in self._entries()? {
let mut file = entry.map_err(|e| TarError::new("failed to iterate over archive", e))?;
file.unpack_in(dst)?;
}
Ok(())
}
fn skip(&self, mut amt: u64) -> io::Result<()> {
let mut buf = [0u8; 4096 * 8];
while amt > 0 {
let n = cmp::min(amt, buf.len() as u64);
let n = (&self.inner).read(&mut buf[..n as usize])?;
if n == 0 {
return Err(other("unexpected EOF during skip"));
}
amt -= n as u64;
}
Ok(())
}
}
impl<'a, R: Read> Entries<'a, R> {
pub fn raw(self, raw: bool) -> Entries<'a, R> {
Entries {
fields: EntriesFields {
raw: raw,
..self.fields
},
_ignored: marker::PhantomData,
}
}
}
impl<'a, R: Read> Iterator for Entries<'a, R> {
type Item = io::Result<Entry<'a, R>>;
fn next(&mut self) -> Option<io::Result<Entry<'a, R>>> {
self.fields
.next()
.map(|result| result.map(|e| EntryFields::from(e).into_entry()))
}
}
impl<'a> EntriesFields<'a> {
fn next_entry_raw(
&mut self,
pax_size: Option<u64>,
) -> io::Result<Option<Entry<'a, io::Empty>>> {
let mut header = Header::new_old();
let mut header_pos = self.next;
loop {
let delta = self.next - self.archive.inner.pos.get();
self.archive.skip(delta)?;
if !try_read_all(&mut &self.archive.inner, header.as_mut_bytes())? {
return Ok(None);
}
if !header.as_bytes().iter().all(|i| *i == 0) {
self.next += 512;
break;
}
if !self.archive.inner.ignore_zeros {
return Ok(None);
}
self.next += 512;
header_pos = self.next;
}
let sum = header.as_bytes()[..148]
.iter()
.chain(&header.as_bytes()[156..])
.fold(0, |a, b| a + (*b as u32))
+ 8 * 32;
let cksum = header.cksum()?;
if sum != cksum {
return Err(other("archive header checksum mismatch"));
}
let file_pos = self.next;
let mut size = header.entry_size()?;
if size == 0 {
if let Some(pax_size) = pax_size {
size = pax_size;
}
}
let ret = EntryFields {
size: size,
header_pos: header_pos,
file_pos: file_pos,
data: vec![EntryIo::Data((&self.archive.inner).take(size))],
header: header,
long_pathname: None,
long_linkname: None,
pax_extensions: None,
unpack_xattrs: self.archive.inner.unpack_xattrs,
preserve_permissions: self.archive.inner.preserve_permissions,
preserve_mtime: self.archive.inner.preserve_mtime,
overwrite: self.archive.inner.overwrite,
};
let size = (size + 511) & !(512 - 1);
self.next += size;
Ok(Some(ret.into_entry()))
}
fn next_entry(&mut self) -> io::Result<Option<Entry<'a, io::Empty>>> {
if self.raw {
return self.next_entry_raw(None);
}
let mut gnu_longname = None;
let mut gnu_longlink = None;
let mut pax_extensions = None;
let mut pax_size = None;
let mut processed = 0;
loop {
processed += 1;
let entry = match self.next_entry_raw(pax_size)? {
Some(entry) => entry,
None if processed > 1 => {
return Err(other(
"members found describing a future member \
but no future member found",
));
}
None => return Ok(None),
};
let is_recognized_header =
entry.header().as_gnu().is_some() || entry.header().as_ustar().is_some();
if is_recognized_header && entry.header().entry_type().is_gnu_longname() {
if gnu_longname.is_some() {
return Err(other(
"two long name entries describing \
the same member",
));
}
gnu_longname = Some(EntryFields::from(entry).read_all()?);
continue;
}
if is_recognized_header && entry.header().entry_type().is_gnu_longlink() {
if gnu_longlink.is_some() {
return Err(other(
"two long name entries describing \
the same member",
));
}
gnu_longlink = Some(EntryFields::from(entry).read_all()?);
continue;
}
if is_recognized_header && entry.header().entry_type().is_pax_local_extensions() {
if pax_extensions.is_some() {
return Err(other(
"two pax extensions entries describing \
the same member",
));
}
pax_extensions = Some(EntryFields::from(entry).read_all()?);
if let Some(pax_extensions_ref) = &pax_extensions {
pax_size = pax_extensions_size(pax_extensions_ref);
}
continue;
}
let mut fields = EntryFields::from(entry);
fields.long_pathname = gnu_longname;
fields.long_linkname = gnu_longlink;
fields.pax_extensions = pax_extensions;
self.parse_sparse_header(&mut fields)?;
return Ok(Some(fields.into_entry()));
}
}
fn parse_sparse_header(&mut self, entry: &mut EntryFields<'a>) -> io::Result<()> {
if !entry.header.entry_type().is_gnu_sparse() {
return Ok(());
}
let gnu = match entry.header.as_gnu() {
Some(gnu) => gnu,
None => return Err(other("sparse entry type listed but not GNU header")),
};
entry.data.truncate(0);
let mut cur = 0;
let mut remaining = entry.size;
{
let data = &mut entry.data;
let reader = &self.archive.inner;
let size = entry.size;
let mut add_block = |block: &GnuSparseHeader| -> io::Result<_> {
if block.is_empty() {
return Ok(());
}
let off = block.offset()?;
let len = block.length()?;
if len != 0 && (size - remaining) % 512 != 0 {
return Err(other(
"previous block in sparse file was not \
aligned to 512-byte boundary",
));
} else if off < cur {
return Err(other(
"out of order or overlapping sparse \
blocks",
));
} else if cur < off {
let block = io::repeat(0).take(off - cur);
data.push(EntryIo::Pad(block));
}
cur = off
.checked_add(len)
.ok_or_else(|| other("more bytes listed in sparse file than u64 can hold"))?;
remaining = remaining.checked_sub(len).ok_or_else(|| {
other(
"sparse file consumed more data than the header \
listed",
)
})?;
data.push(EntryIo::Data(reader.take(len)));
Ok(())
};
for block in gnu.sparse.iter() {
add_block(block)?
}
if gnu.is_extended() {
let mut ext = GnuExtSparseHeader::new();
ext.isextended[0] = 1;
while ext.is_extended() {
if !try_read_all(&mut &self.archive.inner, ext.as_mut_bytes())? {
return Err(other("failed to read extension"));
}
self.next += 512;
for block in ext.sparse.iter() {
add_block(block)?;
}
}
}
}
if cur != gnu.real_size()? {
return Err(other(
"mismatch in sparse file chunks and \
size in header",
));
}
entry.size = cur;
if remaining > 0 {
return Err(other(
"mismatch in sparse file chunks and \
entry size in header",
));
}
Ok(())
}
}
impl<'a> Iterator for EntriesFields<'a> {
type Item = io::Result<Entry<'a, io::Empty>>;
fn next(&mut self) -> Option<io::Result<Entry<'a, io::Empty>>> {
if self.done {
None
} else {
match self.next_entry() {
Ok(Some(e)) => Some(Ok(e)),
Ok(None) => {
self.done = true;
None
}
Err(e) => {
self.done = true;
Some(Err(e))
}
}
}
}
}
impl<'a, R: ?Sized + Read> Read for &'a ArchiveInner<R> {
fn read(&mut self, into: &mut [u8]) -> io::Result<usize> {
self.obj.borrow_mut().read(into).map(|i| {
self.pos.set(self.pos.get() + i as u64);
i
})
}
}
fn try_read_all<R: Read>(r: &mut R, buf: &mut [u8]) -> io::Result<bool> {
let mut read = 0;
while read < buf.len() {
match r.read(&mut buf[read..])? {
0 => {
if read == 0 {
return Ok(false);
}
return Err(other("failed to read entire block"));
}
n => read += n,
}
}
Ok(true)
}