use endian_trait::Endian;
use super::format_definition::*;
-use crate::tools;
-use std::io::{Read, Write, Seek, SeekFrom};
+use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use std::os::unix::io::AsRawFd;
use std::os::unix::io::RawFd;
use std::os::unix::io::FromRawFd;
-use std::os::unix::ffi::{OsStrExt, OsStringExt};
+use std::os::unix::ffi::{OsStringExt};
use std::ffi::{OsStr, OsString};
use nix::fcntl::OFlag;
use nix::errno::Errno;
use nix::NixPath;
-pub struct CaDirectoryEntry {
- start: u64,
- end: u64,
- pub filename: OsString,
- pub entry: CaFormatEntry,
-}
-
-// This one needs Read+Seek (we may want one without Seek?)
-pub struct CaTarDecoder<'a, R: Read + Seek> {
+// This one need Read, but works without Seek
+pub struct CaTarDecoder<'a, R: Read> {
reader: &'a mut R,
- root_start: u64,
- root_end: u64,
+ skip_buffer: Vec<u8>,
}
const HEADER_SIZE: u64 = std::mem::size_of::<CaFormatHeader>() as u64;
-impl <'a, R: Read + Seek> CaTarDecoder<'a, R> {
-
- pub fn new(reader: &'a mut R) -> Result<Self, Error> {
-
- let root_end = reader.seek(SeekFrom::End(0))?;
+impl <'a, R: Read> CaTarDecoder<'a, R> {
- Ok(Self {
- reader: reader,
- root_start: 0,
- root_end: root_end,
- })
- }
-
- pub fn root(&self) -> CaDirectoryEntry {
- CaDirectoryEntry {
- start: self.root_start,
- end: self.root_end,
- filename: OsString::new(), // Empty
- entry: CaFormatEntry {
- feature_flags: 0,
- mode: 0,
- flags: 0,
- uid: 0,
- gid: 0,
- mtime: 0,
- }
- }
+ pub fn new(reader: &'a mut R) -> Self {
+ let mut skip_buffer = vec![0u8; 64*1024];
+ Self { reader, skip_buffer }
}
fn read_item<T: Endian>(&mut self) -> Result<T, Error> {
bail!("filename entry not nul terminated.");
}
- if buffer.iter().find(|b| (**b == b'/')).is_some() {
+ if (buffer.len() == 1 && buffer[0] == b'.') || (buffer.len() == 2 && buffer[0] == b'.' && buffer[1] == b'.') {
bail!("found invalid filename with slashes.");
}
- Ok(std::ffi::OsString::from_vec(buffer))
- }
-
- pub fn restore<F: Fn(&Path) -> Result<(), Error>>(
- &mut self,
- dir: &CaDirectoryEntry,
- callback: F,
- ) -> Result<(), Error> {
-
- let start = dir.start;
-
- self.reader.seek(SeekFrom::Start(start))?;
-
- let base = ".";
-
- let mut path = PathBuf::from(base);
-
- let dir = match nix::dir::Dir::open(&path, nix::fcntl::OFlag::O_DIRECTORY, nix::sys::stat::Mode::empty()) {
- Ok(dir) => dir,
- Err(err) => bail!("unable to open base directory - {}", err),
- };
-
- let restore_dir = "restoretest";
- path.push(restore_dir);
+ if buffer.iter().find(|b| (**b == b'/')).is_some() {
+ bail!("found invalid filename with slashes.");
+ }
- self.restore_sequential(&mut path, &OsString::from(restore_dir), &dir, &callback)?;
+ let name = std::ffi::OsString::from_vec(buffer);
+ if name.is_empty() {
+ bail!("found empty filename.");
+ }
- Ok(())
+ Ok(name)
}
fn restore_attributes(&mut self, _entry: &CaFormatEntry) -> Result<CaFormatHeader, Error> {
Ok(())
}
- pub fn restore_sequential<F: Fn(&Path) -> Result<(), Error>>(
+ pub fn restore<F>(
&mut self,
- path: &mut PathBuf, // user for error reporting
+ path: &Path, // used for error reporting
+ callback: &F,
+ ) -> Result<(), Error>
+ where F: Fn(&Path) -> Result<(), Error>
+ {
+
+ let _ = std::fs::create_dir(path);
+
+ let dir = match nix::dir::Dir::open(path, nix::fcntl::OFlag::O_DIRECTORY, nix::sys::stat::Mode::empty()) {
+ Ok(dir) => dir,
+ Err(err) => bail!("unable to open target directory {:?} - {}", path, err),
+ };
+
+ self.restore_sequential(&mut path.to_owned(), &OsString::new(), &dir, callback)
+ }
+
+ fn restore_sequential<F>(
+ &mut self,
+ path: &mut PathBuf, // used for error reporting
filename: &OsStr, // repeats path last component
parent: &nix::dir::Dir,
callback: &F,
- ) -> Result<(), Error> {
+ ) -> Result<(), Error>
+ where F: Fn(&Path) -> Result<(), Error>
+ {
let parent_fd = parent.as_raw_fd();
let ifmt = mode & libc::S_IFMT;
if ifmt == libc::S_IFDIR {
- let dir = match dir_mkdirat(parent_fd, filename) {
- Ok(dir) => dir,
- Err(err) => bail!("unable to open directory {:?} - {}", path, err),
- };
+ let dir;
+ if filename.is_empty() {
+ dir = nix::dir::Dir::openat(parent_fd, ".", OFlag::O_DIRECTORY, Mode::empty())?;
+ } else {
+ dir = match dir_mkdirat(parent_fd, filename, true) {
+ Ok(dir) => dir,
+ Err(err) => bail!("unable to open directory {:?} - {}", path, err),
+ };
+ }
let mut head = self.restore_attributes(&entry)?;
let name = self.read_filename(head.size)?;
path.push(&name);
println!("NAME: {:?}", path);
+
self.restore_sequential(path, &name, &dir, callback)?;
path.pop();
println!("Skip Goodbye");
if head.size < HEADER_SIZE { bail!("detected short goodbye table"); }
- self.reader.seek(SeekFrom::Current((head.size - HEADER_SIZE) as i64))?;
+
+ // self.reader.seek(SeekFrom::Current((head.size - HEADER_SIZE) as i64))?;
+ let mut done = 0;
+ let skip = (head.size - HEADER_SIZE) as usize;
+ while done < skip {
+ let todo = skip - done;
+ let n = if todo > self.skip_buffer.len() { self.skip_buffer.len() } else { todo };
+ let data = &mut self.skip_buffer[..n];
+ self.reader.read_exact(data)?;
+ done += n;
+ }
+
self.restore_mode(&entry, dir.as_raw_fd())?;
self.restore_mtime(&entry, dir.as_raw_fd())?;
return Ok(());
}
+ if filename.is_empty() {
+ bail!("got empty file name at {:?}", path)
+ }
+
if ifmt == libc::S_IFLNK {
// fixme: create symlink
//fixme: restore permission, acls, xattr, ...
Ok(())
}
-
- fn read_directory_entry(&mut self, start: u64, end: u64) -> Result<CaDirectoryEntry, Error> {
-
- self.reader.seek(SeekFrom::Start(start))?;
- let mut buffer = [0u8; HEADER_SIZE as usize];
- self.reader.read_exact(&mut buffer)?;
- let head = tools::map_struct::<CaFormatHeader>(&buffer)?;
-
- if u64::from_le(head.htype) != CA_FORMAT_FILENAME {
- bail!("wrong filename header type for object [{}..{}]", start, end);
- }
-
- let name_len = u64::from_le(head.size);
-
- let entry_start = start + name_len;
-
- let filename = self.read_filename(name_len)?;
-
- let head: CaFormatHeader = self.read_item()?;
- check_ca_header::<CaFormatEntry>(&head, CA_FORMAT_ENTRY)?;
- let entry: CaFormatEntry = self.read_item()?;
-
- Ok(CaDirectoryEntry {
- start: entry_start,
- end: end,
- filename: filename,
- entry: CaFormatEntry {
- feature_flags: u64::from_le(entry.feature_flags),
- mode: u64::from_le(entry.mode),
- flags: u64::from_le(entry.flags),
- uid: u64::from_le(entry.uid),
- gid: u64::from_le(entry.gid),
- mtime: u64::from_le(entry.mtime),
- },
- })
- }
-
- pub fn list_dir(&mut self, dir: &CaDirectoryEntry) -> Result<Vec<CaDirectoryEntry>, Error> {
-
- const GOODBYE_ITEM_SIZE: u64 = std::mem::size_of::<CaFormatGoodbyeItem>() as u64;
-
- let start = dir.start;
- let end = dir.end;
-
- //println!("list_dir1: {} {}", start, end);
-
- if (end - start) < (HEADER_SIZE + GOODBYE_ITEM_SIZE) {
- bail!("detected short object [{}..{}]", start, end);
- }
-
- self.reader.seek(SeekFrom::Start(end - GOODBYE_ITEM_SIZE))?;
- let mut buffer = [0u8; GOODBYE_ITEM_SIZE as usize];
- self.reader.read_exact(&mut buffer)?;
-
- let item = tools::map_struct::<CaFormatGoodbyeItem>(&buffer)?;
-
- if u64::from_le(item.hash) != CA_FORMAT_GOODBYE_TAIL_MARKER {
- bail!("missing goodbye tail marker for object [{}..{}]", start, end);
- }
-
- let goodbye_table_size = u64::from_le(item.size);
- if goodbye_table_size < (HEADER_SIZE + GOODBYE_ITEM_SIZE) {
- bail!("short goodbye table size for object [{}..{}]", start, end);
-
- }
- let goodbye_inner_size = goodbye_table_size - HEADER_SIZE - GOODBYE_ITEM_SIZE;
- if (goodbye_inner_size % GOODBYE_ITEM_SIZE) != 0 {
- bail!("wrong goodbye inner table size for entry [{}..{}]", start, end);
- }
-
- let goodbye_start = end - goodbye_table_size;
-
- if u64::from_le(item.offset) != (goodbye_start - start) {
- println!("DEBUG: {} {}", u64::from_le(item.offset), goodbye_start - start);
- bail!("wrong offset in goodbye tail marker for entry [{}..{}]", start, end);
- }
-
- self.reader.seek(SeekFrom::Start(goodbye_start))?;
- let mut buffer = [0u8; HEADER_SIZE as usize];
- self.reader.read_exact(&mut buffer)?;
- let head = tools::map_struct::<CaFormatHeader>(&buffer)?;
-
- if u64::from_le(head.htype) != CA_FORMAT_GOODBYE {
- bail!("wrong goodbye table header type for entry [{}..{}]", start, end);
- }
-
- if u64::from_le(head.size) != goodbye_table_size {
- bail!("wrong goodbye table size for entry [{}..{}]", start, end);
- }
-
- let mut buffer = [0u8; GOODBYE_ITEM_SIZE as usize];
-
- let mut range_list = Vec::new();
-
- for i in 0..goodbye_inner_size/GOODBYE_ITEM_SIZE {
- self.reader.read_exact(&mut buffer)?;
- let item = tools::map_struct::<CaFormatGoodbyeItem>(&buffer)?;
- let item_offset = u64::from_le(item.offset);
- if item_offset > (goodbye_start - start) {
- bail!("goodbye entry {} offset out of range [{}..{}] {} {} {}",
- i, start, end, item_offset, goodbye_start, start);
- }
- let item_start = goodbye_start - item_offset;
- let _item_hash = u64::from_le(item.hash);
- let item_end = item_start + u64::from_le(item.size);
- if item_end > goodbye_start {
- bail!("goodbye entry {} end out of range [{}..{}]",
- i, start, end);
- }
-
- range_list.push((item_start, item_end));
- }
-
- let mut result = vec![];
-
- for (item_start, item_end) in range_list {
- let entry = self.read_directory_entry(item_start, item_end)?;
- //println!("ENTRY: {} {} {:?}", item_start, item_end, entry.filename);
- result.push(entry);
- }
-
- Ok(result)
- }
-
- pub fn print_filenames<W: std::io::Write>(
- &mut self,
- output: &mut W,
- prefix: &mut PathBuf,
- dir: &CaDirectoryEntry,
- ) -> Result<(), Error> {
-
- let mut list = self.list_dir(dir)?;
-
- list.sort_unstable_by(|a, b| a.filename.cmp(&b.filename));
-
- for item in &list {
-
- prefix.push(item.filename.clone());
-
- let mode = item.entry.mode as u32;
-
- let ifmt = mode & libc::S_IFMT;
-
- let osstr: &OsStr = prefix.as_ref();
- output.write(osstr.as_bytes())?;
- output.write(b"\n")?;
-
- if ifmt == libc::S_IFDIR {
- self.print_filenames(output, prefix, item)?;
- } else if ifmt == libc::S_IFREG {
- } else if ifmt == libc::S_IFLNK {
- } else if ifmt == libc::S_IFBLK {
- } else if ifmt == libc::S_IFCHR {
- } else {
- bail!("unknown item mode/type for {:?}", prefix);
- }
-
- prefix.pop();
- }
-
- Ok(())
- }
}
fn file_openat(parent: RawFd, filename: &OsStr, flags: OFlag, mode: Mode) -> Result<std::fs::File, Error> {
Ok(file)
}
-fn dir_mkdirat(parent: RawFd, filename: &OsStr) -> Result<nix::dir::Dir, Error> {
+fn dir_mkdirat(parent: RawFd, filename: &OsStr, create_new: bool) -> Result<nix::dir::Dir, nix::Error> {
// call mkdirat first
let res = filename.with_nix_path(|cstr| unsafe {
libc::mkdirat(parent, cstr.as_ptr(), libc::S_IRWXU)
})?;
- Errno::result(res)?;
+
+ match Errno::result(res) {
+ Ok(_) => {},
+ Err(err) => {
+ if err == nix::Error::Sys(nix::errno::Errno::EEXIST) {
+ if create_new { return Err(err); }
+ } else {
+ return Err(err);
+ }
+ }
+ }
let dir = nix::dir::Dir::openat(parent, filename, OFlag::O_DIRECTORY, Mode::empty())?;