]> git.proxmox.com Git - proxmox-backup.git/blobdiff - src/catar/decoder.rs
src/catar/decoder.rs: simplify public restore API
[proxmox-backup.git] / src / catar / decoder.rs
index bf65b490954f58ce7c6b2ef9e0b5682467b1a16d..d6be884bbcbf29e0ca2412f81952d07cbdf0e798 100644 (file)
@@ -6,15 +6,14 @@ use failure::*;
 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;
@@ -22,49 +21,19 @@ use nix::sys::stat::Mode;
 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> {
@@ -120,38 +89,20 @@ impl <'a, R: Read + Seek> CaTarDecoder<'a, R> {
             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> {
@@ -265,13 +216,33 @@ impl <'a, R: Read + Seek> CaTarDecoder<'a, R> {
         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();
 
@@ -285,10 +256,15 @@ impl <'a, R: Read + Seek> CaTarDecoder<'a, R> {
         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)?;
 
@@ -296,6 +272,7 @@ impl <'a, R: Read + Seek> CaTarDecoder<'a, R> {
                 let name = self.read_filename(head.size)?;
                 path.push(&name);
                 println!("NAME: {:?}", path);
+
                 self.restore_sequential(path, &name, &dir, callback)?;
                 path.pop();
 
@@ -308,7 +285,18 @@ impl <'a, R: Read + Seek> CaTarDecoder<'a, R> {
 
             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())?;
@@ -317,6 +305,10 @@ impl <'a, R: Read + Seek> CaTarDecoder<'a, R> {
             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, ...
@@ -427,168 +419,6 @@ impl <'a, R: Read + Seek> CaTarDecoder<'a, R> {
 
         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> {
@@ -602,13 +432,23 @@ fn file_openat(parent: RawFd, filename: &OsStr, flags: OFlag, mode: Mode) -> Res
     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())?;