diff --git a/TODO.md b/TODO.md index 7602690..7bade41 100644 --- a/TODO.md +++ b/TODO.md @@ -3,7 +3,7 @@ ## Functionality * XAttrs in fuse * XAttrs in tar -* `check --repair` +* Repair ## Stability / Reliability * Lock the local repository to avoid index corruption diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 1567bd3..4c3058d 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -438,6 +438,7 @@ pub fn run() -> Result<(), ErrorCode> { } else { checked!(repo.check_backups(), "check repository", ErrorCode::CheckRun) } + repo.set_clean(); info!("Integrity verified") }, Arguments::List{repo_path, backup_name, inode} => { diff --git a/src/repository/backup.rs b/src/repository/backup.rs index ca8575f..6d9c97d 100644 --- a/src/repository/backup.rs +++ b/src/repository/backup.rs @@ -225,6 +225,10 @@ impl Repository { #[allow(dead_code)] pub fn create_backup_recursively>(&mut self, path: P, reference: Option<&Backup>, options: &BackupOptions) -> Result { let _lock = try!(self.lock(false)); + if self.dirty { + return Err(RepositoryError::Dirty) + } + self.dirty = true; let reference_inode = reference.and_then(|b| self.get_inode(&b.root).ok()); let mut backup = Backup::default(); backup.config = self.config.clone(); @@ -251,6 +255,7 @@ impl Repository { backup.bundle_count = info_after.bundle_count - info_before.bundle_count; backup.chunk_count = info_after.chunk_count - info_before.chunk_count; backup.avg_chunk_size = backup.deduplicated_data_size as f32 / backup.chunk_count as f32; + self.dirty = false; if failed_paths.is_empty() { Ok(backup) } else { diff --git a/src/repository/error.rs b/src/repository/error.rs index f818674..6a3e7d7 100644 --- a/src/repository/error.rs +++ b/src/repository/error.rs @@ -78,6 +78,10 @@ quick_error!{ description("Integrity error") display("Repository error: integrity error\n\tcaused by: {}", err) } + Dirty { + description("Dirty repository") + display("The repository is dirty, please run a check") + } Backup(err: BackupError) { from() cause(err) diff --git a/src/repository/info.rs b/src/repository/info.rs index de1aa74..ec503ec 100644 --- a/src/repository/info.rs +++ b/src/repository/info.rs @@ -61,6 +61,10 @@ impl Repository { } pub fn analyze_usage(&mut self) -> Result, RepositoryError> { + if self.dirty { + return Err(RepositoryError::Dirty) + } + self.dirty = true; 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(|| IntegrityError::MissingBundle(bundle))); @@ -101,6 +105,7 @@ impl Repository { } } } + self.dirty = false; Ok(usage) } diff --git a/src/repository/layout.rs b/src/repository/layout.rs index d827c86..2d5130b 100644 --- a/src/repository/layout.rs +++ b/src/repository/layout.rs @@ -102,4 +102,8 @@ impl RepositoryLayout { pub fn remote_bundle_cache_path(&self) -> PathBuf { self.0.join("bundles/remote.cache") } + + pub fn dirtyfile_path(&self) -> PathBuf { + self.0.join("dirty") + } } diff --git a/src/repository/mod.rs b/src/repository/mod.rs index d3df8a0..9b3758e 100644 --- a/src/repository/mod.rs +++ b/src/repository/mod.rs @@ -48,7 +48,8 @@ pub struct Repository { data_bundle: Option, meta_bundle: Option, chunker: Chunker, - locks: LockFolder + locks: LockFolder, + dirty: bool } @@ -92,8 +93,10 @@ impl Repository { (BundleMap::create(), true) } }; + let dirty = layout.dirtyfile_path().exists(); let mut repo = Repository { layout: layout, + dirty: true, chunker: config.chunker.create(), config: config, index: index, @@ -128,6 +131,7 @@ impl Repository { if rebuild_index { try!(repo.rebuild_index()); } + repo.dirty = dirty; Ok(repo) } @@ -190,6 +194,10 @@ impl Repository { } pub fn flush(&mut self) -> Result<(), RepositoryError> { + let dirtyfile = self.layout.dirtyfile_path(); + if self.dirty && !dirtyfile.exists() { + try!(File::create(&dirtyfile)); + } if self.data_bundle.is_some() { let mut finished = None; mem::swap(&mut self.data_bundle, &mut finished); @@ -211,6 +219,9 @@ impl Repository { try!(self.bundles.finish_uploads()); try!(self.save_bundle_map()); try!(self.bundles.save_cache()); + if !self.dirty && dirtyfile.exists() { + try!(fs::remove_file(&dirtyfile)); + } Ok(()) } @@ -281,6 +292,10 @@ impl Repository { fn lock(&self, exclusive: bool) -> Result { Ok(try!(self.locks.lock(exclusive))) } + + pub fn set_clean(&mut self) { + self.dirty = false; + } } diff --git a/src/repository/tarfile.rs b/src/repository/tarfile.rs index 0e20c8b..33ca5da 100644 --- a/src/repository/tarfile.rs +++ b/src/repository/tarfile.rs @@ -155,6 +155,10 @@ impl Repository { pub fn import_tarfile>(&mut self, tarfile: P) -> Result { let _lock = try!(self.lock(false)); + if self.dirty { + return Err(RepositoryError::Dirty) + } + self.dirty = true; let mut backup = Backup::default(); backup.config = self.config.clone(); backup.host = get_hostname().unwrap_or_else(|_| "".to_string()); @@ -177,6 +181,7 @@ impl Repository { backup.bundle_count = info_after.bundle_count - info_before.bundle_count; backup.chunk_count = info_after.chunk_count - info_before.chunk_count; backup.avg_chunk_size = backup.deduplicated_data_size as f32 / backup.chunk_count as f32; + self.dirty = false; if failed_paths.is_empty() { Ok(backup) } else { diff --git a/src/repository/vacuum.rs b/src/repository/vacuum.rs index c09a620..4ec0abe 100644 --- a/src/repository/vacuum.rs +++ b/src/repository/vacuum.rs @@ -17,6 +17,10 @@ impl Repository { try!(self.flush()); info!("Locking repository"); let _lock = try!(self.lock(true)); + if self.dirty { + return Err(RepositoryError::Dirty) + } + self.dirty = true; info!("Analyzing chunk usage"); let usage = try!(self.analyze_usage()); let mut data_total = 0; @@ -36,6 +40,7 @@ impl Repository { } info!("Reclaiming {} by rewriting {} bundles", to_file_size(reclaim_space as u64), rewrite_bundles.len()); if !force { + self.dirty = false; return Ok(()) } for id in &rewrite_bundles { @@ -65,6 +70,7 @@ impl Repository { try!(self.delete_bundle(id)); } try!(self.save_bundle_map()); + self.dirty = false; Ok(()) } }