Display backup name and path on backup integrity error

pull/10/head
Dennis Schwerdel 2017-04-10 17:53:26 +02:00 committed by Dennis Schwerdel
parent 77e396094a
commit 8e4282610c
10 changed files with 54 additions and 35 deletions

View File

@ -10,7 +10,6 @@
## Usability ## Usability
* Backup directories as a thing (list, remove) * 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 * Better control over what is checked in `check` subcommand
* Man pages for all minor subcommands * Man pages for all minor subcommands

View File

@ -279,6 +279,7 @@ pub fn parse() -> Result<(LogLevel, Arguments), ErrorCode> {
.settings(&[AppSettings::VersionlessSubcommands, AppSettings::SubcommandRequiredElseHelp]) .settings(&[AppSettings::VersionlessSubcommands, AppSettings::SubcommandRequiredElseHelp])
.global_settings(&[AppSettings::AllowMissingPositional, AppSettings::UnifiedHelpMessage, AppSettings::ColoredHelp, AppSettings::ColorAuto]) .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("-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") .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'") .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)) .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)) .default_value(DEFAULT_HASH).validator(validate_hash))
.arg(Arg::from_usage("<FILE> 'File with test data'") .arg(Arg::from_usage("<FILE> 'File with test data'")
.validator(validate_existing_path))).get_matches(); .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, 0 => LogLevel::Warn,
1 => LogLevel::Info, 1 => LogLevel::Info,
2 => LogLevel::Debug, 2 => LogLevel::Debug,

View File

@ -11,6 +11,7 @@ use std::collections::HashMap;
use std::io::{BufReader, BufRead}; use std::io::{BufReader, BufRead};
use std::fs::File; use std::fs::File;
use std::env; use std::env;
use std::path::Path;
use self::args::Arguments; use self::args::Arguments;
@ -421,9 +422,9 @@ pub fn run() -> Result<(), ErrorCode> {
let mut repo = try!(open_repository(&repo_path)); let mut repo = try!(open_repository(&repo_path));
if let Some(backup_name) = backup_name { if let Some(backup_name) = backup_name {
let backup = try!(get_backup(&repo, &backup_name)); let backup = try!(get_backup(&repo, &backup_name));
if let Some(inode) = inode { if let Some(path) = inode {
let inode = checked!(repo.get_backup_inode(&backup, inode), "load subpath inode", ErrorCode::LoadInode); let inode = checked!(repo.get_backup_inode(&backup, &path), "load subpath inode", ErrorCode::LoadInode);
checked!(repo.check_inode(&inode), "check inode", ErrorCode::CheckRun) checked!(repo.check_inode(&inode, Path::new(&path)), "check inode", ErrorCode::CheckRun)
} else { } else {
checked!(repo.check_backup(&backup), "check backup", ErrorCode::CheckRun) checked!(repo.check_backup(&backup), "check backup", ErrorCode::CheckRun)
} }

View File

@ -1,7 +1,7 @@
pub use ::util::*; pub use ::util::*;
pub use ::bundledb::{BundleReader, BundleMode, BundleWriter, BundleInfo, BundleId, BundleDbError, BundleDb, BundleWriterError, StoredBundle}; pub use ::bundledb::{BundleReader, BundleMode, BundleWriter, BundleInfo, BundleId, BundleDbError, BundleDb, BundleWriterError, StoredBundle};
pub use ::chunker::{ChunkerType, Chunker, ChunkerStatus, IChunker, ChunkerError}; 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 ::index::{Index, Location, IndexError};
pub use ::mount::FuseFilesystem; pub use ::mount::FuseFilesystem;

View File

@ -35,7 +35,7 @@ impl<'a> Read for ChunkReader<'a> {
if let Some(chunk) = self.chunks.pop_front() { if let Some(chunk) = self.chunks.pop_front() {
self.data = match self.repo.get_chunk(chunk.0) { self.data = match self.repo.get_chunk(chunk.0) {
Ok(Some(data)) => data, 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)) Err(err) => return Err(io::Error::new(io::ErrorKind::Other, err))
}; };
self.pos = 0; self.pos = 0;
@ -55,7 +55,7 @@ impl<'a> Read for ChunkReader<'a> {
impl Repository { impl Repository {
pub fn get_bundle_id(&self, id: u32) -> Result<BundleId, RepositoryError> { pub fn get_bundle_id(&self, id: u32) -> Result<BundleId, RepositoryError> {
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<Option<Vec<u8>>, RepositoryError> { pub fn get_chunk(&mut self, hash: Hash) -> Result<Option<Vec<u8>>, RepositoryError> {
@ -202,7 +202,7 @@ impl Repository {
#[inline] #[inline]
pub fn get_stream<W: Write>(&mut self, chunks: &[Chunk], w: &mut W) -> Result<(), RepositoryError> { pub fn get_stream<W: Write>(&mut self, chunks: &[Chunk], w: &mut W) -> Result<(), RepositoryError> {
for &(ref hash, len) in chunks { 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); debug_assert_eq!(data.len() as u32, len);
try!(w.write_all(&data)); try!(w.write_all(&data));
} }

View File

@ -72,7 +72,7 @@ quick_error!{
description("Bundle map error") description("Bundle map error")
display("Repository error: bundle map error\n\tcaused by: {}", err) display("Repository error: bundle map error\n\tcaused by: {}", err)
} }
Integrity(err: RepositoryIntegrityError) { Integrity(err: IntegrityError) {
from() from()
cause(err) cause(err)
description("Integrity error") description("Integrity error")

View File

@ -51,10 +51,10 @@ impl Repository {
bundle.used_raw_size += len as usize; bundle.used_raw_size += len as usize;
} }
} else { } else {
return Err(RepositoryIntegrityError::MissingBundleId(pos.bundle).into()); return Err(IntegrityError::MissingBundleId(pos.bundle).into());
} }
} else { } else {
return Err(RepositoryIntegrityError::MissingChunk(hash).into()); return Err(IntegrityError::MissingChunk(hash).into());
} }
} }
Ok(new) Ok(new)
@ -63,7 +63,7 @@ impl Repository {
pub fn analyze_usage(&mut self) -> Result<HashMap<u32, BundleAnalysis>, RepositoryError> { pub fn analyze_usage(&mut self) -> Result<HashMap<u32, BundleAnalysis>, RepositoryError> {
let mut usage = HashMap::new(); let mut usage = HashMap::new();
for (id, bundle) in self.bundle_map.bundles() { 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 { usage.insert(id, BundleAnalysis {
chunk_usage: Bitmap::new(bundle.info.chunk_count), chunk_usage: Bitmap::new(bundle.info.chunk_count),
info: bundle.info.clone(), info: bundle.info.clone(),

View File

@ -1,11 +1,12 @@
use ::prelude::*; use ::prelude::*;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::path::{Path, PathBuf};
quick_error!{ quick_error!{
#[derive(Debug)] #[derive(Debug)]
pub enum RepositoryIntegrityError { pub enum IntegrityError {
MissingChunk(hash: Hash) { MissingChunk(hash: Hash) {
description("Missing chunk") description("Missing chunk")
display("Missing chunk: {}", hash) display("Missing chunk: {}", hash)
@ -25,6 +26,16 @@ quick_error!{
InvalidNextBundleId { InvalidNextBundleId {
description("Invalid next bundle id") description("Invalid next bundle id")
} }
BrokenInode(path: PathBuf, err: Box<RepositoryError>) {
cause(err)
description("Broken inode")
display("Broken inode: {:?}\n\tcaused by: {}", path, err)
}
MissingInodeData(path: PathBuf, err: Box<RepositoryError>) {
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) { let bundle = if let Some(bundle) = self.bundles.get_bundle_info(&bundle_id) {
bundle bundle
} else { } else {
return Err(RepositoryIntegrityError::MissingBundle(bundle_id.clone()).into()) return Err(IntegrityError::MissingBundle(bundle_id.clone()).into())
}; };
// Get chunk from bundle // Get chunk from bundle
if bundle.info.chunk_count <= location.chunk as usize { 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(()) Ok(())
}) })
@ -49,13 +60,13 @@ impl Repository {
fn check_repository(&self) -> Result<(), RepositoryError> { fn check_repository(&self) -> Result<(), RepositoryError> {
if self.next_data_bundle == self.next_meta_bundle { 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() { 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() { if self.bundle_map.get(self.next_meta_bundle).is_some() {
return Err(RepositoryIntegrityError::InvalidNextBundleId.into()) return Err(IntegrityError::InvalidNextBundleId.into())
} }
Ok(()) Ok(())
} }
@ -67,7 +78,7 @@ impl Repository {
new |= !checked.get(pos); new |= !checked.get(pos);
checked.set(pos); checked.set(pos);
} else { } else {
return Err(RepositoryIntegrityError::MissingChunk(hash).into()) return Err(IntegrityError::MissingChunk(hash).into())
} }
} }
Ok(new) Ok(new)
@ -90,20 +101,24 @@ impl Repository {
Ok(()) 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(); let mut todo = VecDeque::new();
todo.push_back(ChunkList::from(chunks.to_vec())); todo.push_back((path, ChunkList::from(chunks.to_vec())));
while let Some(chunks) = todo.pop_front() { while let Some((path, chunks)) = todo.pop_front() {
if !try!(self.check_chunks(checked, &chunks)) { match self.check_chunks(checked, &chunks) {
continue 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)); let inode = try!(self.get_inode(&chunks));
// Mark the content chunks as used // 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 // Put children in todo
if let Some(children) = inode.children { if let Some(children) = inode.children {
for (_name, chunks) in children { for (name, chunks) in children {
todo.push_back(chunks); todo.push_back((path.join(name), chunks));
} }
} }
} }
@ -112,15 +127,15 @@ impl Repository {
pub fn check_backup(&mut self, backup: &Backup) -> Result<(), RepositoryError> { pub fn check_backup(&mut self, backup: &Backup) -> Result<(), RepositoryError> {
let mut checked = Bitmap::new(self.index.capacity()); 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()); let mut checked = Bitmap::new(self.index.capacity());
try!(self.check_inode_contents(inode, &mut checked)); try!(self.check_inode_contents(inode, &mut checked));
if let Some(ref children) = inode.children { if let Some(ref children) = inode.children {
for chunks in children.values() { for chunks in children.values() {
try!(self.check_subtree(chunks, &mut checked)) try!(self.check_subtree(path.to_path_buf(), chunks, &mut checked))
} }
} }
Ok(()) Ok(())
@ -136,8 +151,9 @@ impl Repository {
}, },
Err(err) => return Err(err) Err(err) => return Err(err)
}; };
for (_name, backup) in backup_map { for (name, backup) in backup_map {
try!(self.check_subtree(&backup.root, &mut checked)); let path = name+"::";
try!(self.check_subtree(Path::new(&path).to_path_buf(), &backup.root, &mut checked));
} }
Ok(()) Ok(())
} }

View File

@ -26,7 +26,7 @@ pub use self::config::Config;
pub use self::metadata::{Inode, FileType, FileData, InodeError}; pub use self::metadata::{Inode, FileType, FileData, InodeError};
pub use self::backup::{BackupError, BackupOptions, DiffType}; pub use self::backup::{BackupError, BackupOptions, DiffType};
pub use self::backup_file::{Backup, BackupFileError}; 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::info::{RepositoryInfo, BundleAnalysis};
pub use self::layout::RepositoryLayout; pub use self::layout::RepositoryLayout;
use self::bundle_map::BundleMap; use self::bundle_map::BundleMap;

View File

@ -9,7 +9,7 @@ impl Repository {
try!(self.bundles.delete_bundle(&bundle)); try!(self.bundles.delete_bundle(&bundle));
Ok(()) Ok(())
} else { } else {
Err(RepositoryIntegrityError::MissingBundleId(id).into()) Err(IntegrityError::MissingBundleId(id).into())
} }
} }