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
|
### Core functionality
|
||||||
- Keep backup files also remotely and sync them
|
- Keep backup files also remotely and sync them
|
||||||
- Lock during backup and vacuum
|
|
||||||
- Options for creating backups (same filesystem, exclude/include patterns)
|
- Options for creating backups (same filesystem, exclude/include patterns)
|
||||||
- Recompress & combine bundles
|
- Recompress & combine bundles
|
||||||
- Allow to use tar files for backup and restore (--tar, http://alexcrichton.com/tar-rs/tar/index.html)
|
- Allow to use tar files for backup and restore (--tar, http://alexcrichton.com/tar-rs/tar/index.html)
|
||||||
- File attributes
|
- File attributes
|
||||||
- xattrs https://crates.io/crates/xattr
|
- 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
|
### CLI functionality
|
||||||
- list --tree
|
- list --tree
|
||||||
|
|
|
@ -286,6 +286,7 @@ impl Repository {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn restore_inode_tree<P: AsRef<Path>>(&mut self, inode: Inode, path: P) -> Result<(), RepositoryError> {
|
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();
|
let mut queue = VecDeque::new();
|
||||||
queue.push_back((path.as_ref().to_owned(), inode));
|
queue.push_back((path.as_ref().to_owned(), inode));
|
||||||
while let Some((path, inode)) = queue.pop_front() {
|
while let Some((path, inode)) = queue.pop_front() {
|
||||||
|
@ -303,6 +304,7 @@ impl Repository {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn restore_backup<P: AsRef<Path>>(&mut self, backup: &Backup, path: P) -> Result<(), RepositoryError> {
|
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));
|
let inode = try!(self.get_inode(&backup.root));
|
||||||
self.restore_inode_tree(inode, path)
|
self.restore_inode_tree(inode, path)
|
||||||
}
|
}
|
||||||
|
@ -356,6 +358,7 @@ impl Repository {
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn create_backup_recursively<P: AsRef<Path>>(&mut self, path: P, reference: Option<&Backup>) -> Result<Backup, RepositoryError> {
|
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 reference_inode = reference.and_then(|b| self.get_inode(&b.root).ok());
|
||||||
let mut backup = Backup::default();
|
let mut backup = Backup::default();
|
||||||
backup.config = self.config.clone();
|
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> {
|
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 mut inodes = try!(self.get_backup_path(backup, path));
|
||||||
let to_remove = inodes.pop().unwrap();
|
let to_remove = inodes.pop().unwrap();
|
||||||
let mut remove_from = match inodes.pop() {
|
let mut remove_from = match inodes.pop() {
|
||||||
|
|
|
@ -79,6 +79,12 @@ quick_error!{
|
||||||
description("Failed to create a backup")
|
description("Failed to create a backup")
|
||||||
display("Repository error: failed to create backup\n\tcaused by: {}", err)
|
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) {
|
Io(err: io::Error) {
|
||||||
from()
|
from()
|
||||||
|
|
|
@ -37,7 +37,8 @@ pub struct Repository {
|
||||||
bundles: BundleDb,
|
bundles: BundleDb,
|
||||||
content_bundle: Option<BundleWriter>,
|
content_bundle: Option<BundleWriter>,
|
||||||
meta_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")));
|
try!(fs::create_dir(path.join("keys")));
|
||||||
let crypto = Arc::new(Mutex::new(try!(Crypto::open(path.join("keys")))));
|
let crypto = Arc::new(Mutex::new(try!(Crypto::open(path.join("keys")))));
|
||||||
try!(symlink(remote, path.join("remote")));
|
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(
|
let bundles = try!(BundleDb::create(
|
||||||
path.join("remote/bundles"),
|
path.join("remote/bundles"),
|
||||||
path.join("bundles"),
|
path.join("bundles"),
|
||||||
|
@ -69,13 +72,15 @@ impl Repository {
|
||||||
bundles: bundles,
|
bundles: bundles,
|
||||||
content_bundle: None,
|
content_bundle: None,
|
||||||
meta_bundle: None,
|
meta_bundle: None,
|
||||||
crypto: crypto
|
crypto: crypto,
|
||||||
|
locks: locks
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, RepositoryError> {
|
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, RepositoryError> {
|
||||||
let path = path.as_ref().to_owned();
|
let path = path.as_ref().to_owned();
|
||||||
let config = try!(Config::load(path.join("config.yaml")));
|
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 crypto = Arc::new(Mutex::new(try!(Crypto::open(path.join("keys")))));
|
||||||
let (bundles, new, gone) = try!(BundleDb::open(
|
let (bundles, new, gone) = try!(BundleDb::open(
|
||||||
path.join("remote/bundles"),
|
path.join("remote/bundles"),
|
||||||
|
@ -96,6 +101,7 @@ impl Repository {
|
||||||
bundles: bundles,
|
bundles: bundles,
|
||||||
content_bundle: None,
|
content_bundle: None,
|
||||||
meta_bundle: None,
|
meta_bundle: None,
|
||||||
|
locks: locks
|
||||||
};
|
};
|
||||||
for bundle in new {
|
for bundle in new {
|
||||||
try!(repo.add_new_remote_bundle(bundle))
|
try!(repo.add_new_remote_bundle(bundle))
|
||||||
|
@ -212,6 +218,10 @@ impl Repository {
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn lock(&self, exclusive: bool) -> Result<LockHandle, RepositoryError> {
|
||||||
|
Ok(try!(self.locks.lock(exclusive)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Repository {
|
impl Drop for Repository {
|
||||||
|
|
|
@ -92,6 +92,8 @@ 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");
|
||||||
|
let _lock = try!(self.lock(true));
|
||||||
info!("Analyzing chunk usage");
|
info!("Analyzing chunk usage");
|
||||||
let usage = try!(self.analyze_usage());
|
let usage = try!(self.analyze_usage());
|
||||||
let total = usage.values().map(|b| b.total_size).sum::<usize>();
|
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> {
|
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 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 {
|
match result {
|
||||||
0 => Ok(()),
|
0 => Ok(()),
|
||||||
err => Err(io::Error::from_raw_os_error(err))
|
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 cli;
|
||||||
mod hostname;
|
mod hostname;
|
||||||
mod fs;
|
mod fs;
|
||||||
|
mod lock;
|
||||||
pub mod msgpack;
|
pub mod msgpack;
|
||||||
|
|
||||||
pub use self::fs::*;
|
pub use self::fs::*;
|
||||||
|
@ -21,3 +22,4 @@ pub use self::bitmap::*;
|
||||||
pub use self::hex::*;
|
pub use self::hex::*;
|
||||||
pub use self::cli::*;
|
pub use self::cli::*;
|
||||||
pub use self::hostname::*;
|
pub use self::hostname::*;
|
||||||
|
pub use self::lock::*;
|
||||||
|
|
Loading…
Reference in New Issue