use std::cmp;
use std::fs;
use std::io::prelude::*;
use std::io::{self, SeekFrom};
use std::marker;
use std::path::Path;
use filetime::{self, FileTime};
use error::TarError;
use {Header, Archive};
use other;
#[doc(hidden)]
pub type File<'a, T> = Entry<'a, T>;
pub struct Entry<'a, R: 'a> {
fields: EntryFields<'a>,
_ignored: marker::PhantomData<&'a Archive<R>>,
}
pub struct EntryFields<'a> {
pub header: Header,
pub archive: &'a Archive<Read + 'a>,
pub pos: u64,
pub size: u64,
pub seek: Box<Fn(&EntryFields) -> io::Result<()> + 'a>,
pub tar_offset: u64,
}
impl<'a, R: Read> Entry<'a, R> {
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, R: Read + Seek> Seek for Entry<'a, R> {
fn seek(&mut self, how: SeekFrom) -> io::Result<u64> {
self.fields._seek(how)
}
}
impl<'a> EntryFields<'a> {
pub fn into_entry<R>(self) -> Entry<'a, R> {
Entry {
fields: self,
_ignored: marker::PhantomData,
}
}
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.header.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_file() {
return Err(other(&format!("unknown file type 0x{:x}",
kind.as_byte())))
};
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)]
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)
}
}
fn _seek(&mut self, how: SeekFrom) -> io::Result<u64> {
let next = match how {
SeekFrom::Start(pos) => pos as i64,
SeekFrom::Current(pos) => self.pos as i64 + pos,
SeekFrom::End(pos) => self.size as i64 + pos,
};
if next < 0 {
Err(other("cannot seek before position 0"))
} else if next as u64 > self.size {
Err(other("cannot seek past end of file"))
} else {
self.pos = next as u64;
Ok(self.pos)
}
}
}
impl<'a> Read for EntryFields<'a> {
fn read(&mut self, into: &mut [u8]) -> io::Result<usize> {
if self.size == self.pos { return Ok(0) }
try!((self.seek)(self));
let amt = cmp::min((self.size - self.pos) as usize, into.len());
let amt = try!(Read::read(&mut self.archive, &mut into[..amt]));
self.pos += amt as u64;
Ok(amt)
}
}