mirror of https://github.com/dswd/zvault
Locking
This commit is contained in:
parent
6ab28c10df
commit
828bcc6dc9
|
@ -99,15 +99,11 @@ Recommended: Brotli/2-7
|
|||
|
||||
### Core functionality
|
||||
- Keep backup files also remotely and sync them
|
||||
- Lock during backup and vacuum
|
||||
- Options for creating backups (same filesystem, exclude/include patterns)
|
||||
- Recompress & combine bundles
|
||||
- Allow to use tar files for backup and restore (--tar, http://alexcrichton.com/tar-rs/tar/index.html)
|
||||
- File attributes
|
||||
- xattrs https://crates.io/crates/xattr
|
||||
- gid/uid
|
||||
- http://mahkoh.github.io/posix/doc/posix/unistd/fn.lchown.html
|
||||
- http://mahkoh.github.io/posix/doc/posix/sys/time/fn.utimes.html
|
||||
|
||||
### CLI functionality
|
||||
- list --tree
|
||||
|
|
|
@ -286,6 +286,7 @@ impl Repository {
|
|||
}
|
||||
|
||||
pub fn restore_inode_tree<P: AsRef<Path>>(&mut self, inode: Inode, path: P) -> Result<(), RepositoryError> {
|
||||
let _lock = try!(self.lock(false));
|
||||
let mut queue = VecDeque::new();
|
||||
queue.push_back((path.as_ref().to_owned(), inode));
|
||||
while let Some((path, inode)) = queue.pop_front() {
|
||||
|
@ -303,6 +304,7 @@ impl Repository {
|
|||
|
||||
#[inline]
|
||||
pub fn restore_backup<P: AsRef<Path>>(&mut self, backup: &Backup, path: P) -> Result<(), RepositoryError> {
|
||||
let _lock = try!(self.lock(false));
|
||||
let inode = try!(self.get_inode(&backup.root));
|
||||
self.restore_inode_tree(inode, path)
|
||||
}
|
||||
|
@ -356,6 +358,7 @@ impl Repository {
|
|||
|
||||
#[allow(dead_code)]
|
||||
pub fn create_backup_recursively<P: AsRef<Path>>(&mut self, path: P, reference: Option<&Backup>) -> Result<Backup, RepositoryError> {
|
||||
let _lock = try!(self.lock(false));
|
||||
let reference_inode = reference.and_then(|b| self.get_inode(&b.root).ok());
|
||||
let mut backup = Backup::default();
|
||||
backup.config = self.config.clone();
|
||||
|
@ -383,6 +386,7 @@ impl Repository {
|
|||
}
|
||||
|
||||
pub fn remove_backup_path<P: AsRef<Path>>(&mut self, backup: &mut Backup, path: P) -> Result<(), RepositoryError> {
|
||||
let _lock = try!(self.lock(false));
|
||||
let mut inodes = try!(self.get_backup_path(backup, path));
|
||||
let to_remove = inodes.pop().unwrap();
|
||||
let mut remove_from = match inodes.pop() {
|
||||
|
|
|
@ -79,6 +79,12 @@ quick_error!{
|
|||
description("Failed to create a backup")
|
||||
display("Repository error: failed to create backup\n\tcaused by: {}", err)
|
||||
}
|
||||
Lock(err: LockError) {
|
||||
from()
|
||||
cause(err)
|
||||
description("Failed to obtain lock")
|
||||
display("Repository error: failed to obtain lock\n\tcaused by: {}", err)
|
||||
}
|
||||
|
||||
Io(err: io::Error) {
|
||||
from()
|
||||
|
|
|
@ -37,7 +37,8 @@ pub struct Repository {
|
|||
bundles: BundleDb,
|
||||
content_bundle: Option<BundleWriter>,
|
||||
meta_bundle: Option<BundleWriter>,
|
||||
chunker: Chunker
|
||||
chunker: Chunker,
|
||||
locks: LockFolder
|
||||
}
|
||||
|
||||
|
||||
|
@ -48,6 +49,8 @@ impl Repository {
|
|||
try!(fs::create_dir(path.join("keys")));
|
||||
let crypto = Arc::new(Mutex::new(try!(Crypto::open(path.join("keys")))));
|
||||
try!(symlink(remote, path.join("remote")));
|
||||
try!(fs::create_dir_all(path.join("remote/locks")));
|
||||
let locks = LockFolder::new(path.join("remote/locks"));
|
||||
let bundles = try!(BundleDb::create(
|
||||
path.join("remote/bundles"),
|
||||
path.join("bundles"),
|
||||
|
@ -69,13 +72,15 @@ impl Repository {
|
|||
bundles: bundles,
|
||||
content_bundle: None,
|
||||
meta_bundle: None,
|
||||
crypto: crypto
|
||||
crypto: crypto,
|
||||
locks: locks
|
||||
})
|
||||
}
|
||||
|
||||
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, RepositoryError> {
|
||||
let path = path.as_ref().to_owned();
|
||||
let config = try!(Config::load(path.join("config.yaml")));
|
||||
let locks = LockFolder::new(path.join("remote/locks"));
|
||||
let crypto = Arc::new(Mutex::new(try!(Crypto::open(path.join("keys")))));
|
||||
let (bundles, new, gone) = try!(BundleDb::open(
|
||||
path.join("remote/bundles"),
|
||||
|
@ -96,6 +101,7 @@ impl Repository {
|
|||
bundles: bundles,
|
||||
content_bundle: None,
|
||||
meta_bundle: None,
|
||||
locks: locks
|
||||
};
|
||||
for bundle in new {
|
||||
try!(repo.add_new_remote_bundle(bundle))
|
||||
|
@ -212,6 +218,10 @@ impl Repository {
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn lock(&self, exclusive: bool) -> Result<LockHandle, RepositoryError> {
|
||||
Ok(try!(self.locks.lock(exclusive)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Repository {
|
||||
|
|
|
@ -92,6 +92,8 @@ impl Repository {
|
|||
|
||||
pub fn vacuum(&mut self, ratio: f32, force: bool) -> Result<(), RepositoryError> {
|
||||
try!(self.flush());
|
||||
info!("Locking repository");
|
||||
let _lock = try!(self.lock(true));
|
||||
info!("Analyzing chunk usage");
|
||||
let usage = try!(self.analyze_usage());
|
||||
let total = usage.values().map(|b| b.total_size).sum::<usize>();
|
||||
|
|
|
@ -8,7 +8,7 @@ mod linux {
|
|||
|
||||
pub fn chown<P: AsRef<Path>>(path: P, uid: libc::uid_t, gid: libc::gid_t) -> Result<(), io::Error> {
|
||||
let path = CString::new(path.as_ref().to_path_buf().into_os_string().into_vec()).unwrap();
|
||||
let result = unsafe { libc::chown((&path).as_ptr(), uid, gid) };
|
||||
let result = unsafe { libc::lchown((&path).as_ptr(), uid, gid) };
|
||||
match result {
|
||||
0 => Ok(()),
|
||||
err => Err(io::Error::from_raw_os_error(err))
|
||||
|
|
|
@ -0,0 +1,156 @@
|
|||
use ::prelude::*;
|
||||
|
||||
use serde_yaml;
|
||||
use chrono::prelude::*;
|
||||
use libc;
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::io;
|
||||
use std::fs::{self, File};
|
||||
|
||||
|
||||
quick_error!{
|
||||
#[derive(Debug)]
|
||||
pub enum LockError {
|
||||
Io(err: io::Error) {
|
||||
from()
|
||||
cause(err)
|
||||
description("IO error")
|
||||
display("Lock error: IO error\n\tcaused by: {}", err)
|
||||
}
|
||||
Yaml(err: serde_yaml::Error) {
|
||||
from()
|
||||
cause(err)
|
||||
description("Yaml format error")
|
||||
display("Lock error: yaml format error\n\tcaused by: {}", err)
|
||||
}
|
||||
InvalidLockState(reason: &'static str) {
|
||||
description("Invalid lock state")
|
||||
display("Lock error: invalid lock state: {}", reason)
|
||||
}
|
||||
Locked {
|
||||
description("Locked")
|
||||
display("Lock error: locked")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone, Default, Eq, PartialEq)]
|
||||
pub struct LockFile {
|
||||
pub hostname: String,
|
||||
pub processid: usize,
|
||||
pub date: i64,
|
||||
pub exclusive: bool
|
||||
}
|
||||
serde_impl!(LockFile(String) {
|
||||
hostname: String => "hostname",
|
||||
processid: usize => "processid",
|
||||
date: i64 => "date",
|
||||
exclusive: bool => "exclusive"
|
||||
});
|
||||
|
||||
impl LockFile {
|
||||
pub fn load<P: AsRef<Path>>(path: P) -> Result<Self, LockError> {
|
||||
let f = try!(File::open(path));
|
||||
Ok(try!(serde_yaml::from_reader(f)))
|
||||
}
|
||||
|
||||
pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<(), LockError> {
|
||||
let mut f = try!(File::create(path));
|
||||
Ok(try!(serde_yaml::to_writer(&mut f, &self)))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
|
||||
pub enum LockLevel {
|
||||
Free,
|
||||
Shared,
|
||||
Exclusive
|
||||
}
|
||||
|
||||
|
||||
pub struct LockHandle {
|
||||
path: PathBuf
|
||||
}
|
||||
|
||||
impl LockHandle {
|
||||
pub fn release(&self) -> Result<(), LockError> {
|
||||
if self.path.exists() {
|
||||
try!(fs::remove_file(&self.path))
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn refresh(&self) -> Result<(), LockError> {
|
||||
let mut file = try!(LockFile::load(&self.path));
|
||||
file.date = UTC::now().timestamp();
|
||||
file.save(&self.path)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for LockHandle {
|
||||
fn drop(&mut self) {
|
||||
self.release().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
pub struct LockFolder {
|
||||
path: PathBuf
|
||||
}
|
||||
|
||||
impl LockFolder {
|
||||
pub fn new<P: AsRef<Path>>(path: P) -> Self {
|
||||
LockFolder { path: path.as_ref().to_path_buf() }
|
||||
}
|
||||
|
||||
fn get_locks(&self) -> Result<Vec<LockFile>, LockError> {
|
||||
let mut locks = vec![];
|
||||
for entry in try!(fs::read_dir(&self.path)) {
|
||||
let entry = try!(entry);
|
||||
locks.push(try!(LockFile::load(entry.path())));
|
||||
}
|
||||
Ok(locks)
|
||||
}
|
||||
|
||||
pub fn get_lock_level(&self) -> Result<LockLevel, LockError> {
|
||||
let mut level = LockLevel::Free;
|
||||
for lock in try!(self.get_locks()) {
|
||||
if lock.exclusive {
|
||||
if level == LockLevel::Exclusive {
|
||||
return Err(LockError::InvalidLockState("multiple exclusive locks"))
|
||||
} else {
|
||||
level = LockLevel::Exclusive
|
||||
}
|
||||
} else if level == LockLevel::Exclusive {
|
||||
return Err(LockError::InvalidLockState("exclusive lock and shared locks"))
|
||||
} else {
|
||||
level = LockLevel::Shared
|
||||
}
|
||||
}
|
||||
Ok(level)
|
||||
}
|
||||
|
||||
pub fn lock(&self, exclusive: bool) -> Result<LockHandle, LockError> {
|
||||
let level = try!(self.get_lock_level());
|
||||
if level == LockLevel::Exclusive || level == LockLevel::Shared && exclusive {
|
||||
return Err(LockError::Locked)
|
||||
}
|
||||
let lockfile = LockFile {
|
||||
hostname: get_hostname().unwrap(),
|
||||
processid: unsafe { libc::getpid() } as usize,
|
||||
date: UTC::now().timestamp(),
|
||||
exclusive: exclusive
|
||||
};
|
||||
let path = self.path.join(format!("{}-{}.lock", &lockfile.hostname, lockfile.processid));
|
||||
try!(lockfile.save(&path));
|
||||
let handle = LockHandle{path: path};
|
||||
if self.get_lock_level().is_err() {
|
||||
try!(handle.release());
|
||||
return Err(LockError::Locked)
|
||||
}
|
||||
Ok(handle)
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ mod hex;
|
|||
mod cli;
|
||||
mod hostname;
|
||||
mod fs;
|
||||
mod lock;
|
||||
pub mod msgpack;
|
||||
|
||||
pub use self::fs::*;
|
||||
|
@ -21,3 +22,4 @@ pub use self::bitmap::*;
|
|||
pub use self::hex::*;
|
||||
pub use self::cli::*;
|
||||
pub use self::hostname::*;
|
||||
pub use self::lock::*;
|
||||
|
|
Loading…
Reference in New Issue