use std::borrow::Cow;
use std::cmp;
use std::fs;
use std::io::prelude::*;
use std::io;
use std::marker;
use std::path::Path;
use filetime::{self, FileTime};
use {Header, Archive, PaxExtensions};
use archive::ArchiveInner;
use error::TarError;
use header::{deslash, bytes2path};
use other;
use pax::pax_extensions;
pub struct Entry<'a, R: 'a + Read> {
fields: EntryFields<'a>,
_ignored: marker::PhantomData<&'a Archive<R>>,
}
pub struct EntryFields<'a> {
pub long_pathname: Option<Vec<u8>>,
pub long_linkname: Option<Vec<u8>>,
pub pax_extensions: Option<Vec<u8>>,
pub header: Header,
pub size: u64,
pub data: io::Take<&'a ArchiveInner<Read + 'a>>,
}
impl<'a, R: Read> Entry<'a, R> {
pub fn path(&self) -> io::Result<Cow<Path>> {
self.fields.path()
}
pub fn path_bytes(&self) -> Cow<[u8]> {
self.fields.path_bytes()
}
pub fn link_name(&self) -> io::Result<Option<Cow<Path>>> {
self.fields.link_name()
}
pub fn link_name_bytes(&self) -> Option<Cow<[u8]>> {
self.fields.link_name_bytes()
}
pub fn pax_extensions(&mut self) -> io::Result<Option<PaxExtensions>> {
self.fields.pax_extensions()
}
pub fn header(&self) -> &Header {
&self.fields.header
}
pub fn unpack<P: AsRef<Path>>(&mut self, dst: P) -> io::Result<()> {
self.fields.unpack(dst.as_ref())
}
}
impl<'a, R: Read> Read for Entry<'a, R> {
fn read(&mut self, into: &mut [u8]) -> io::Result<usize> {
self.fields.read(into)
}
}
impl<'a> EntryFields<'a> {
pub fn from<R: Read>(entry: Entry<R>) -> EntryFields {
entry.fields
}
pub fn into_entry<R: Read>(self) -> Entry<'a, R> {
Entry {
fields: self,
_ignored: marker::PhantomData,
}
}
pub fn read_all(&mut self) -> io::Result<Vec<u8>> {
let cap = cmp::min(self.size, 128 * 1024);
let mut v = Vec::with_capacity(cap as usize);
self.read_to_end(&mut v).map(|_| v)
}
fn path(&self) -> io::Result<Cow<Path>> {
bytes2path(self.path_bytes())
}
fn path_bytes(&self) -> Cow<[u8]> {
match self.long_pathname {
Some(ref bytes) => deslash(bytes),
None => self.header.path_bytes(),
}
}
fn link_name(&self) -> io::Result<Option<Cow<Path>>> {
match self.link_name_bytes() {
Some(bytes) => bytes2path(bytes).map(Some),
None => Ok(None),
}
}
fn link_name_bytes(&self) -> Option<Cow<[u8]>> {
match self.long_linkname {
Some(ref bytes) => Some(deslash(bytes)),
None => self.header.link_name_bytes(),
}
}
fn pax_extensions(&mut self) -> io::Result<Option<PaxExtensions>> {
if self.pax_extensions.is_none() {
if !self.header.entry_type().is_pax_global_extensions() &&
!self.header.entry_type().is_pax_local_extensions() {
return Ok(None)
}
self.pax_extensions = Some(try!(self.read_all()));
}
Ok(Some(pax_extensions(self.pax_extensions.as_ref().unwrap())))
}
fn unpack(&mut self, dst: &Path) -> io::Result<()> {
let kind = self.header.entry_type();
if kind.is_dir() {
let prev = fs::metadata(&dst);
if prev.map(|m| m.is_dir()).unwrap_or(false) {
return Ok(())
}
return fs::create_dir(&dst)
} else if kind.is_hard_link() || kind.is_symlink() {
let src = match try!(self.link_name()) {
Some(name) => name,
None => return Err(other("hard link listed but no link \
name found"))
};
return if kind.is_hard_link() {
fs::hard_link(&src, dst)
} else {
symlink(&src, dst)
};
#[cfg(windows)]
fn symlink(src: &Path, dst: &Path) -> io::Result<()> {
::std::os::windows::fs::symlink_file(src, dst)
}
#[cfg(unix)]
fn symlink(src: &Path, dst: &Path) -> io::Result<()> {
::std::os::unix::fs::symlink(src, dst)
}
} else if kind.is_pax_global_extensions() ||
kind.is_pax_local_extensions() ||
kind.is_gnu_longname() ||
kind.is_gnu_longlink() {
return Ok(())
};
try!(fs::File::create(dst).and_then(|mut f| {
if try!(io::copy(self, &mut f)) != self.size {
return Err(other("failed to write entire file"));
}
Ok(())
}).map_err(|e| {
let header = self.header.path_bytes();
TarError::new(&format!("failed to unpack `{}` into `{}`",
String::from_utf8_lossy(&header),
dst.display()), e)
}));
if let Ok(mtime) = self.header.mtime() {
let mtime = FileTime::from_seconds_since_1970(mtime, 0);
try!(filetime::set_file_times(dst, mtime, mtime).map_err(|e| {
TarError::new(&format!("failed to set mtime for `{}`",
dst.display()), e)
}));
}
if let Ok(mode) = self.header.mode() {
try!(set_perms(dst, mode).map_err(|e| {
TarError::new(&format!("failed to set permissions to {:o} \
for `{}`", mode, dst.display()), e)
}));
}
return Ok(());
#[cfg(unix)]
#[allow(deprecated)] fn set_perms(dst: &Path, mode: u32) -> io::Result<()> {
use std::os::unix::raw;
use std::os::unix::prelude::*;
let perm = fs::Permissions::from_mode(mode as raw::mode_t);
fs::set_permissions(dst, perm)
}
#[cfg(windows)]
fn set_perms(dst: &Path, mode: u32) -> io::Result<()> {
let mut perm = try!(fs::metadata(dst)).permissions();
perm.set_readonly(mode & 0o200 != 0o200);
fs::set_permissions(dst, perm)
}
}
}
impl<'a> Read for EntryFields<'a> {
fn read(&mut self, into: &mut [u8]) -> io::Result<usize> {
self.data.read(into)
}
}