This commit is contained in:
Dennis Schwerdel 2017-03-24 08:56:57 +01:00
parent 6ab28c10df
commit 828bcc6dc9
8 changed files with 183 additions and 7 deletions

View File

@ -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

View File

@ -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() {

View File

@ -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()

View File

@ -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 {

View File

@ -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>();

View File

@ -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))

156
src/util/lock.rs Normal file
View File

@ -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)
}
}

View File

@ -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::*;