From 8e4282610c0fc02642c92b43f89f27b1ce79e888 Mon Sep 17 00:00:00 2001 From: Dennis Schwerdel Date: Mon, 10 Apr 2017 17:53:26 +0200 Subject: [PATCH] Display backup name and path on backup integrity error --- TODO.md | 1 - src/cli/args.rs | 5 +++- src/cli/mod.rs | 7 +++-- src/prelude.rs | 2 +- src/repository/basic_io.rs | 6 ++-- src/repository/error.rs | 2 +- src/repository/info.rs | 6 ++-- src/repository/integrity.rs | 56 ++++++++++++++++++++++++------------- src/repository/mod.rs | 2 +- src/repository/vacuum.rs | 2 +- 10 files changed, 54 insertions(+), 35 deletions(-) diff --git a/TODO.md b/TODO.md index 1fb3ef8..2c66ce2 100644 --- a/TODO.md +++ b/TODO.md @@ -10,7 +10,6 @@ ## Usability * Backup directories as a thing (list, remove) -* Display backup name and path on backup integrity error * Better control over what is checked in `check` subcommand * Man pages for all minor subcommands diff --git a/src/cli/args.rs b/src/cli/args.rs index 05e2f2f..4ec5517 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -279,6 +279,7 @@ pub fn parse() -> Result<(LogLevel, Arguments), ErrorCode> { .settings(&[AppSettings::VersionlessSubcommands, AppSettings::SubcommandRequiredElseHelp]) .global_settings(&[AppSettings::AllowMissingPositional, AppSettings::UnifiedHelpMessage, AppSettings::ColoredHelp, AppSettings::ColorAuto]) .arg(Arg::from_usage("-v --verbose 'Print more information'").global(true).multiple(true).max_values(3).takes_value(false)) + .arg(Arg::from_usage("-q --quiet 'Print less information'").global(true).conflicts_with("verbose")) .subcommand(SubCommand::with_name("init").about("Initialize a new repository") .arg(Arg::from_usage("[bundle_size] --bundle-size [SIZE] 'Set the target bundle size in MiB'") .default_value(DEFAULT_BUNDLE_SIZE_STR).validator(validate_num)) @@ -410,7 +411,9 @@ pub fn parse() -> Result<(LogLevel, Arguments), ErrorCode> { .default_value(DEFAULT_HASH).validator(validate_hash)) .arg(Arg::from_usage(" 'File with test data'") .validator(validate_existing_path))).get_matches(); - let log_level = match args.subcommand().1.map(|m| m.occurrences_of("verbose")).unwrap_or(0) + args.occurrences_of("verbose") { + let verbose_count = args.subcommand().1.map(|m| m.occurrences_of("verbose")).unwrap_or(0) + args.occurrences_of("verbose"); + let quiet_count= args.subcommand().1.map(|m| m.occurrences_of("quiet")).unwrap_or(0) + args.occurrences_of("quiet"); + let log_level = match 1 + verbose_count - quiet_count { 0 => LogLevel::Warn, 1 => LogLevel::Info, 2 => LogLevel::Debug, diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 3aaa37b..bd455da 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -11,6 +11,7 @@ use std::collections::HashMap; use std::io::{BufReader, BufRead}; use std::fs::File; use std::env; +use std::path::Path; use self::args::Arguments; @@ -421,9 +422,9 @@ pub fn run() -> Result<(), ErrorCode> { let mut repo = try!(open_repository(&repo_path)); if let Some(backup_name) = backup_name { let backup = try!(get_backup(&repo, &backup_name)); - if let Some(inode) = inode { - let inode = checked!(repo.get_backup_inode(&backup, inode), "load subpath inode", ErrorCode::LoadInode); - checked!(repo.check_inode(&inode), "check inode", ErrorCode::CheckRun) + if let Some(path) = inode { + let inode = checked!(repo.get_backup_inode(&backup, &path), "load subpath inode", ErrorCode::LoadInode); + checked!(repo.check_inode(&inode, Path::new(&path)), "check inode", ErrorCode::CheckRun) } else { checked!(repo.check_backup(&backup), "check backup", ErrorCode::CheckRun) } diff --git a/src/prelude.rs b/src/prelude.rs index 07254d3..962bbaa 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,7 +1,7 @@ pub use ::util::*; pub use ::bundledb::{BundleReader, BundleMode, BundleWriter, BundleInfo, BundleId, BundleDbError, BundleDb, BundleWriterError, StoredBundle}; pub use ::chunker::{ChunkerType, Chunker, ChunkerStatus, IChunker, ChunkerError}; -pub use ::repository::{Repository, Backup, Config, RepositoryError, RepositoryInfo, Inode, FileType, RepositoryIntegrityError, BackupFileError, BackupError, BackupOptions, BundleAnalysis, FileData, DiffType, InodeError, RepositoryLayout}; +pub use ::repository::{Repository, Backup, Config, RepositoryError, RepositoryInfo, Inode, FileType, IntegrityError, BackupFileError, BackupError, BackupOptions, BundleAnalysis, FileData, DiffType, InodeError, RepositoryLayout}; pub use ::index::{Index, Location, IndexError}; pub use ::mount::FuseFilesystem; diff --git a/src/repository/basic_io.rs b/src/repository/basic_io.rs index 784c247..0420179 100644 --- a/src/repository/basic_io.rs +++ b/src/repository/basic_io.rs @@ -35,7 +35,7 @@ impl<'a> Read for ChunkReader<'a> { if let Some(chunk) = self.chunks.pop_front() { self.data = match self.repo.get_chunk(chunk.0) { Ok(Some(data)) => data, - Ok(None) => return Err(io::Error::new(io::ErrorKind::Other, RepositoryIntegrityError::MissingChunk(chunk.0))), + Ok(None) => return Err(io::Error::new(io::ErrorKind::Other, IntegrityError::MissingChunk(chunk.0))), Err(err) => return Err(io::Error::new(io::ErrorKind::Other, err)) }; self.pos = 0; @@ -55,7 +55,7 @@ impl<'a> Read for ChunkReader<'a> { impl Repository { pub fn get_bundle_id(&self, id: u32) -> Result { - self.bundle_map.get(id).ok_or_else(|| RepositoryIntegrityError::MissingBundleId(id).into()) + self.bundle_map.get(id).ok_or_else(|| IntegrityError::MissingBundleId(id).into()) } pub fn get_chunk(&mut self, hash: Hash) -> Result>, RepositoryError> { @@ -202,7 +202,7 @@ impl Repository { #[inline] pub fn get_stream(&mut self, chunks: &[Chunk], w: &mut W) -> Result<(), RepositoryError> { for &(ref hash, len) in chunks { - let data = try!(try!(self.get_chunk(*hash)).ok_or_else(|| RepositoryIntegrityError::MissingChunk(hash.clone()))); + let data = try!(try!(self.get_chunk(*hash)).ok_or_else(|| IntegrityError::MissingChunk(hash.clone()))); debug_assert_eq!(data.len() as u32, len); try!(w.write_all(&data)); } diff --git a/src/repository/error.rs b/src/repository/error.rs index 922d6d8..f818674 100644 --- a/src/repository/error.rs +++ b/src/repository/error.rs @@ -72,7 +72,7 @@ quick_error!{ description("Bundle map error") display("Repository error: bundle map error\n\tcaused by: {}", err) } - Integrity(err: RepositoryIntegrityError) { + Integrity(err: IntegrityError) { from() cause(err) description("Integrity error") diff --git a/src/repository/info.rs b/src/repository/info.rs index e4a4c77..de1aa74 100644 --- a/src/repository/info.rs +++ b/src/repository/info.rs @@ -51,10 +51,10 @@ impl Repository { bundle.used_raw_size += len as usize; } } else { - return Err(RepositoryIntegrityError::MissingBundleId(pos.bundle).into()); + return Err(IntegrityError::MissingBundleId(pos.bundle).into()); } } else { - return Err(RepositoryIntegrityError::MissingChunk(hash).into()); + return Err(IntegrityError::MissingChunk(hash).into()); } } Ok(new) @@ -63,7 +63,7 @@ impl Repository { pub fn analyze_usage(&mut self) -> Result, RepositoryError> { let mut usage = HashMap::new(); for (id, bundle) in self.bundle_map.bundles() { - let bundle = try!(self.bundles.get_bundle_info(&bundle).ok_or_else(|| RepositoryIntegrityError::MissingBundle(bundle))); + let bundle = try!(self.bundles.get_bundle_info(&bundle).ok_or_else(|| IntegrityError::MissingBundle(bundle))); usage.insert(id, BundleAnalysis { chunk_usage: Bitmap::new(bundle.info.chunk_count), info: bundle.info.clone(), diff --git a/src/repository/integrity.rs b/src/repository/integrity.rs index 0db05f2..f804a4c 100644 --- a/src/repository/integrity.rs +++ b/src/repository/integrity.rs @@ -1,11 +1,12 @@ use ::prelude::*; use std::collections::VecDeque; +use std::path::{Path, PathBuf}; quick_error!{ #[derive(Debug)] - pub enum RepositoryIntegrityError { + pub enum IntegrityError { MissingChunk(hash: Hash) { description("Missing chunk") display("Missing chunk: {}", hash) @@ -25,6 +26,16 @@ quick_error!{ InvalidNextBundleId { description("Invalid next bundle id") } + BrokenInode(path: PathBuf, err: Box) { + cause(err) + description("Broken inode") + display("Broken inode: {:?}\n\tcaused by: {}", path, err) + } + MissingInodeData(path: PathBuf, err: Box) { + cause(err) + description("Missing inode data") + display("Missing inode data in: {:?}\n\tcaused by: {}", path, err) + } } } @@ -37,11 +48,11 @@ impl Repository { let bundle = if let Some(bundle) = self.bundles.get_bundle_info(&bundle_id) { bundle } else { - return Err(RepositoryIntegrityError::MissingBundle(bundle_id.clone()).into()) + return Err(IntegrityError::MissingBundle(bundle_id.clone()).into()) }; // Get chunk from bundle if bundle.info.chunk_count <= location.chunk as usize { - return Err(RepositoryIntegrityError::NoSuchChunk(bundle_id.clone(), location.chunk).into()) + return Err(IntegrityError::NoSuchChunk(bundle_id.clone(), location.chunk).into()) } Ok(()) }) @@ -49,13 +60,13 @@ impl Repository { fn check_repository(&self) -> Result<(), RepositoryError> { if self.next_data_bundle == self.next_meta_bundle { - return Err(RepositoryIntegrityError::InvalidNextBundleId.into()) + return Err(IntegrityError::InvalidNextBundleId.into()) } if self.bundle_map.get(self.next_data_bundle).is_some() { - return Err(RepositoryIntegrityError::InvalidNextBundleId.into()) + return Err(IntegrityError::InvalidNextBundleId.into()) } if self.bundle_map.get(self.next_meta_bundle).is_some() { - return Err(RepositoryIntegrityError::InvalidNextBundleId.into()) + return Err(IntegrityError::InvalidNextBundleId.into()) } Ok(()) } @@ -67,7 +78,7 @@ impl Repository { new |= !checked.get(pos); checked.set(pos); } else { - return Err(RepositoryIntegrityError::MissingChunk(hash).into()) + return Err(IntegrityError::MissingChunk(hash).into()) } } Ok(new) @@ -90,20 +101,24 @@ impl Repository { Ok(()) } - fn check_subtree(&mut self, chunks: &[Chunk], checked: &mut Bitmap) -> Result<(), RepositoryError> { + fn check_subtree(&mut self, path: PathBuf, chunks: &[Chunk], checked: &mut Bitmap) -> Result<(), RepositoryError> { let mut todo = VecDeque::new(); - todo.push_back(ChunkList::from(chunks.to_vec())); - while let Some(chunks) = todo.pop_front() { - if !try!(self.check_chunks(checked, &chunks)) { - continue + todo.push_back((path, ChunkList::from(chunks.to_vec()))); + while let Some((path, chunks)) = todo.pop_front() { + match self.check_chunks(checked, &chunks) { + Ok(false) => continue, // checked this chunk list before + Ok(true) => (), + Err(err) => return Err(IntegrityError::BrokenInode(path, Box::new(err)).into()) } let inode = try!(self.get_inode(&chunks)); // Mark the content chunks as used - try!(self.check_inode_contents(&inode, checked)); + if let Err(err) = self.check_inode_contents(&inode, checked) { + return Err(IntegrityError::MissingInodeData(path, Box::new(err)).into()) + } // Put children in todo if let Some(children) = inode.children { - for (_name, chunks) in children { - todo.push_back(chunks); + for (name, chunks) in children { + todo.push_back((path.join(name), chunks)); } } } @@ -112,15 +127,15 @@ impl Repository { pub fn check_backup(&mut self, backup: &Backup) -> Result<(), RepositoryError> { let mut checked = Bitmap::new(self.index.capacity()); - self.check_subtree(&backup.root, &mut checked) + self.check_subtree(Path::new("").to_path_buf(), &backup.root, &mut checked) } - pub fn check_inode(&mut self, inode: &Inode) -> Result<(), RepositoryError> { + pub fn check_inode(&mut self, inode: &Inode, path: &Path) -> Result<(), RepositoryError> { let mut checked = Bitmap::new(self.index.capacity()); try!(self.check_inode_contents(inode, &mut checked)); if let Some(ref children) = inode.children { for chunks in children.values() { - try!(self.check_subtree(chunks, &mut checked)) + try!(self.check_subtree(path.to_path_buf(), chunks, &mut checked)) } } Ok(()) @@ -136,8 +151,9 @@ impl Repository { }, Err(err) => return Err(err) }; - for (_name, backup) in backup_map { - try!(self.check_subtree(&backup.root, &mut checked)); + for (name, backup) in backup_map { + let path = name+"::"; + try!(self.check_subtree(Path::new(&path).to_path_buf(), &backup.root, &mut checked)); } Ok(()) } diff --git a/src/repository/mod.rs b/src/repository/mod.rs index 72527ec..54234e3 100644 --- a/src/repository/mod.rs +++ b/src/repository/mod.rs @@ -26,7 +26,7 @@ pub use self::config::Config; pub use self::metadata::{Inode, FileType, FileData, InodeError}; pub use self::backup::{BackupError, BackupOptions, DiffType}; pub use self::backup_file::{Backup, BackupFileError}; -pub use self::integrity::RepositoryIntegrityError; +pub use self::integrity::IntegrityError; pub use self::info::{RepositoryInfo, BundleAnalysis}; pub use self::layout::RepositoryLayout; use self::bundle_map::BundleMap; diff --git a/src/repository/vacuum.rs b/src/repository/vacuum.rs index 4862629..c09a620 100644 --- a/src/repository/vacuum.rs +++ b/src/repository/vacuum.rs @@ -9,7 +9,7 @@ impl Repository { try!(self.bundles.delete_bundle(&bundle)); Ok(()) } else { - Err(RepositoryIntegrityError::MissingBundleId(id).into()) + Err(IntegrityError::MissingBundleId(id).into()) } }