]> git.proxmox.com Git - proxmox-backup.git/blobdiff - src/backup/catalog.rs
switch from failure to anyhow
[proxmox-backup.git] / src / backup / catalog.rs
index ee0cfb8bf43aab8cc5c26717343fdc6b44eda79a..8d54fe37a0e1b2deea1d660e952b4620bb067adb 100644 (file)
@@ -1,4 +1,4 @@
-use failure::*;
+use anyhow::{bail, format_err, Error};
 use std::fmt;
 use std::ffi::{CStr, CString, OsStr};
 use std::os::unix::ffi::OsStrExt;
@@ -8,13 +8,16 @@ use std::convert::TryFrom;
 use chrono::offset::{TimeZone, Local};
 
 use proxmox::tools::io::ReadExt;
+use proxmox::sys::error::io_err_other;
 
 use crate::pxar::catalog::BackupCatalogWriter;
+use crate::pxar::{MatchPattern, MatchPatternSlice, MatchType};
 use crate::backup::file_formats::PROXMOX_CATALOG_FILE_MAGIC_1_0;
+use crate::tools::runtime::block_on;
 
 #[repr(u8)]
 #[derive(Copy,Clone,PartialEq)]
-pub enum CatalogEntryType {
+enum CatalogEntryType {
     Directory = b'd',
     File = b'f',
     Symlink = b'l',
@@ -49,12 +52,19 @@ impl fmt::Display for CatalogEntryType {
     }
 }
 
-struct DirEntry {
-    name: Vec<u8>,
-    attr: DirEntryAttribute,
+/// Represents a named directory entry
+///
+/// The ``attr`` property contain the exact type with type specific
+/// attributes.
+#[derive(Clone, PartialEq)]
+pub struct DirEntry {
+    pub name: Vec<u8>,
+    pub attr: DirEntryAttribute,
 }
 
-enum DirEntryAttribute {
+/// Used to specific additional attributes inside DirEntry
+#[derive(Clone, PartialEq)]
+pub enum DirEntryAttribute {
     Directory { start: u64 },
     File { size: u64, mtime: u64 },
     Symlink,
@@ -65,6 +75,54 @@ enum DirEntryAttribute {
     Socket,
 }
 
+impl DirEntry {
+
+    fn new(etype: CatalogEntryType, name: Vec<u8>, start: u64, size: u64, mtime:u64) -> Self {
+        match etype {
+            CatalogEntryType::Directory => {
+                DirEntry { name, attr: DirEntryAttribute::Directory { start } }
+            }
+            CatalogEntryType::File => {
+                DirEntry { name, attr: DirEntryAttribute::File { size, mtime } }
+            }
+            CatalogEntryType::Symlink => {
+                DirEntry { name, attr: DirEntryAttribute::Symlink }
+            }
+            CatalogEntryType::Hardlink => {
+                DirEntry { name, attr: DirEntryAttribute::Hardlink }
+            }
+            CatalogEntryType::BlockDevice => {
+                DirEntry { name, attr: DirEntryAttribute::BlockDevice }
+            }
+            CatalogEntryType::CharDevice => {
+                DirEntry { name, attr: DirEntryAttribute::CharDevice }
+            }
+            CatalogEntryType::Fifo => {
+                DirEntry { name, attr: DirEntryAttribute::Fifo }
+            }
+            CatalogEntryType::Socket => {
+                DirEntry { name, attr: DirEntryAttribute::Socket }
+            }
+        }
+    }
+
+    /// Check if DirEntry is a directory
+    pub fn is_directory(&self) -> bool {
+        match self.attr {
+            DirEntryAttribute::Directory { .. } => true,
+            _ => false,
+        }
+    }
+
+    /// Check if DirEntry is a symlink
+    pub fn is_symlink(&self) -> bool {
+        match self.attr {
+            DirEntryAttribute::Symlink { .. } => true,
+            _ => false,
+        }
+    }
+}
+
 struct DirInfo {
     name: CString,
     entries: Vec<DirEntry>,
@@ -147,7 +205,7 @@ impl DirInfo {
         Ok((self.name, data))
     }
 
-    fn parse<C: FnMut(CatalogEntryType, &[u8], u64, u64, u64) -> Result<(), Error>>(
+    fn parse<C: FnMut(CatalogEntryType, &[u8], u64, u64, u64) -> Result<bool, Error>>(
         data: &[u8],
         mut callback: C,
     ) -> Result<(), Error> {
@@ -171,19 +229,22 @@ impl DirInfo {
             let name = &mut name_buf[0..name_len];
             cursor.read_exact(name)?;
 
-            match etype {
+            let cont = match etype {
                 CatalogEntryType::Directory => {
                     let offset = catalog_decode_u64(&mut cursor)?;
-                    callback(etype, name, offset, 0, 0)?;
+                    callback(etype, name, offset, 0, 0)?
                 }
                 CatalogEntryType::File => {
                     let size = catalog_decode_u64(&mut cursor)?;
                     let mtime = catalog_decode_u64(&mut cursor)?;
-                    callback(etype, name, 0, size, mtime)?;
+                    callback(etype, name, 0, size, mtime)?
                 }
                 _ => {
-                    callback(etype, name, 0, 0, 0)?;
+                    callback(etype, name, 0, 0, 0)?
                 }
+            };
+            if !cont {
+                return Ok(());
             }
         }
 
@@ -195,6 +256,12 @@ impl DirInfo {
     }
 }
 
+/// Write small catalog files
+///
+/// A Catalogs simply contains list of files and directories
+/// (directory tree). They are use to find content without having to
+/// search the real archive (which may be large). For files, they
+/// include the last modification time and file size.
 pub struct CatalogWriter<W> {
     writer: W,
     dirstack: Vec<DirInfo>,
@@ -203,6 +270,7 @@ pub struct CatalogWriter<W> {
 
 impl <W: Write> CatalogWriter<W> {
 
+    /// Create a new  CatalogWriter instance
     pub fn new(writer: W) -> Result<Self, Error> {
         let mut me = Self { writer, dirstack: vec![ DirInfo::new_rootdir() ], pos: 0 };
         me.write_all(&PROXMOX_CATALOG_FILE_MAGIC_1_0)?;
@@ -215,6 +283,9 @@ impl <W: Write> CatalogWriter<W> {
         Ok(())
     }
 
+    /// Finish writing, flush all data
+    ///
+    /// This need to be called before drop.
     pub fn finish(&mut self) -> Result<(), Error> {
         if self.dirstack.len() != 1 {
             bail!("unable to finish catalog at level {}", self.dirstack.len());
@@ -324,10 +395,12 @@ impl SenderWriter {
 
 impl Write for SenderWriter {
     fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {
-        futures::executor::block_on(async move {
-            self.0.send(Ok(buf.to_vec())).await
-                .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err.to_string()))?;
-            Ok(buf.len())
+        block_on(async move {
+            self.0
+                .send(Ok(buf.to_vec()))
+                .await
+                .map_err(io_err_other)
+                .and(Ok(buf.len()))
         })
     }
 
@@ -336,34 +409,112 @@ impl Write for SenderWriter {
     }
 }
 
+/// Read Catalog files
 pub struct CatalogReader<R> {
     reader: R,
 }
 
 impl <R: Read + Seek> CatalogReader<R> {
 
+    /// Create a new CatalogReader instance
     pub fn new(reader: R) -> Self {
         Self { reader }
     }
 
+    /// Print whole catalog to stdout
     pub fn dump(&mut self) -> Result<(), Error> {
 
-        self.reader.seek(SeekFrom::End(-8))?;
+        let root = self.root()?;
+        match root {
+            DirEntry { attr: DirEntryAttribute::Directory { start }, .. }=> {
+                self.dump_dir(std::path::Path::new("./"), start)
+            }
+            _ => unreachable!(),
+        }
+    }
 
+    /// Get the root DirEntry
+    pub fn root(&mut self) ->  Result<DirEntry, Error>  {
+        // Root dir is special
+        self.reader.seek(SeekFrom::Start(0))?;
+        let mut magic = [ 0u8; 8];
+        self.reader.read_exact(&mut magic)?;
+        if magic != PROXMOX_CATALOG_FILE_MAGIC_1_0 {
+            bail!("got unexpected magic number for catalog");
+        }
+        self.reader.seek(SeekFrom::End(-8))?;
         let start = unsafe { self.reader.read_le_value::<u64>()? };
+        Ok(DirEntry { name: b"".to_vec(), attr: DirEntryAttribute::Directory { start } })
+    }
+
+    /// Read all directory entries
+    pub fn read_dir(
+        &mut self,
+        parent: &DirEntry,
+    ) -> Result<Vec<DirEntry>, Error>  {
+
+        let start = match parent.attr {
+            DirEntryAttribute::Directory { start } => start,
+            _ => bail!("parent is not a directory - internal error"),
+        };
+
+        let data = self.read_raw_dirinfo_block(start)?;
 
-        self.dump_dir(std::path::Path::new("./"), start)
+        let mut entry_list = Vec::new();
+
+        DirInfo::parse(&data, |etype, name, offset, size, mtime| {
+            let entry = DirEntry::new(etype, name.to_vec(), start - offset, size, mtime);
+            entry_list.push(entry);
+            Ok(true)
+        })?;
+
+        Ok(entry_list)
     }
 
-    pub fn dump_dir(&mut self, prefix: &std::path::Path, start: u64) -> Result<(), Error> {
+    /// Lockup a DirEntry inside a parent directory
+    pub fn lookup(
+        &mut self,
+        parent: &DirEntry,
+        filename: &[u8],
+    ) -> Result<DirEntry, Error>  {
 
-        self.reader.seek(SeekFrom::Start(start))?;
+        let start = match parent.attr {
+            DirEntryAttribute::Directory { start } => start,
+            _ => bail!("parent is not a directory - internal error"),
+        };
 
-        let size = catalog_decode_u64(&mut self.reader)?;
+        let data = self.read_raw_dirinfo_block(start)?;
 
-        if size < 1 { bail!("got small directory size {}", size) };
+        let mut item = None;
+        DirInfo::parse(&data, |etype, name, offset, size, mtime| {
+            if name != filename {
+                return Ok(true);
+            }
+
+            let entry = DirEntry::new(etype, name.to_vec(), start - offset, size, mtime);
+            item = Some(entry);
+            Ok(false) // stop parsing
+        })?;
+
+        match item {
+            None => bail!("no such file"),
+            Some(entry) => Ok(entry),
+        }
+    }
 
+    /// Read the raw directory info block from current reader position.
+    fn read_raw_dirinfo_block(&mut self, start: u64) ->  Result<Vec<u8>, Error>  {
+        self.reader.seek(SeekFrom::Start(start))?;
+        let size = catalog_decode_u64(&mut self.reader)?;
+        if size < 1 { bail!("got small directory size {}", size) };
         let data = self.reader.read_exact_allocated(size as usize)?;
+        Ok(data)
+    }
+
+    /// Print the content of a directory to stdout
+    pub fn dump_dir(&mut self, prefix: &std::path::Path, start: u64) -> Result<(), Error> {
+
+        let data = self.read_raw_dirinfo_block(start)?;
 
         DirInfo::parse(&data, |etype, name, offset, size, mtime| {
 
@@ -396,9 +547,49 @@ impl <R: Read + Seek> CatalogReader<R> {
                 }
             }
 
-            Ok(())
+            Ok(true)
         })
     }
+
+    /// Finds all entries matching the given match patterns and calls the
+    /// provided callback on them.
+    pub fn find(
+        &mut self,
+        mut entry: &mut Vec<DirEntry>,
+        pattern: &[MatchPatternSlice],
+        callback: &Box<fn(&[DirEntry])>,
+    ) -> Result<(), Error> {
+        let parent = entry.last().unwrap();
+        if !parent.is_directory() {
+            return Ok(())
+        }
+
+        for e in self.read_dir(parent)?  {
+            match MatchPatternSlice::match_filename_include(
+                &CString::new(e.name.clone())?,
+                e.is_directory(),
+                pattern,
+            )? {
+                (MatchType::Positive, _) => {
+                    entry.push(e);
+                    callback(&entry);
+                    let pattern = MatchPattern::from_line(b"**/*").unwrap().unwrap();
+                    let child_pattern = vec![pattern.as_slice()];
+                    self.find(&mut entry, &child_pattern, callback)?;
+                    entry.pop();
+                }
+                (MatchType::PartialPositive, child_pattern)
+                | (MatchType::PartialNegative, child_pattern) => {
+                    entry.push(e);
+                    self.find(&mut entry, &child_pattern, callback)?;
+                    entry.pop();
+                }
+                _ => {}
+            }
+        }
+
+        Ok(())
+    }
 }
 
 /// Serialize u64 as short, variable length byte sequence