Locking local repository to avoid index corruption (closes #4)

pull/10/head
Dennis Schwerdel 2017-04-12 08:32:27 +02:00
parent b4b004dd23
commit 1ab11c8ff9
7 changed files with 90 additions and 19 deletions

View File

@ -397,7 +397,7 @@ pub fn run() -> Result<(), ErrorCode> {
} }
}, },
Arguments::Prune{repo_path, prefix, daily, weekly, monthly, yearly, force} => { Arguments::Prune{repo_path, prefix, daily, weekly, monthly, yearly, force} => {
let repo = try!(open_repository(&repo_path)); let mut repo = try!(open_repository(&repo_path));
if daily + weekly + monthly + yearly == 0 { if daily + weekly + monthly + yearly == 0 {
error!("This would remove all those backups"); error!("This would remove all those backups");
return Err(ErrorCode::UnsafeArgs) return Err(ErrorCode::UnsafeArgs)

View File

@ -51,12 +51,14 @@ impl Repository {
} }
pub fn save_backup(&mut self, backup: &Backup, name: &str) -> Result<(), RepositoryError> { pub fn save_backup(&mut self, backup: &Backup, name: &str) -> Result<(), RepositoryError> {
try!(self.write_mode());
let path = self.layout.backup_path(name); let path = self.layout.backup_path(name);
try!(fs::create_dir_all(path.parent().unwrap())); try!(fs::create_dir_all(path.parent().unwrap()));
Ok(try!(backup.save_to(&self.crypto.lock().unwrap(), self.config.encryption.clone(), path))) Ok(try!(backup.save_to(&self.crypto.lock().unwrap(), self.config.encryption.clone(), path)))
} }
pub fn delete_backup(&self, name: &str) -> Result<(), RepositoryError> { pub fn delete_backup(&mut self, name: &str) -> Result<(), RepositoryError> {
try!(self.write_mode());
let mut path = self.layout.backup_path(name); let mut path = self.layout.backup_path(name);
try!(fs::remove_file(&path)); try!(fs::remove_file(&path));
loop { loop {
@ -69,7 +71,8 @@ impl Repository {
} }
pub fn prune_backups(&self, prefix: &str, daily: usize, weekly: usize, monthly: usize, yearly: usize, force: bool) -> Result<(), RepositoryError> { pub fn prune_backups(&mut self, prefix: &str, daily: usize, weekly: usize, monthly: usize, yearly: usize, force: bool) -> Result<(), RepositoryError> {
try!(self.write_mode());
let mut backups = Vec::new(); let mut backups = Vec::new();
let backup_map = match self.get_backups() { let backup_map = match self.get_backups() {
Ok(backup_map) => backup_map, Ok(backup_map) => backup_map,
@ -224,6 +227,7 @@ impl Repository {
} }
pub fn create_backup_recursively<P: AsRef<Path>>(&mut self, path: P, reference: Option<&Backup>, options: &BackupOptions) -> Result<Backup, RepositoryError> { pub fn create_backup_recursively<P: AsRef<Path>>(&mut self, path: P, reference: Option<&Backup>, options: &BackupOptions) -> Result<Backup, RepositoryError> {
try!(self.write_mode());
let _lock = try!(self.lock(false)); let _lock = try!(self.lock(false));
if self.dirty { if self.dirty {
return Err(RepositoryError::Dirty) return Err(RepositoryError::Dirty)
@ -264,6 +268,7 @@ impl Repository {
} }
pub fn remove_backup_path<P: AsRef<Path>>(&mut self, backup: &mut Backup, path: P) -> Result<(), RepositoryError> { pub fn remove_backup_path<P: AsRef<Path>>(&mut self, backup: &mut Backup, path: P) -> Result<(), RepositoryError> {
try!(self.write_mode());
let _lock = try!(self.lock(false)); let _lock = try!(self.lock(false));
let mut inodes = try!(self.get_backup_path(backup, path)); let mut inodes = try!(self.get_backup_path(backup, path));
let to_remove = inodes.pop().unwrap(); let to_remove = inodes.pop().unwrap();

View File

@ -40,6 +40,11 @@ impl RepositoryLayout {
self.0.join("bundles.map") self.0.join("bundles.map")
} }
#[inline]
pub fn local_locks_path(&self) -> PathBuf {
self.0.join("locks")
}
#[inline] #[inline]
pub fn backups_path(&self) -> PathBuf { pub fn backups_path(&self) -> PathBuf {
self.0.join("remote/backups") self.0.join("remote/backups")

View File

@ -48,7 +48,9 @@ pub struct Repository {
data_bundle: Option<BundleWriter>, data_bundle: Option<BundleWriter>,
meta_bundle: Option<BundleWriter>, meta_bundle: Option<BundleWriter>,
chunker: Chunker, chunker: Chunker,
locks: LockFolder, remote_locks: LockFolder,
local_locks: LockFolder,
lock: LockHandle,
dirty: bool dirty: bool
} }
@ -59,6 +61,7 @@ impl Repository {
try!(fs::create_dir(layout.base_path())); try!(fs::create_dir(layout.base_path()));
try!(File::create(layout.excludes_path()).and_then(|mut f| f.write_all(DEFAULT_EXCLUDES))); try!(File::create(layout.excludes_path()).and_then(|mut f| f.write_all(DEFAULT_EXCLUDES)));
try!(fs::create_dir(layout.keys_path())); try!(fs::create_dir(layout.keys_path()));
try!(fs::create_dir(layout.local_locks_path()));
try!(symlink(remote, layout.remote_path())); try!(symlink(remote, layout.remote_path()));
try!(File::create(layout.remote_readme_path()).and_then(|mut f| f.write_all(REPOSITORY_README))); try!(File::create(layout.remote_readme_path()).and_then(|mut f| f.write_all(REPOSITORY_README)));
try!(fs::create_dir_all(layout.remote_locks_path())); try!(fs::create_dir_all(layout.remote_locks_path()));
@ -70,13 +73,17 @@ impl Repository {
Self::open(path) Self::open(path)
} }
#[allow(unknown_lints,useless_let_if_seq)]
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, RepositoryError> { pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, RepositoryError> {
let layout = RepositoryLayout::new(path.as_ref().to_path_buf()); let layout = RepositoryLayout::new(path.as_ref().to_path_buf());
if !layout.remote_exists() { if !layout.remote_exists() {
return Err(RepositoryError::NoRemote) return Err(RepositoryError::NoRemote)
} }
let config = try!(Config::load(layout.config_path())); let config = try!(Config::load(layout.config_path()));
let locks = LockFolder::new(layout.remote_locks_path()); let remote_locks = LockFolder::new(layout.remote_locks_path());
try!(fs::create_dir_all(layout.local_locks_path())); // Added after v0.1.0
let local_locks = LockFolder::new(layout.local_locks_path());
let lock = try!(local_locks.lock(false));
let crypto = Arc::new(Mutex::new(try!(Crypto::open(layout.keys_path())))); let crypto = Arc::new(Mutex::new(try!(Crypto::open(layout.keys_path()))));
let (bundles, new, gone) = try!(BundleDb::open(layout.clone(), crypto.clone())); let (bundles, new, gone) = try!(BundleDb::open(layout.clone(), crypto.clone()));
let (index, mut rebuild_index) = match Index::open(layout.index_path()) { let (index, mut rebuild_index) = match Index::open(layout.index_path()) {
@ -107,28 +114,42 @@ impl Repository {
bundles: bundles, bundles: bundles,
data_bundle: None, data_bundle: None,
meta_bundle: None, meta_bundle: None,
locks: locks lock: lock,
remote_locks: remote_locks,
local_locks: local_locks
}; };
if !new.is_empty() { if !rebuild_bundle_map {
info!("Adding {} new bundles to index", new.len()); let mut save_bundle_map = false;
for bundle in ProgressIter::new("adding bundles to index", new.len(), new.into_iter()) { if !new.is_empty() {
try!(repo.add_new_remote_bundle(bundle)) info!("Adding {} new bundles to index", new.len());
try!(repo.write_mode());
for bundle in ProgressIter::new("adding bundles to index", new.len(), new.into_iter()) {
try!(repo.add_new_remote_bundle(bundle))
}
save_bundle_map = true;
}
if !gone.is_empty() {
info!("Removig {} old bundles from index", gone.len());
try!(repo.write_mode());
for bundle in gone {
try!(repo.remove_gone_remote_bundle(bundle))
}
save_bundle_map = true;
}
if save_bundle_map {
try!(repo.write_mode());
try!(repo.save_bundle_map());
} }
} }
if !gone.is_empty() {
info!("Removig {} old bundles from index", gone.len());
for bundle in gone {
try!(repo.remove_gone_remote_bundle(bundle))
}
}
try!(repo.save_bundle_map());
repo.next_meta_bundle = repo.next_free_bundle_id(); repo.next_meta_bundle = repo.next_free_bundle_id();
repo.next_data_bundle = repo.next_free_bundle_id(); repo.next_data_bundle = repo.next_free_bundle_id();
if rebuild_bundle_map { if rebuild_bundle_map {
try!(repo.write_mode());
try!(repo.rebuild_bundle_map()); try!(repo.rebuild_bundle_map());
rebuild_index = true; rebuild_index = true;
} }
if rebuild_index { if rebuild_index {
try!(repo.write_mode());
try!(repo.rebuild_index()); try!(repo.rebuild_index());
} }
repo.dirty = dirty; repo.dirty = dirty;
@ -156,11 +177,13 @@ impl Repository {
#[inline] #[inline]
pub fn register_key(&mut self, public: PublicKey, secret: SecretKey) -> Result<(), RepositoryError> { pub fn register_key(&mut self, public: PublicKey, secret: SecretKey) -> Result<(), RepositoryError> {
try!(self.write_mode());
Ok(try!(self.crypto.lock().unwrap().register_secret_key(public, secret))) Ok(try!(self.crypto.lock().unwrap().register_secret_key(public, secret)))
} }
#[inline] #[inline]
pub fn save_config(&mut self) -> Result<(), RepositoryError> { pub fn save_config(&mut self) -> Result<(), RepositoryError> {
try!(self.write_mode());
try!(self.config.save(self.layout.config_path())); try!(self.config.save(self.layout.config_path()));
Ok(()) Ok(())
} }
@ -290,9 +313,14 @@ impl Repository {
Ok(()) Ok(())
} }
#[inline]
fn write_mode(&mut self) -> Result<(), RepositoryError> {
Ok(try!(self.local_locks.upgrade(&mut self.lock)))
}
#[inline] #[inline]
fn lock(&self, exclusive: bool) -> Result<LockHandle, RepositoryError> { fn lock(&self, exclusive: bool) -> Result<LockHandle, RepositoryError> {
Ok(try!(self.locks.lock(exclusive))) Ok(try!(self.remote_locks.lock(exclusive)))
} }
#[inline] #[inline]

View File

@ -154,6 +154,7 @@ impl Repository {
} }
pub fn import_tarfile<P: AsRef<Path>>(&mut self, tarfile: P) -> Result<Backup, RepositoryError> { pub fn import_tarfile<P: AsRef<Path>>(&mut self, tarfile: P) -> Result<Backup, RepositoryError> {
try!(self.write_mode());
let _lock = try!(self.lock(false)); let _lock = try!(self.lock(false));
if self.dirty { if self.dirty {
return Err(RepositoryError::Dirty) return Err(RepositoryError::Dirty)

View File

@ -16,6 +16,7 @@ impl Repository {
pub fn vacuum(&mut self, ratio: f32, force: bool) -> Result<(), RepositoryError> { pub fn vacuum(&mut self, ratio: f32, force: bool) -> Result<(), RepositoryError> {
try!(self.flush()); try!(self.flush());
info!("Locking repository"); info!("Locking repository");
try!(self.write_mode());
let _lock = try!(self.lock(true)); let _lock = try!(self.lock(true));
if self.dirty { if self.dirty {
return Err(RepositoryError::Dirty) return Err(RepositoryError::Dirty)

View File

@ -71,6 +71,7 @@ pub enum LockLevel {
pub struct LockHandle { pub struct LockHandle {
lock: LockFile,
path: PathBuf path: PathBuf
} }
@ -146,11 +147,41 @@ impl LockFolder {
}; };
let path = self.path.join(format!("{}-{}.lock", &lockfile.hostname, lockfile.processid)); let path = self.path.join(format!("{}-{}.lock", &lockfile.hostname, lockfile.processid));
try!(lockfile.save(&path)); try!(lockfile.save(&path));
let handle = LockHandle{path: path}; let handle = LockHandle{lock: lockfile, path: path};
if self.get_lock_level().is_err() { if self.get_lock_level().is_err() {
try!(handle.release()); try!(handle.release());
return Err(LockError::Locked) return Err(LockError::Locked)
} }
Ok(handle) Ok(handle)
} }
pub fn upgrade(&self, lock: &mut LockHandle) -> Result<(), LockError> {
let lockfile = &mut lock.lock;
if lockfile.exclusive {
return Ok(())
}
let level = try!(self.get_lock_level());
if level == LockLevel::Exclusive {
return Err(LockError::Locked)
}
lockfile.exclusive = true;
let path = self.path.join(format!("{}-{}.lock", &lockfile.hostname, lockfile.processid));
try!(lockfile.save(&path));
if self.get_lock_level().is_err() {
lockfile.exclusive = false;
try!(lockfile.save(&path));
return Err(LockError::Locked)
}
Ok(())
}
pub fn downgrade(&self, lock: &mut LockHandle) -> Result<(), LockError> {
let lockfile = &mut lock.lock;
if !lockfile.exclusive {
return Ok(())
}
lockfile.exclusive = false;
let path = self.path.join(format!("{}-{}.lock", &lockfile.hostname, lockfile.processid));
lockfile.save(&path)
}
} }