use std::{path, time, io, cmp, fs};
use std::io::Read;
use std::str::FromStr;
use crate::fs::{get_file_type, get_unix_mode, get_unix_owner, get_unix_group};
use crate::{normalize, spanning};
use crate::tar::{ustar, pax, recovery};
#[derive(Copy, Clone, Debug)]
pub enum TarFormat {
USTAR,
POSIX
}
impl FromStr for TarFormat {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.as_ref() {
"ustar" => Ok(TarFormat::USTAR),
"posix" => Ok(TarFormat::POSIX),
_ => Err(())
}
}
}
#[derive(Copy, Clone)]
pub enum TarFileType {
FileStream,
HardLink,
SymbolicLink,
CharacterDevice,
BlockDevice,
Directory,
FIFOPipe,
Other(char)
}
impl TarFileType {
pub fn type_flag(&self) -> char {
match self {
TarFileType::FileStream => '0',
TarFileType::HardLink => '1',
TarFileType::SymbolicLink => '2',
TarFileType::CharacterDevice => '3',
TarFileType::BlockDevice => '4',
TarFileType::Directory => '5',
TarFileType::FIFOPipe => '6',
TarFileType::Other(f) => f.clone()
}
}
}
#[derive(Clone)]
pub struct TarHeader {
pub path: Box<path::PathBuf>,
pub unix_mode: u32,
pub unix_uid: u32,
pub unix_gid: u32,
pub file_size: u64,
pub mtime: Option<time::SystemTime>,
pub file_type: TarFileType,
pub symlink_path: Option<Box<path::PathBuf>>,
pub unix_uname: String,
pub unix_gname: String,
pub unix_devmajor: u32,
pub unix_devminor: u32,
pub atime: Option<time::SystemTime>,
pub birthtime: Option<time::SystemTime>,
pub recovery_path: Option<Box<path::PathBuf>>,
pub recovery_remaining_size: Option<u64>,
pub recovery_seek_offset: Option<u64>,
}
impl TarHeader {
pub fn abstract_header_for_file(archival_path: &path::Path, entry_metadata: &fs::Metadata, entry_path: &path::Path) -> io::Result<TarHeader> {
let (uid, owner) = get_unix_owner(entry_metadata, entry_path).unwrap_or((65534, "nobody".to_string()));
let (gid, group) = get_unix_group(entry_metadata, entry_path).unwrap_or((65534, "nogroup".to_string()));
Ok(TarHeader {
path: Box::new(normalize::normalize(&archival_path)),
unix_mode: get_unix_mode(entry_metadata)?,
unix_uid: uid,
unix_gid: gid,
file_size: entry_metadata.len(),
mtime: entry_metadata.modified().ok(),
file_type: get_file_type(entry_metadata)?,
symlink_path: None,
unix_uname: owner,
unix_gname: group,
unix_devmajor: 0,
unix_devminor: 0,
atime: entry_metadata.accessed().ok(),
birthtime: entry_metadata.created().ok(),
recovery_path: None,
recovery_remaining_size: None,
recovery_seek_offset: None
})
}
pub fn with_recovery(archival_path: &path::Path, entry_metadata: &fs::Metadata, entry_path: &path::Path, zone: &spanning::DataZone<recovery::RecoveryEntry>) -> io::Result<TarHeader> {
let mut recovery_header = Self::abstract_header_for_file(archival_path, entry_metadata, entry_path)?;
if let Some(ref ident) = zone.ident {
let offset = zone.committed_length.checked_sub(ident.header_length).unwrap_or(0);
recovery_header.recovery_path = Some(Box::new(normalize::normalize(&ident.original_path.as_ref())));
recovery_header.recovery_remaining_size = Some(entry_metadata.len());
recovery_header.recovery_seek_offset = Some(cmp::min(offset, recovery_header.file_size));
recovery_header.file_size = recovery_header.file_size.checked_sub(offset).unwrap_or(0);
}
Ok(recovery_header)
}
}
pub struct HeaderGenResult {
pub tar_header: TarHeader,
pub encoded_header: Vec<u8>,
pub original_path: Box<path::PathBuf>,
pub canonical_path: Box<path::PathBuf>,
pub file_prefix: Option<Vec<u8>>
}
pub fn headergen(entry_path: &path::Path, archival_path: &path::Path, tarheader: TarHeader, format: TarFormat) -> io::Result<HeaderGenResult> {
let mut concrete_tarheader = match format {
TarFormat::USTAR => ustar::ustar_header(&tarheader)?,
TarFormat::POSIX => pax::pax_header(&tarheader)?
};
match format {
TarFormat::USTAR => ustar::checksum_header(&mut concrete_tarheader),
TarFormat::POSIX => pax::checksum_header(&mut concrete_tarheader)
}
let canonical_path = fs::canonicalize(entry_path).unwrap();
let readahead = match tarheader.file_type {
TarFileType::FileStream => {
let cache_len = cmp::min(tarheader.file_size, 64*1024);
let mut filebuf = Vec::with_capacity(cache_len as usize);
filebuf.resize(cache_len as usize, 0);
let mut final_cache_len = 0;
match fs::File::open(canonical_path.clone()) {
Ok(mut file) => {
loop {
match file.read(&mut filebuf[final_cache_len..]) {
Ok(size) => {
final_cache_len += size;
if size == 0 || final_cache_len == filebuf.len() {
break;
}
if cache_len == final_cache_len as u64 {
break;
}
},
Err(e) => {
match e.kind() {
io::ErrorKind::Interrupted => {},
_ => {
break;
}
}
}
}
}
assert!(final_cache_len <= filebuf.capacity());
unsafe {
filebuf.set_len(final_cache_len);
}
Some(filebuf)
},
Err(_) => {
None
}
}
},
_ => None
};
Ok(HeaderGenResult{tar_header: tarheader,
encoded_header: concrete_tarheader,
original_path: Box::new(archival_path.to_path_buf()),
canonical_path: Box::new(canonical_path),
file_prefix: readahead})
}