zvault/src/repository/mod.rs

256 lines
9.0 KiB
Rust
Raw Normal View History

2017-03-10 11:43:32 +00:00
mod config;
mod bundle_map;
mod integrity;
mod basic_io;
2017-03-15 07:27:27 +00:00
mod info;
2017-03-15 11:32:44 +00:00
mod metadata;
2017-03-15 20:53:05 +00:00
mod backup;
2017-03-16 08:42:30 +00:00
mod error;
2017-03-20 13:03:29 +00:00
mod vacuum;
2017-03-24 08:26:55 +00:00
mod backup_file;
2017-04-03 13:18:06 +00:00
mod tarfile;
2017-03-10 11:43:32 +00:00
2017-03-21 10:28:11 +00:00
use ::prelude::*;
2017-03-10 11:43:32 +00:00
use std::mem;
use std::cmp::max;
use std::path::{PathBuf, Path};
2017-03-26 18:33:32 +00:00
use std::fs::{self, File};
2017-03-18 16:22:11 +00:00
use std::sync::{Arc, Mutex};
2017-03-22 13:10:42 +00:00
use std::os::unix::fs::symlink;
2017-03-26 18:33:32 +00:00
use std::io::Write;
2017-03-10 11:43:32 +00:00
2017-03-16 08:42:30 +00:00
pub use self::error::RepositoryError;
2017-03-10 11:43:32 +00:00
pub use self::config::Config;
2017-04-03 13:18:06 +00:00
pub use self::metadata::{Inode, FileType, FileData, InodeError};
2017-03-29 21:24:26 +00:00
pub use self::backup::{BackupError, BackupOptions, DiffType};
2017-03-24 08:26:55 +00:00
pub use self::backup_file::{Backup, BackupFileError};
2017-03-20 13:03:29 +00:00
pub use self::integrity::RepositoryIntegrityError;
2017-03-25 11:43:49 +00:00
pub use self::info::{RepositoryInfo, BundleAnalysis};
2017-03-15 07:27:27 +00:00
use self::bundle_map::BundleMap;
2017-03-10 11:43:32 +00:00
2017-04-02 16:55:53 +00:00
const REPOSITORY_README: &'static [u8] = include_bytes!("../../docs/repository_readme.md");
2017-04-03 13:18:06 +00:00
const DEFAULT_EXCLUDES: &'static [u8] = include_bytes!("../../docs/excludes.default");
2017-03-26 18:33:32 +00:00
2017-03-10 11:43:32 +00:00
pub struct Repository {
path: PathBuf,
2017-03-24 08:05:41 +00:00
backups_path: PathBuf,
2017-03-26 18:33:32 +00:00
pub excludes_path: PathBuf,
2017-03-18 16:22:11 +00:00
pub config: Config,
2017-03-10 11:43:32 +00:00
index: Index,
2017-03-18 16:22:11 +00:00
crypto: Arc<Mutex<Crypto>>,
2017-03-10 11:43:32 +00:00
bundle_map: BundleMap,
2017-04-03 12:05:16 +00:00
next_data_bundle: u32,
2017-03-10 11:43:32 +00:00
next_meta_bundle: u32,
bundles: BundleDb,
2017-04-03 12:05:16 +00:00
data_bundle: Option<BundleWriter>,
2017-03-10 11:43:32 +00:00
meta_bundle: Option<BundleWriter>,
2017-03-24 07:56:57 +00:00
chunker: Chunker,
locks: LockFolder
2017-03-10 11:43:32 +00:00
}
impl Repository {
2017-03-22 13:10:42 +00:00
pub fn create<P: AsRef<Path>, R: AsRef<Path>>(path: P, config: Config, remote: R) -> Result<Self, RepositoryError> {
2017-03-10 11:43:32 +00:00
let path = path.as_ref().to_owned();
2017-03-16 08:42:30 +00:00
try!(fs::create_dir(&path));
2017-03-26 18:33:32 +00:00
let mut excludes = try!(File::create(path.join("excludes")));
try!(excludes.write_all(DEFAULT_EXCLUDES));
2017-03-18 16:22:11 +00:00
try!(fs::create_dir(path.join("keys")));
let crypto = Arc::new(Mutex::new(try!(Crypto::open(path.join("keys")))));
2017-03-22 13:10:42 +00:00
try!(symlink(remote, path.join("remote")));
2017-04-02 16:55:53 +00:00
let mut remote_readme = try!(File::create(path.join("remote/README.md")));
try!(remote_readme.write_all(REPOSITORY_README));
2017-03-24 07:56:57 +00:00
try!(fs::create_dir_all(path.join("remote/locks")));
let locks = LockFolder::new(path.join("remote/locks"));
2017-03-10 11:43:32 +00:00
let bundles = try!(BundleDb::create(
2017-03-21 12:44:30 +00:00
path.join("remote/bundles"),
2017-03-10 11:43:32 +00:00
path.join("bundles"),
2017-03-18 16:22:11 +00:00
crypto.clone()
2017-03-16 08:42:30 +00:00
));
let index = try!(Index::create(&path.join("index")));
try!(config.save(path.join("config.yaml")));
2017-03-10 11:43:32 +00:00
let bundle_map = BundleMap::create();
2017-03-16 08:42:30 +00:00
try!(bundle_map.save(path.join("bundles.map")));
2017-03-24 08:05:41 +00:00
try!(fs::create_dir_all(&path.join("remote/backups")));
Ok(Repository {
backups_path: path.join("remote/backups"),
2017-03-26 18:33:32 +00:00
excludes_path: path.join("excludes"),
2017-03-10 11:43:32 +00:00
path: path,
chunker: config.chunker.create(),
config: config,
index: index,
bundle_map: bundle_map,
2017-04-03 12:05:16 +00:00
next_data_bundle: 1,
2017-03-10 11:43:32 +00:00
next_meta_bundle: 0,
bundles: bundles,
2017-04-03 12:05:16 +00:00
data_bundle: None,
2017-03-10 11:43:32 +00:00
meta_bundle: None,
2017-03-24 07:56:57 +00:00
crypto: crypto,
locks: locks
2017-03-10 11:43:32 +00:00
})
}
2017-03-16 08:42:30 +00:00
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, RepositoryError> {
2017-03-10 11:43:32 +00:00
let path = path.as_ref().to_owned();
2017-03-16 08:42:30 +00:00
let config = try!(Config::load(path.join("config.yaml")));
2017-03-24 07:56:57 +00:00
let locks = LockFolder::new(path.join("remote/locks"));
2017-03-18 16:22:11 +00:00
let crypto = Arc::new(Mutex::new(try!(Crypto::open(path.join("keys")))));
2017-03-21 14:38:42 +00:00
let (bundles, new, gone) = try!(BundleDb::open(
2017-03-21 12:44:30 +00:00
path.join("remote/bundles"),
2017-03-10 11:43:32 +00:00
path.join("bundles"),
2017-03-18 16:22:11 +00:00
crypto.clone()
2017-03-16 08:42:30 +00:00
));
let index = try!(Index::open(&path.join("index")));
let bundle_map = try!(BundleMap::load(path.join("bundles.map")));
2017-03-10 11:43:32 +00:00
let mut repo = Repository {
2017-03-24 08:05:41 +00:00
backups_path: path.join("remote/backups"),
2017-03-26 18:33:32 +00:00
excludes_path: path.join("excludes"),
2017-03-10 11:43:32 +00:00
path: path,
chunker: config.chunker.create(),
config: config,
index: index,
2017-03-18 16:22:11 +00:00
crypto: crypto,
2017-03-10 11:43:32 +00:00
bundle_map: bundle_map,
2017-04-03 12:05:16 +00:00
next_data_bundle: 0,
2017-03-10 11:43:32 +00:00
next_meta_bundle: 0,
bundles: bundles,
2017-04-03 12:05:16 +00:00
data_bundle: None,
2017-03-10 11:43:32 +00:00
meta_bundle: None,
2017-03-24 07:56:57 +00:00
locks: locks
2017-03-10 11:43:32 +00:00
};
2017-03-22 11:27:17 +00:00
for bundle in new {
try!(repo.add_new_remote_bundle(bundle))
}
for bundle in gone {
try!(repo.remove_gone_remote_bundle(bundle))
}
2017-03-22 13:10:42 +00:00
try!(repo.save_bundle_map());
2017-03-10 11:43:32 +00:00
repo.next_meta_bundle = repo.next_free_bundle_id();
2017-04-03 12:05:16 +00:00
repo.next_data_bundle = repo.next_free_bundle_id();
2017-03-10 11:43:32 +00:00
Ok(repo)
}
2017-03-22 16:28:45 +00:00
pub fn import<P: AsRef<Path>, R: AsRef<Path>>(path: P, remote: R, key_files: Vec<String>) -> Result<Self, RepositoryError> {
2017-03-22 13:42:27 +00:00
let path = path.as_ref();
2017-03-22 16:28:45 +00:00
let mut repo = try!(Repository::create(path, Config::default(), remote));
for file in key_files {
try!(repo.crypto.lock().unwrap().register_keyfile(file));
}
repo = try!(Repository::open(path));
2017-04-07 09:05:28 +00:00
let mut backups: Vec<(String, Backup)> = try!(repo.get_backups()).into_iter().collect();
backups.sort_by_key(|&(_, ref b)| b.date);
if let Some((name, backup)) = backups.pop() {
info!("Taking configuration from the last backup '{}'", name);
2017-03-22 13:42:27 +00:00
repo.config = backup.config;
try!(repo.save_config())
2017-04-07 09:05:28 +00:00
} else {
warn!("No backup found in the repository to take configuration from, please set the configuration manually.");
2017-03-22 13:42:27 +00:00
}
Ok(repo)
}
2017-03-18 16:22:11 +00:00
#[inline]
pub fn register_key(&mut self, public: PublicKey, secret: SecretKey) -> Result<(), RepositoryError> {
Ok(try!(self.crypto.lock().unwrap().register_secret_key(public, secret)))
}
pub fn save_config(&mut self) -> Result<(), RepositoryError> {
try!(self.config.save(self.path.join("config.yaml")));
Ok(())
}
#[inline]
pub fn set_encryption(&mut self, public: Option<&PublicKey>) {
if let Some(key) = public {
let mut key_bytes = Vec::new();
key_bytes.extend_from_slice(&key[..]);
self.config.encryption = Some((EncryptionMethod::Sodium, key_bytes.into()))
} else {
self.config.encryption = None
}
}
2017-03-10 11:43:32 +00:00
#[inline]
2017-03-16 08:42:30 +00:00
fn save_bundle_map(&self) -> Result<(), RepositoryError> {
try!(self.bundle_map.save(self.path.join("bundles.map")));
Ok(())
2017-03-10 11:43:32 +00:00
}
#[inline]
fn next_free_bundle_id(&self) -> u32 {
2017-04-03 12:05:16 +00:00
let mut id = max(self.next_data_bundle, self.next_meta_bundle) + 1;
2017-03-10 11:43:32 +00:00
while self.bundle_map.get(id).is_some() {
id += 1;
}
id
}
2017-03-16 08:42:30 +00:00
pub fn flush(&mut self) -> Result<(), RepositoryError> {
2017-04-03 12:05:16 +00:00
if self.data_bundle.is_some() {
2017-03-10 11:43:32 +00:00
let mut finished = None;
2017-04-03 12:05:16 +00:00
mem::swap(&mut self.data_bundle, &mut finished);
2017-03-10 11:43:32 +00:00
{
2017-03-16 08:42:30 +00:00
let bundle = try!(self.bundles.add_bundle(finished.unwrap()));
2017-04-03 12:05:16 +00:00
self.bundle_map.set(self.next_data_bundle, bundle.id.clone());
2017-03-10 11:43:32 +00:00
}
2017-04-03 12:05:16 +00:00
self.next_data_bundle = self.next_free_bundle_id()
2017-03-10 11:43:32 +00:00
}
if self.meta_bundle.is_some() {
let mut finished = None;
mem::swap(&mut self.meta_bundle, &mut finished);
{
2017-03-16 08:42:30 +00:00
let bundle = try!(self.bundles.add_bundle(finished.unwrap()));
2017-03-22 11:27:17 +00:00
self.bundle_map.set(self.next_meta_bundle, bundle.id.clone());
2017-03-10 11:43:32 +00:00
}
self.next_meta_bundle = self.next_free_bundle_id()
}
2017-03-16 08:42:30 +00:00
try!(self.save_bundle_map());
2017-03-22 13:10:42 +00:00
try!(self.bundles.save_cache());
2017-03-10 11:43:32 +00:00
Ok(())
}
2017-03-22 11:27:17 +00:00
fn add_new_remote_bundle(&mut self, bundle: BundleInfo) -> Result<(), RepositoryError> {
info!("Adding new bundle to index: {}", bundle.id);
let bundle_id = match bundle.mode {
2017-04-03 12:05:16 +00:00
BundleMode::Data => self.next_data_bundle,
2017-03-22 11:27:17 +00:00
BundleMode::Meta => self.next_meta_bundle
};
let chunks = try!(self.bundles.get_chunk_list(&bundle.id));
self.bundle_map.set(bundle_id, bundle.id);
if self.next_meta_bundle == bundle_id {
self.next_meta_bundle = self.next_free_bundle_id()
}
2017-04-03 12:05:16 +00:00
if self.next_data_bundle == bundle_id {
self.next_data_bundle = self.next_free_bundle_id()
2017-03-22 11:27:17 +00:00
}
for (i, (hash, _len)) in chunks.into_inner().into_iter().enumerate() {
try!(self.index.set(&hash, &Location{bundle: bundle_id as u32, chunk: i as u32}));
}
Ok(())
}
fn remove_gone_remote_bundle(&mut self, bundle: BundleInfo) -> Result<(), RepositoryError> {
if let Some(id) = self.bundle_map.find(&bundle.id) {
info!("Removing bundle from index: {}", bundle.id);
try!(self.bundles.delete_local_bundle(&bundle.id));
try!(self.index.filter(|_key, data| data.bundle != id));
2017-03-22 11:27:17 +00:00
self.bundle_map.remove(id);
}
Ok(())
}
2017-03-24 07:56:57 +00:00
fn lock(&self, exclusive: bool) -> Result<LockHandle, RepositoryError> {
Ok(try!(self.locks.lock(exclusive)))
}
2017-03-10 11:43:32 +00:00
}
2017-04-03 13:18:06 +00:00
2017-03-10 11:43:32 +00:00
impl Drop for Repository {
fn drop(&mut self) {
self.flush().expect("Failed to write last bundles")
}
}