mirror of https://github.com/dswd/zvault
Refactored backups, vacuum, etc.
This commit is contained in:
parent
4287896945
commit
f3f6a3cf49
|
@ -14,7 +14,7 @@ quick_error!{
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[allow(unknown_lints,large_enum_variant)]
|
#[allow(unknown_lints,large_enum_variant)]
|
||||||
pub enum BackupError {
|
pub enum BackupError {
|
||||||
FailedPaths(backup: Backup, failed: Vec<PathBuf>) {
|
FailedPaths(backup: BackupFile, failed: Vec<PathBuf>) {
|
||||||
description(tr!("Some paths could not be backed up"))
|
description(tr!("Some paths could not be backed up"))
|
||||||
display("{}", tr_format!("Backup error: some paths could not be backed up"))
|
display("{}", tr_format!("Backup error: some paths could not be backed up"))
|
||||||
}
|
}
|
||||||
|
@ -39,9 +39,9 @@ pub enum DiffType {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl Repository {
|
impl BackupRepository {
|
||||||
pub fn get_all_backups(&self) -> Result<HashMap<String, Backup>, RepositoryError> {
|
pub fn get_all_backups(&self) -> Result<HashMap<String, BackupFile>, RepositoryError> {
|
||||||
Ok(try!(Backup::get_all_from(
|
Ok(try!(BackupFile::get_all_from(
|
||||||
&self.crypto,
|
&self.crypto,
|
||||||
self.layout.backups_path()
|
self.layout.backups_path()
|
||||||
)))
|
)))
|
||||||
|
@ -50,8 +50,8 @@ impl Repository {
|
||||||
pub fn get_backups<P: AsRef<Path>>(
|
pub fn get_backups<P: AsRef<Path>>(
|
||||||
&self,
|
&self,
|
||||||
path: P,
|
path: P,
|
||||||
) -> Result<HashMap<String, Backup>, RepositoryError> {
|
) -> Result<HashMap<String, BackupFile>, RepositoryError> {
|
||||||
Ok(try!(Backup::get_all_from(
|
Ok(try!(BackupFile::get_all_from(
|
||||||
&self.crypto,
|
&self.crypto,
|
||||||
self.layout.backups_path().join(path)
|
self.layout.backups_path().join(path)
|
||||||
)))
|
)))
|
||||||
|
@ -62,27 +62,27 @@ impl Repository {
|
||||||
self.layout.backup_path(name).exists()
|
self.layout.backup_path(name).exists()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_backup(&self, name: &str) -> Result<Backup, RepositoryError> {
|
pub fn get_backup(&self, name: &str) -> Result<BackupFile, RepositoryError> {
|
||||||
Ok(try!(Backup::read_from(
|
Ok(try!(BackupFile::read_from(
|
||||||
&self.crypto,
|
&self.crypto,
|
||||||
self.layout.backup_path(name)
|
self.layout.backup_path(name)
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save_backup(&mut self, backup: &Backup, name: &str) -> Result<(), RepositoryError> {
|
pub fn save_backup(&mut self, backup: &BackupFile, name: &str) -> Result<(), RepositoryError> {
|
||||||
try!(self.write_mode());
|
try!(self.repo.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()));
|
||||||
try!(backup.save_to(
|
try!(backup.save_to(
|
||||||
&self.crypto,
|
&self.crypto,
|
||||||
self.config.encryption.clone(),
|
self.get_config().encryption.clone(),
|
||||||
path
|
path
|
||||||
));
|
));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_backup(&mut self, name: &str) -> Result<(), RepositoryError> {
|
pub fn delete_backup(&mut self, name: &str) -> Result<(), RepositoryError> {
|
||||||
try!(self.write_mode());
|
try!(self.repo.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 {
|
||||||
|
@ -104,7 +104,7 @@ impl Repository {
|
||||||
yearly: usize,
|
yearly: usize,
|
||||||
force: bool,
|
force: bool,
|
||||||
) -> Result<(), RepositoryError> {
|
) -> Result<(), RepositoryError> {
|
||||||
try!(self.write_mode());
|
try!(self.repo.write_mode());
|
||||||
let mut backups = Vec::new();
|
let mut backups = Vec::new();
|
||||||
let backup_map = match self.get_all_backups() {
|
let backup_map = match self.get_all_backups() {
|
||||||
Ok(backup_map) => backup_map,
|
Ok(backup_map) => backup_map,
|
||||||
|
@ -125,7 +125,7 @@ impl Repository {
|
||||||
let mut keep = Bitmap::new(backups.len());
|
let mut keep = Bitmap::new(backups.len());
|
||||||
|
|
||||||
fn mark_needed<K: Eq, F: Fn(&DateTime<Local>) -> K>(
|
fn mark_needed<K: Eq, F: Fn(&DateTime<Local>) -> K>(
|
||||||
backups: &[(String, DateTime<Local>, Backup)],
|
backups: &[(String, DateTime<Local>, BackupFile)],
|
||||||
keep: &mut Bitmap,
|
keep: &mut Bitmap,
|
||||||
max: usize,
|
max: usize,
|
||||||
keyfn: F,
|
keyfn: F,
|
||||||
|
@ -183,11 +183,11 @@ impl Repository {
|
||||||
|
|
||||||
pub fn restore_inode_tree<P: AsRef<Path>>(
|
pub fn restore_inode_tree<P: AsRef<Path>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
backup: &Backup,
|
backup: &BackupFile,
|
||||||
inode: Inode,
|
inode: Inode,
|
||||||
path: P,
|
path: P,
|
||||||
) -> Result<(), RepositoryError> {
|
) -> Result<(), RepositoryError> {
|
||||||
let _lock = try!(self.lock(false));
|
let _lock = try!(self.repo.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));
|
||||||
let cache = users::UsersCache::new();
|
let cache = users::UsersCache::new();
|
||||||
|
@ -204,7 +204,7 @@ impl Repository {
|
||||||
inode.group = group.gid();
|
inode.group = group.gid();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try!(self.save_inode_at(&inode, &path));
|
try!(self.repo.save_inode_at(&inode, &path));
|
||||||
}
|
}
|
||||||
if inode.file_type == FileType::Directory {
|
if inode.file_type == FileType::Directory {
|
||||||
let path = if is_root {
|
let path = if is_root {
|
||||||
|
@ -227,11 +227,11 @@ impl Repository {
|
||||||
path: P,
|
path: P,
|
||||||
reference: Option<&Inode>,
|
reference: Option<&Inode>,
|
||||||
options: &BackupOptions,
|
options: &BackupOptions,
|
||||||
backup: &mut Backup,
|
backup: &mut BackupFile,
|
||||||
failed_paths: &mut Vec<PathBuf>,
|
failed_paths: &mut Vec<PathBuf>,
|
||||||
) -> Result<Inode, RepositoryError> {
|
) -> Result<Inode, RepositoryError> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
let mut inode = try!(self.create_inode(path, reference));
|
let mut inode = try!(self.repo.create_inode(path, reference));
|
||||||
if !backup.user_names.contains_key(&inode.user) {
|
if !backup.user_names.contains_key(&inode.user) {
|
||||||
if let Some(user) = users::get_user_by_uid(inode.user) {
|
if let Some(user) = users::get_user_by_uid(inode.user) {
|
||||||
backup.user_names.insert(
|
backup.user_names.insert(
|
||||||
|
@ -296,7 +296,7 @@ impl Repository {
|
||||||
}
|
}
|
||||||
Err(err) => return Err(err),
|
Err(err) => return Err(err),
|
||||||
};
|
};
|
||||||
let chunks = try!(self.put_inode(&child_inode));
|
let chunks = try!(self.repo.put_inode(&child_inode));
|
||||||
inode.cum_size += child_inode.cum_size;
|
inode.cum_size += child_inode.cum_size;
|
||||||
for &(_, len) in chunks.iter() {
|
for &(_, len) in chunks.iter() {
|
||||||
meta_size += u64::from(len);
|
meta_size += u64::from(len);
|
||||||
|
@ -328,18 +328,18 @@ impl Repository {
|
||||||
pub fn create_backup_recursively<P: AsRef<Path>>(
|
pub fn create_backup_recursively<P: AsRef<Path>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
path: P,
|
path: P,
|
||||||
reference: Option<&Backup>,
|
reference: Option<&BackupFile>,
|
||||||
options: &BackupOptions,
|
options: &BackupOptions,
|
||||||
) -> Result<Backup, RepositoryError> {
|
) -> Result<BackupFile, RepositoryError> {
|
||||||
try!(self.write_mode());
|
try!(self.repo.write_mode());
|
||||||
let _lock = try!(self.lock(false));
|
let _lock = try!(self.repo.lock(false));
|
||||||
if self.dirty {
|
if self.repo.is_dirty() {
|
||||||
return Err(RepositoryError::Dirty);
|
return Err(RepositoryError::Dirty);
|
||||||
}
|
}
|
||||||
try!(self.set_dirty());
|
try!(self.repo.set_dirty());
|
||||||
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 = BackupFile::default();
|
||||||
backup.config = self.config.clone();
|
backup.config = self.get_config().clone();
|
||||||
backup.host = get_hostname().unwrap_or_else(|_| "".to_string());
|
backup.host = get_hostname().unwrap_or_else(|_| "".to_string());
|
||||||
backup.path = path.as_ref().to_string_lossy().to_string();
|
backup.path = path.as_ref().to_string_lossy().to_string();
|
||||||
let info_before = self.info();
|
let info_before = self.info();
|
||||||
|
@ -352,8 +352,8 @@ impl Repository {
|
||||||
&mut backup,
|
&mut backup,
|
||||||
&mut failed_paths
|
&mut failed_paths
|
||||||
));
|
));
|
||||||
backup.root = try!(self.put_inode(&root_inode));
|
backup.root = try!(self.repo.put_inode(&root_inode));
|
||||||
try!(self.flush());
|
try!(self.repo.flush());
|
||||||
let elapsed = Local::now().signed_duration_since(start);
|
let elapsed = Local::now().signed_duration_since(start);
|
||||||
backup.timestamp = start.timestamp();
|
backup.timestamp = start.timestamp();
|
||||||
backup.total_data_size = root_inode.cum_size;
|
backup.total_data_size = root_inode.cum_size;
|
||||||
|
@ -369,7 +369,7 @@ impl Repository {
|
||||||
backup.bundle_count = info_after.bundle_count - info_before.bundle_count;
|
backup.bundle_count = info_after.bundle_count - info_before.bundle_count;
|
||||||
backup.chunk_count = info_after.chunk_count - info_before.chunk_count;
|
backup.chunk_count = info_after.chunk_count - info_before.chunk_count;
|
||||||
backup.avg_chunk_size = backup.deduplicated_data_size as f32 / backup.chunk_count as f32;
|
backup.avg_chunk_size = backup.deduplicated_data_size as f32 / backup.chunk_count as f32;
|
||||||
self.dirty = false;
|
self.repo.set_clean();
|
||||||
if failed_paths.is_empty() {
|
if failed_paths.is_empty() {
|
||||||
Ok(backup)
|
Ok(backup)
|
||||||
} else {
|
} else {
|
||||||
|
@ -379,11 +379,11 @@ impl Repository {
|
||||||
|
|
||||||
pub fn remove_backup_path<P: AsRef<Path>>(
|
pub fn remove_backup_path<P: AsRef<Path>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
backup: &mut Backup,
|
backup: &mut BackupFile,
|
||||||
path: P,
|
path: P,
|
||||||
) -> Result<(), RepositoryError> {
|
) -> Result<(), RepositoryError> {
|
||||||
try!(self.write_mode());
|
try!(self.repo.write_mode());
|
||||||
let _lock = try!(self.lock(false));
|
let _lock = try!(self.repo.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() {
|
||||||
|
@ -393,14 +393,14 @@ impl Repository {
|
||||||
remove_from.children.as_mut().unwrap().remove(
|
remove_from.children.as_mut().unwrap().remove(
|
||||||
&to_remove.name
|
&to_remove.name
|
||||||
);
|
);
|
||||||
let mut last_inode_chunks = try!(self.put_inode(&remove_from));
|
let mut last_inode_chunks = try!(self.repo.put_inode(&remove_from));
|
||||||
let mut last_inode_name = remove_from.name;
|
let mut last_inode_name = remove_from.name;
|
||||||
while let Some(mut inode) = inodes.pop() {
|
while let Some(mut inode) = inodes.pop() {
|
||||||
inode.children.as_mut().unwrap().insert(
|
inode.children.as_mut().unwrap().insert(
|
||||||
last_inode_name,
|
last_inode_name,
|
||||||
last_inode_chunks
|
last_inode_chunks
|
||||||
);
|
);
|
||||||
last_inode_chunks = try!(self.put_inode(&inode));
|
last_inode_chunks = try!(self.repo.put_inode(&inode));
|
||||||
last_inode_name = inode.name;
|
last_inode_name = inode.name;
|
||||||
}
|
}
|
||||||
backup.root = last_inode_chunks;
|
backup.root = last_inode_chunks;
|
||||||
|
@ -410,7 +410,7 @@ impl Repository {
|
||||||
|
|
||||||
pub fn get_backup_path<P: AsRef<Path>>(
|
pub fn get_backup_path<P: AsRef<Path>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
backup: &Backup,
|
backup: &BackupFile,
|
||||||
path: P,
|
path: P,
|
||||||
) -> Result<Vec<Inode>, RepositoryError> {
|
) -> Result<Vec<Inode>, RepositoryError> {
|
||||||
let mut inodes = vec![];
|
let mut inodes = vec![];
|
||||||
|
@ -444,7 +444,7 @@ impl Repository {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_backup_inode<P: AsRef<Path>>(
|
pub fn get_backup_inode<P: AsRef<Path>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
backup: &Backup,
|
backup: &BackupFile,
|
||||||
path: P,
|
path: P,
|
||||||
) -> Result<Inode, RepositoryError> {
|
) -> Result<Inode, RepositoryError> {
|
||||||
self.get_backup_path(backup, path).map(|mut inodes| {
|
self.get_backup_path(backup, path).map(|mut inodes| {
|
|
@ -55,24 +55,25 @@ quick_error!{
|
||||||
description(tr!("Encryption failed"))
|
description(tr!("Encryption failed"))
|
||||||
display("{}", tr_format!("Backup file error: encryption failed\n\tcaused by: {}", err))
|
display("{}", tr_format!("Backup file error: encryption failed\n\tcaused by: {}", err))
|
||||||
}
|
}
|
||||||
PartialBackupsList(partial: HashMap<String, Backup>, failed: Vec<PathBuf>) {
|
PartialBackupsList(partial: HashMap<String, BackupFile>, failed: Vec<PathBuf>) {
|
||||||
description(tr!("Some backups could not be loaded"))
|
description(tr!("Some backups could not be loaded"))
|
||||||
display("{}", tr_format!("Backup file error: some backups could not be loaded: {:?}", failed))
|
display("{}", tr_format!("Backup file error: some backups could not be loaded: {:?}", failed))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone)]
|
#[derive(Default, Debug, Clone)]
|
||||||
struct BackupHeader {
|
struct BackupFileHeader {
|
||||||
pub encryption: Option<Encryption>
|
pub encryption: Option<Encryption>
|
||||||
}
|
}
|
||||||
serde_impl!(BackupHeader(u8) {
|
serde_impl!(BackupFileHeader(u8) {
|
||||||
encryption: Option<Encryption> => 0
|
encryption: Option<Encryption> => 0
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone)]
|
#[derive(Default, Debug, Clone)]
|
||||||
pub struct Backup {
|
pub struct BackupFile {
|
||||||
pub root: ChunkList,
|
pub root: ChunkList,
|
||||||
pub total_data_size: u64, // Sum of all raw sizes of all entities
|
pub total_data_size: u64, // Sum of all raw sizes of all entities
|
||||||
pub changed_data_size: u64, // Sum of all raw sizes of all entities actively stored
|
pub changed_data_size: u64, // Sum of all raw sizes of all entities actively stored
|
||||||
|
@ -92,7 +93,7 @@ pub struct Backup {
|
||||||
pub user_names: HashMap<u32, String>,
|
pub user_names: HashMap<u32, String>,
|
||||||
pub group_names: HashMap<u32, String>
|
pub group_names: HashMap<u32, String>
|
||||||
}
|
}
|
||||||
serde_impl!(Backup(u8?) {
|
serde_impl!(BackupFile(u8?) {
|
||||||
root: ChunkList => 0,
|
root: ChunkList => 0,
|
||||||
total_data_size: u64 => 1,
|
total_data_size: u64 => 1,
|
||||||
changed_data_size: u64 => 2,
|
changed_data_size: u64 => 2,
|
||||||
|
@ -113,7 +114,7 @@ serde_impl!(Backup(u8?) {
|
||||||
group_names: HashMap<u32, String> => 17
|
group_names: HashMap<u32, String> => 17
|
||||||
});
|
});
|
||||||
|
|
||||||
impl Backup {
|
impl BackupFile {
|
||||||
pub fn read_from<P: AsRef<Path>>(crypto: &Crypto, path: P) -> Result<Self, BackupFileError> {
|
pub fn read_from<P: AsRef<Path>>(crypto: &Crypto, path: P) -> Result<Self, BackupFileError> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
let mut file = BufReader::new(try!(File::open(path).map_err(|err| {
|
let mut file = BufReader::new(try!(File::open(path).map_err(|err| {
|
||||||
|
@ -133,7 +134,7 @@ impl Backup {
|
||||||
version
|
version
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let header: BackupHeader = try!(msgpack::decode_from_stream(&mut file).context(path));
|
let header: BackupFileHeader = try!(msgpack::decode_from_stream(&mut file).context(path));
|
||||||
let mut data = Vec::new();
|
let mut data = Vec::new();
|
||||||
try!(file.read_to_end(&mut data).map_err(|err| {
|
try!(file.read_to_end(&mut data).map_err(|err| {
|
||||||
BackupFileError::Read(err, path.to_path_buf())
|
BackupFileError::Read(err, path.to_path_buf())
|
||||||
|
@ -164,7 +165,7 @@ impl Backup {
|
||||||
try!(file.write_all(&[HEADER_VERSION]).map_err(|err| {
|
try!(file.write_all(&[HEADER_VERSION]).map_err(|err| {
|
||||||
BackupFileError::Write(err, path.to_path_buf())
|
BackupFileError::Write(err, path.to_path_buf())
|
||||||
}));
|
}));
|
||||||
let header = BackupHeader { encryption };
|
let header = BackupFileHeader { encryption };
|
||||||
try!(msgpack::encode_to_stream(&header, &mut file).context(path));
|
try!(msgpack::encode_to_stream(&header, &mut file).context(path));
|
||||||
try!(file.write_all(&data).map_err(|err| {
|
try!(file.write_all(&data).map_err(|err| {
|
||||||
BackupFileError::Write(err, path.to_path_buf())
|
BackupFileError::Write(err, path.to_path_buf())
|
||||||
|
@ -175,7 +176,7 @@ impl Backup {
|
||||||
pub fn get_all_from<P: AsRef<Path>>(
|
pub fn get_all_from<P: AsRef<Path>>(
|
||||||
crypto: &Crypto,
|
crypto: &Crypto,
|
||||||
path: P,
|
path: P,
|
||||||
) -> Result<HashMap<String, Backup>, BackupFileError> {
|
) -> Result<HashMap<String, BackupFile>, BackupFileError> {
|
||||||
let mut backups = HashMap::new();
|
let mut backups = HashMap::new();
|
||||||
let base_path = path.as_ref();
|
let base_path = path.as_ref();
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
|
@ -203,7 +204,7 @@ impl Backup {
|
||||||
.with_file_name(relpath.file_stem().unwrap())
|
.with_file_name(relpath.file_stem().unwrap())
|
||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
.to_string();
|
.to_string();
|
||||||
if let Ok(backup) = Backup::read_from(crypto, &path) {
|
if let Ok(backup) = BackupFile::read_from(crypto, &path) {
|
||||||
backups.insert(name, backup);
|
backups.insert(name, backup);
|
||||||
} else {
|
} else {
|
||||||
failed_paths.push(path.clone());
|
failed_paths.push(path.clone());
|
|
@ -0,0 +1,350 @@
|
||||||
|
use prelude::*;
|
||||||
|
|
||||||
|
use filetime::{self, FileTime};
|
||||||
|
use xattr;
|
||||||
|
use libc;
|
||||||
|
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::fs::{self, File, Permissions};
|
||||||
|
use std::os::linux::fs::MetadataExt;
|
||||||
|
use std::os::unix::fs::{FileTypeExt, PermissionsExt, MetadataExt as UnixMetadataExt, symlink};
|
||||||
|
use std::io;
|
||||||
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
use std::fmt;
|
||||||
|
use std::ffi;
|
||||||
|
|
||||||
|
|
||||||
|
quick_error!{
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum InodeError {
|
||||||
|
UnsupportedFiletype(path: PathBuf) {
|
||||||
|
description(tr!("Unsupported file type"))
|
||||||
|
display("{}", tr_format!("Inode error: file {:?} has an unsupported type", path))
|
||||||
|
}
|
||||||
|
ReadMetadata(err: io::Error, path: PathBuf) {
|
||||||
|
cause(err)
|
||||||
|
description(tr!("Failed to obtain metadata for file"))
|
||||||
|
display("{}", tr_format!("Inode error: failed to obtain metadata for file {:?}\n\tcaused by: {}", path, err))
|
||||||
|
}
|
||||||
|
ReadXattr(err: io::Error, path: PathBuf) {
|
||||||
|
cause(err)
|
||||||
|
description(tr!("Failed to obtain xattr for file"))
|
||||||
|
display("{}", tr_format!("Inode error: failed to obtain xattr for file {:?}\n\tcaused by: {}", path, err))
|
||||||
|
}
|
||||||
|
ReadLinkTarget(err: io::Error, path: PathBuf) {
|
||||||
|
cause(err)
|
||||||
|
description(tr!("Failed to obtain link target for file"))
|
||||||
|
display("{}", tr_format!("Inode error: failed to obtain link target for file {:?}\n\tcaused by: {}", path, err))
|
||||||
|
}
|
||||||
|
Create(err: io::Error, path: PathBuf) {
|
||||||
|
cause(err)
|
||||||
|
description(tr!("Failed to create entity"))
|
||||||
|
display("{}", tr_format!("Inode error: failed to create entity {:?}\n\tcaused by: {}", path, err))
|
||||||
|
}
|
||||||
|
Integrity(reason: &'static str) {
|
||||||
|
description(tr!("Integrity error"))
|
||||||
|
display("{}", tr_format!("Inode error: inode integrity error: {}", reason))
|
||||||
|
}
|
||||||
|
Decode(err: msgpack::DecodeError) {
|
||||||
|
from()
|
||||||
|
cause(err)
|
||||||
|
description(tr!("Failed to decode metadata"))
|
||||||
|
display("{}", tr_format!("Inode error: failed to decode metadata\n\tcaused by: {}", err))
|
||||||
|
}
|
||||||
|
Encode(err: msgpack::EncodeError) {
|
||||||
|
from()
|
||||||
|
cause(err)
|
||||||
|
description(tr!("Failed to encode metadata"))
|
||||||
|
display("{}", tr_format!("Inode error: failed to encode metadata\n\tcaused by: {}", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)]
|
||||||
|
pub enum FileType {
|
||||||
|
File,
|
||||||
|
Directory,
|
||||||
|
Symlink,
|
||||||
|
BlockDevice,
|
||||||
|
CharDevice,
|
||||||
|
NamedPipe
|
||||||
|
}
|
||||||
|
serde_impl!(FileType(u8) {
|
||||||
|
File => 0,
|
||||||
|
Directory => 1,
|
||||||
|
Symlink => 2,
|
||||||
|
BlockDevice => 3,
|
||||||
|
CharDevice => 4,
|
||||||
|
NamedPipe => 5
|
||||||
|
});
|
||||||
|
impl fmt::Display for FileType {
|
||||||
|
fn fmt(&self, format: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
|
match *self {
|
||||||
|
FileType::File => write!(format, "{}", tr!("file")),
|
||||||
|
FileType::Directory => write!(format, "{}", tr!("directory")),
|
||||||
|
FileType::Symlink => write!(format, "{}", tr!("symlink")),
|
||||||
|
FileType::BlockDevice => write!(format, "{}", tr!("block device")),
|
||||||
|
FileType::CharDevice => write!(format, "{}", tr!("char device")),
|
||||||
|
FileType::NamedPipe => write!(format, "{}", tr!("named pipe")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||||
|
pub enum FileData {
|
||||||
|
Inline(msgpack::Bytes),
|
||||||
|
ChunkedDirect(ChunkList),
|
||||||
|
ChunkedIndirect(ChunkList)
|
||||||
|
}
|
||||||
|
serde_impl!(FileData(u8) {
|
||||||
|
Inline(ByteBuf) => 0,
|
||||||
|
ChunkedDirect(ChunkList) => 1,
|
||||||
|
ChunkedIndirect(ChunkList) => 2
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Hash, Eq, PartialEq)]
|
||||||
|
pub struct Inode {
|
||||||
|
pub name: String,
|
||||||
|
pub size: u64,
|
||||||
|
pub file_type: FileType,
|
||||||
|
pub mode: u32,
|
||||||
|
pub user: u32,
|
||||||
|
pub group: u32,
|
||||||
|
pub timestamp: i64,
|
||||||
|
pub symlink_target: Option<String>,
|
||||||
|
pub data: Option<FileData>,
|
||||||
|
pub children: Option<BTreeMap<String, ChunkList>>,
|
||||||
|
pub cum_size: u64,
|
||||||
|
pub cum_dirs: usize,
|
||||||
|
pub cum_files: usize,
|
||||||
|
pub xattrs: BTreeMap<String, msgpack::Bytes>,
|
||||||
|
pub device: Option<(u32, u32)>
|
||||||
|
}
|
||||||
|
impl Default for Inode {
|
||||||
|
fn default() -> Self {
|
||||||
|
Inode {
|
||||||
|
name: "".to_string(),
|
||||||
|
size: 0,
|
||||||
|
file_type: FileType::File,
|
||||||
|
mode: 0o644,
|
||||||
|
user: 1000,
|
||||||
|
group: 1000,
|
||||||
|
timestamp: 0,
|
||||||
|
symlink_target: None,
|
||||||
|
data: None,
|
||||||
|
children: None,
|
||||||
|
cum_size: 0,
|
||||||
|
cum_dirs: 0,
|
||||||
|
cum_files: 0,
|
||||||
|
xattrs: BTreeMap::new(),
|
||||||
|
device: None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
serde_impl!(Inode(u8?) {
|
||||||
|
name: String => 0,
|
||||||
|
size: u64 => 1,
|
||||||
|
file_type: FileType => 2,
|
||||||
|
mode: u32 => 3,
|
||||||
|
user: u32 => 4,
|
||||||
|
group: u32 => 5,
|
||||||
|
timestamp: i64 => 7,
|
||||||
|
symlink_target: Option<String> => 9,
|
||||||
|
data: Option<FileData> => 10,
|
||||||
|
children: Option<BTreeMap<String, ChunkList>> => 11,
|
||||||
|
cum_size: u64 => 12,
|
||||||
|
cum_dirs: usize => 13,
|
||||||
|
cum_files: usize => 14,
|
||||||
|
xattrs: BTreeMap<String, msgpack::Bytes> => 15,
|
||||||
|
device: Option<(u32, u32)> => 16
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
impl Inode {
|
||||||
|
pub fn get_from<P: AsRef<Path>>(path: P) -> Result<Self, InodeError> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
let name = path.file_name()
|
||||||
|
.map(|s| s.to_string_lossy().to_string())
|
||||||
|
.unwrap_or_else(|| "_".to_string());
|
||||||
|
let meta = try!(fs::symlink_metadata(path).map_err(|e| {
|
||||||
|
InodeError::ReadMetadata(e, path.to_owned())
|
||||||
|
}));
|
||||||
|
let mut inode = Inode::default();
|
||||||
|
inode.name = name;
|
||||||
|
if meta.is_file() {
|
||||||
|
inode.size = meta.len();
|
||||||
|
}
|
||||||
|
inode.file_type = if meta.is_file() {
|
||||||
|
FileType::File
|
||||||
|
} else if meta.is_dir() {
|
||||||
|
FileType::Directory
|
||||||
|
} else if meta.file_type().is_symlink() {
|
||||||
|
FileType::Symlink
|
||||||
|
} else if meta.file_type().is_block_device() {
|
||||||
|
FileType::BlockDevice
|
||||||
|
} else if meta.file_type().is_char_device() {
|
||||||
|
FileType::CharDevice
|
||||||
|
} else if meta.file_type().is_fifo() {
|
||||||
|
FileType::NamedPipe
|
||||||
|
} else {
|
||||||
|
return Err(InodeError::UnsupportedFiletype(path.to_owned()));
|
||||||
|
};
|
||||||
|
if meta.file_type().is_symlink() {
|
||||||
|
inode.symlink_target = Some(
|
||||||
|
try!(fs::read_link(path).map_err(|e| {
|
||||||
|
InodeError::ReadLinkTarget(e, path.to_owned())
|
||||||
|
})).to_string_lossy()
|
||||||
|
.to_string()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if meta.file_type().is_block_device() || meta.file_type().is_char_device() {
|
||||||
|
let rdev = meta.rdev();
|
||||||
|
let major = (rdev >> 8) as u32;
|
||||||
|
let minor = (rdev & 0xff) as u32;
|
||||||
|
inode.device = Some((major, minor));
|
||||||
|
}
|
||||||
|
inode.mode = meta.permissions().mode();
|
||||||
|
inode.user = meta.st_uid();
|
||||||
|
inode.group = meta.st_gid();
|
||||||
|
inode.timestamp = meta.st_mtime();
|
||||||
|
if xattr::SUPPORTED_PLATFORM {
|
||||||
|
if let Ok(attrs) = xattr::list(path) {
|
||||||
|
for name in attrs {
|
||||||
|
if let Some(data) = try!(xattr::get(path, &name).map_err(|e| {
|
||||||
|
InodeError::ReadXattr(e, path.to_owned())
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
inode.xattrs.insert(
|
||||||
|
name.to_string_lossy().to_string(),
|
||||||
|
data.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(inode)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_at<P: AsRef<Path>>(&self, path: P) -> Result<Option<File>, InodeError> {
|
||||||
|
let full_path = path.as_ref().join(&self.name);
|
||||||
|
let mut file = None;
|
||||||
|
match self.file_type {
|
||||||
|
FileType::File => {
|
||||||
|
file = Some(try!(File::create(&full_path).map_err(|e| {
|
||||||
|
InodeError::Create(e, full_path.clone())
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
FileType::Directory => {
|
||||||
|
try!(fs::create_dir(&full_path).map_err(|e| {
|
||||||
|
InodeError::Create(e, full_path.clone())
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
FileType::Symlink => {
|
||||||
|
if let Some(ref src) = self.symlink_target {
|
||||||
|
try!(symlink(src, &full_path).map_err(|e| {
|
||||||
|
InodeError::Create(e, full_path.clone())
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
return Err(InodeError::Integrity(tr!("Symlink without target")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FileType::NamedPipe => {
|
||||||
|
let name = try!(
|
||||||
|
ffi::CString::new(full_path.as_os_str().as_bytes())
|
||||||
|
.map_err(|_| InodeError::Integrity(tr!("Name contains nulls")))
|
||||||
|
);
|
||||||
|
let mode = self.mode | libc::S_IFIFO;
|
||||||
|
if unsafe { libc::mkfifo(name.as_ptr(), mode) } != 0 {
|
||||||
|
return Err(InodeError::Create(
|
||||||
|
io::Error::last_os_error(),
|
||||||
|
full_path.clone()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FileType::BlockDevice | FileType::CharDevice => {
|
||||||
|
let name = try!(
|
||||||
|
ffi::CString::new(full_path.as_os_str().as_bytes())
|
||||||
|
.map_err(|_| InodeError::Integrity(tr!("Name contains nulls")))
|
||||||
|
);
|
||||||
|
let mode = self.mode |
|
||||||
|
match self.file_type {
|
||||||
|
FileType::BlockDevice => libc::S_IFBLK,
|
||||||
|
FileType::CharDevice => libc::S_IFCHR,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
let device = if let Some((major, minor)) = self.device {
|
||||||
|
unsafe { libc::makedev(major, minor) }
|
||||||
|
} else {
|
||||||
|
return Err(InodeError::Integrity(tr!("Device without id")));
|
||||||
|
};
|
||||||
|
if unsafe { libc::mknod(name.as_ptr(), mode, device) } != 0 {
|
||||||
|
return Err(InodeError::Create(
|
||||||
|
io::Error::last_os_error(),
|
||||||
|
full_path.clone()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let time = FileTime::from_seconds_since_1970(self.timestamp as u64, 0);
|
||||||
|
if let Err(err) = filetime::set_file_times(&full_path, time, time) {
|
||||||
|
tr_warn!("Failed to set file time on {:?}: {}", full_path, err);
|
||||||
|
}
|
||||||
|
if !self.xattrs.is_empty() {
|
||||||
|
if xattr::SUPPORTED_PLATFORM {
|
||||||
|
for (name, data) in &self.xattrs {
|
||||||
|
if let Err(err) = xattr::set(&full_path, name, data) {
|
||||||
|
tr_warn!("Failed to set xattr {} on {:?}: {}", name, full_path, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tr_warn!("Not setting xattr on {:?}", full_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Err(err) = fs::set_permissions(&full_path, Permissions::from_mode(self.mode)) {
|
||||||
|
tr_warn!(
|
||||||
|
"Failed to set permissions {:o} on {:?}: {}",
|
||||||
|
self.mode,
|
||||||
|
full_path,
|
||||||
|
err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if let Err(err) = chown(&full_path, self.user, self.group) {
|
||||||
|
tr_warn!(
|
||||||
|
"Failed to set user {} and group {} on {:?}: {}",
|
||||||
|
self.user,
|
||||||
|
self.group,
|
||||||
|
full_path,
|
||||||
|
err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_same_meta(&self, other: &Inode) -> bool {
|
||||||
|
self.file_type == other.file_type && self.size == other.size &&
|
||||||
|
self.mode == other.mode && self.user == other.user &&
|
||||||
|
self.group == other.group && self.name == other.name &&
|
||||||
|
self.timestamp == other.timestamp && self.symlink_target == other.symlink_target
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_same_meta_quick(&self, other: &Inode) -> bool {
|
||||||
|
self.timestamp == other.timestamp && self.file_type == other.file_type &&
|
||||||
|
self.size == other.size
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn encode(&self) -> Result<Vec<u8>, InodeError> {
|
||||||
|
Ok(try!(msgpack::encode(&self)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn decode(data: &[u8]) -> Result<Self, InodeError> {
|
||||||
|
Ok(try!(msgpack::decode(data)))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,323 @@
|
||||||
|
use prelude::*;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
|
||||||
|
quick_error!{
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum InodeIntegrityError {
|
||||||
|
BrokenInode(path: PathBuf, err: Box<RepositoryError>) {
|
||||||
|
cause(err)
|
||||||
|
description(tr!("Broken inode"))
|
||||||
|
display("{}", tr_format!("Broken inode: {:?}\n\tcaused by: {}", path, err))
|
||||||
|
}
|
||||||
|
MissingInodeData(path: PathBuf, err: Box<RepositoryError>) {
|
||||||
|
cause(err)
|
||||||
|
description(tr!("Missing inode data"))
|
||||||
|
display("{}", tr_format!("Missing inode data in: {:?}\n\tcaused by: {}", path, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BackupRepository {
|
||||||
|
fn check_inode_contents(
|
||||||
|
&mut self,
|
||||||
|
inode: &Inode,
|
||||||
|
checked: &mut Bitmap,
|
||||||
|
) -> Result<(), RepositoryError> {
|
||||||
|
match inode.data {
|
||||||
|
None |
|
||||||
|
Some(FileData::Inline(_)) => (),
|
||||||
|
Some(FileData::ChunkedDirect(ref chunks)) => {
|
||||||
|
try!(self.repo.check_chunks(checked, chunks, true));
|
||||||
|
}
|
||||||
|
Some(FileData::ChunkedIndirect(ref chunks)) => {
|
||||||
|
if try!(self.repo.check_chunks(checked, chunks, false)) {
|
||||||
|
let chunk_data = try!(self.get_data(chunks));
|
||||||
|
let chunks2 = ChunkList::read_from(&chunk_data);
|
||||||
|
try!(self.repo.check_chunks(checked, &chunks2, true));
|
||||||
|
try!(self.repo.check_chunks(checked, chunks, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_subtree(
|
||||||
|
&mut self,
|
||||||
|
path: PathBuf,
|
||||||
|
chunks: &[Chunk],
|
||||||
|
checked: &mut Bitmap,
|
||||||
|
repair: bool,
|
||||||
|
) -> Result<Option<ChunkList>, RepositoryError> {
|
||||||
|
let mut modified = false;
|
||||||
|
match self.repo.check_chunks(checked, chunks, false) {
|
||||||
|
Ok(false) => return Ok(None),
|
||||||
|
Ok(true) => (),
|
||||||
|
Err(err) => return Err(InodeIntegrityError::BrokenInode(path, Box::new(err)).into()),
|
||||||
|
}
|
||||||
|
let mut inode = try!(self.get_inode(chunks));
|
||||||
|
// Mark the content chunks as used
|
||||||
|
if let Err(err) = self.check_inode_contents(&inode, checked) {
|
||||||
|
if repair {
|
||||||
|
tr_warn!(
|
||||||
|
"Problem detected: data of {:?} is corrupt\n\tcaused by: {}",
|
||||||
|
path,
|
||||||
|
err
|
||||||
|
);
|
||||||
|
tr_info!("Removing inode data");
|
||||||
|
inode.data = Some(FileData::Inline(vec![].into()));
|
||||||
|
inode.size = 0;
|
||||||
|
modified = true;
|
||||||
|
} else {
|
||||||
|
return Err(InodeIntegrityError::MissingInodeData(path, Box::new(err)).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Put children in to do
|
||||||
|
if let Some(ref mut children) = inode.children {
|
||||||
|
let mut removed = vec![];
|
||||||
|
for (name, chunks) in children.iter_mut() {
|
||||||
|
match self.check_subtree(path.join(name), chunks, checked, repair) {
|
||||||
|
Ok(None) => (),
|
||||||
|
Ok(Some(c)) => {
|
||||||
|
*chunks = c;
|
||||||
|
modified = true;
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
if repair {
|
||||||
|
tr_warn!(
|
||||||
|
"Problem detected: inode {:?} is corrupt\n\tcaused by: {}",
|
||||||
|
path.join(name),
|
||||||
|
err
|
||||||
|
);
|
||||||
|
tr_info!("Removing broken inode from backup");
|
||||||
|
removed.push(name.to_string());
|
||||||
|
modified = true;
|
||||||
|
} else {
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for name in removed {
|
||||||
|
children.remove(&name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if modified {
|
||||||
|
Ok(Some(try!(self.repo.put_inode(&inode))))
|
||||||
|
} else {
|
||||||
|
try!(self.repo.check_chunks(checked, chunks, true));
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn evacuate_broken_backup(&self, name: &str) -> Result<(), RepositoryError> {
|
||||||
|
tr_warn!(
|
||||||
|
"The backup {} was corrupted and needed to be modified.",
|
||||||
|
name
|
||||||
|
);
|
||||||
|
let src = self.layout.backup_path(name);
|
||||||
|
let mut dst = src.with_extension("backup.broken");
|
||||||
|
let mut num = 1;
|
||||||
|
while dst.exists() {
|
||||||
|
dst = src.with_extension(&format!("backup.{}.broken", num));
|
||||||
|
num += 1;
|
||||||
|
}
|
||||||
|
if fs::rename(&src, &dst).is_err() {
|
||||||
|
try!(fs::copy(&src, &dst));
|
||||||
|
try!(fs::remove_file(&src));
|
||||||
|
}
|
||||||
|
tr_info!("The original backup was renamed to {:?}", dst);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn check_backup(
|
||||||
|
&mut self,
|
||||||
|
name: &str,
|
||||||
|
backup: &mut BackupFile,
|
||||||
|
repair: bool,
|
||||||
|
) -> Result<(), RepositoryError> {
|
||||||
|
let _lock = if repair {
|
||||||
|
try!(self.repo.write_mode());
|
||||||
|
Some(self.repo.lock(false))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
tr_info!("Checking backup...");
|
||||||
|
let mut checked = self.repo.get_chunk_marker();
|
||||||
|
match self.check_subtree(
|
||||||
|
Path::new("").to_path_buf(),
|
||||||
|
&backup.root,
|
||||||
|
&mut checked,
|
||||||
|
repair
|
||||||
|
) {
|
||||||
|
Ok(None) => (),
|
||||||
|
Ok(Some(chunks)) => {
|
||||||
|
try!(self.repo.flush());
|
||||||
|
backup.root = chunks;
|
||||||
|
backup.modified = true;
|
||||||
|
try!(self.evacuate_broken_backup(name));
|
||||||
|
try!(self.save_backup(backup, name));
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
if repair {
|
||||||
|
tr_warn!(
|
||||||
|
"The root of the backup {} has been corrupted\n\tcaused by: {}",
|
||||||
|
name,
|
||||||
|
err
|
||||||
|
);
|
||||||
|
try!(self.evacuate_broken_backup(name));
|
||||||
|
} else {
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_backup_inode(
|
||||||
|
&mut self,
|
||||||
|
name: &str,
|
||||||
|
backup: &mut BackupFile,
|
||||||
|
path: &Path,
|
||||||
|
repair: bool,
|
||||||
|
) -> Result<(), RepositoryError> {
|
||||||
|
let _lock = if repair {
|
||||||
|
try!(self.repo.write_mode());
|
||||||
|
Some(self.repo.lock(false))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
tr_info!("Checking inode...");
|
||||||
|
let mut checked = self.repo.get_chunk_marker();
|
||||||
|
let mut inodes = try!(self.get_backup_path(backup, path));
|
||||||
|
let mut inode = inodes.pop().unwrap();
|
||||||
|
let mut modified = false;
|
||||||
|
if let Err(err) = self.check_inode_contents(&inode, &mut checked) {
|
||||||
|
if repair {
|
||||||
|
tr_warn!(
|
||||||
|
"Problem detected: data of {:?} is corrupt\n\tcaused by: {}",
|
||||||
|
path,
|
||||||
|
err
|
||||||
|
);
|
||||||
|
tr_info!("Removing inode data");
|
||||||
|
inode.data = Some(FileData::Inline(vec![].into()));
|
||||||
|
inode.size = 0;
|
||||||
|
modified = true;
|
||||||
|
} else {
|
||||||
|
return Err(
|
||||||
|
InodeIntegrityError::MissingInodeData(path.to_path_buf(), Box::new(err)).into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(ref mut children) = inode.children {
|
||||||
|
let mut removed = vec![];
|
||||||
|
for (name, chunks) in children.iter_mut() {
|
||||||
|
match self.check_subtree(path.join(name), chunks, &mut checked, repair) {
|
||||||
|
Ok(None) => (),
|
||||||
|
Ok(Some(c)) => {
|
||||||
|
*chunks = c;
|
||||||
|
modified = true;
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
if repair {
|
||||||
|
tr_warn!(
|
||||||
|
"Problem detected: inode {:?} is corrupt\n\tcaused by: {}",
|
||||||
|
path.join(name),
|
||||||
|
err
|
||||||
|
);
|
||||||
|
tr_info!("Removing broken inode from backup");
|
||||||
|
removed.push(name.to_string());
|
||||||
|
modified = true;
|
||||||
|
} else {
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for name in removed {
|
||||||
|
children.remove(&name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut chunks = try!(self.repo.put_inode(&inode));
|
||||||
|
while let Some(mut parent) = inodes.pop() {
|
||||||
|
parent.children.as_mut().unwrap().insert(inode.name, chunks);
|
||||||
|
inode = parent;
|
||||||
|
chunks = try!(self.repo.put_inode(&inode));
|
||||||
|
}
|
||||||
|
if modified {
|
||||||
|
try!(self.repo.flush());
|
||||||
|
backup.root = chunks;
|
||||||
|
backup.modified = true;
|
||||||
|
try!(self.evacuate_broken_backup(name));
|
||||||
|
try!(self.save_backup(backup, name));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_backups(&mut self, repair: bool) -> Result<(), RepositoryError> {
|
||||||
|
let _lock = if repair {
|
||||||
|
try!(self.repo.write_mode());
|
||||||
|
Some(self.repo.lock(false))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
tr_info!("Checking backups...");
|
||||||
|
let mut checked = self.repo.get_chunk_marker();
|
||||||
|
let backup_map = match self.get_all_backups() {
|
||||||
|
Ok(backup_map) => backup_map,
|
||||||
|
Err(RepositoryError::BackupFile(BackupFileError::PartialBackupsList(backup_map,
|
||||||
|
_failed))) => {
|
||||||
|
tr_warn!("Some backups could not be read, ignoring them");
|
||||||
|
backup_map
|
||||||
|
}
|
||||||
|
Err(err) => return Err(err),
|
||||||
|
};
|
||||||
|
for (name, mut backup) in
|
||||||
|
ProgressIter::new(tr!("checking backups"), backup_map.len(), backup_map.into_iter())
|
||||||
|
{
|
||||||
|
let path = format!("{}::", name);
|
||||||
|
match self.check_subtree(
|
||||||
|
Path::new(&path).to_path_buf(),
|
||||||
|
&backup.root,
|
||||||
|
&mut checked,
|
||||||
|
repair
|
||||||
|
) {
|
||||||
|
Ok(None) => (),
|
||||||
|
Ok(Some(chunks)) => {
|
||||||
|
try!(self.repo.flush());
|
||||||
|
backup.root = chunks;
|
||||||
|
backup.modified = true;
|
||||||
|
try!(self.evacuate_broken_backup(&name));
|
||||||
|
try!(self.save_backup(&backup, &name));
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
if repair {
|
||||||
|
tr_warn!(
|
||||||
|
"The root of the backup {} has been corrupted\n\tcaused by: {}",
|
||||||
|
name,
|
||||||
|
err
|
||||||
|
);
|
||||||
|
try!(self.evacuate_broken_backup(&name));
|
||||||
|
} else {
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn check_bundles(&mut self, full: bool, repair: bool) -> Result<(), RepositoryError> {
|
||||||
|
self.repo.check_bundles(full, repair)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_repository(&mut self, repair: bool) -> Result<(), RepositoryError> {
|
||||||
|
self.repo.check_repository(repair)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,48 +1,91 @@
|
||||||
pub mod mount;
|
pub mod mount;
|
||||||
|
mod backup_file;
|
||||||
|
mod inode;
|
||||||
|
mod tarfile;
|
||||||
|
mod backup;
|
||||||
|
mod integrity;
|
||||||
|
mod vacuum;
|
||||||
|
|
||||||
|
pub use self::backup::{BackupOptions, BackupError, DiffType};
|
||||||
|
pub use self::backup_file::{BackupFile, BackupFileError};
|
||||||
|
pub use self::inode::{Inode, FileData, FileType, InodeError};
|
||||||
|
pub use self::integrity::InodeIntegrityError;
|
||||||
|
|
||||||
use ::prelude::*;
|
use ::prelude::*;
|
||||||
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::Path;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::fs::{self, File};
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
|
||||||
|
const DEFAULT_EXCLUDES: &[u8] = include_bytes!("../../docs/excludes.default");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
pub struct BackupRepository {
|
pub struct BackupRepository {
|
||||||
layout: Arc<RepositoryLayout>,
|
layout: Arc<RepositoryLayout>,
|
||||||
|
crypto: Arc<Crypto>,
|
||||||
repo: Repository
|
repo: Repository
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BackupRepository {
|
impl BackupRepository {
|
||||||
pub fn create<P: AsRef<Path>, R: AsRef<Path>>(path: P, config: &Config, remote: R) -> Result<Self, RepositoryError> {
|
pub fn create<P: AsRef<Path>, R: AsRef<Path>>(path: P, config: &Config, remote: R) -> Result<Self, RepositoryError> {
|
||||||
let layout = Arc::new(RepositoryLayout::new(path.as_ref()));
|
let layout = Arc::new(RepositoryLayout::new(path.as_ref()));
|
||||||
|
try!(fs::create_dir(layout.base_path()));
|
||||||
|
try!(File::create(layout.excludes_path()).and_then(|mut f| {
|
||||||
|
f.write_all(DEFAULT_EXCLUDES)
|
||||||
|
}));
|
||||||
|
try!(fs::create_dir(layout.keys_path()));
|
||||||
|
let crypto = Arc::new(try!(Crypto::open(layout.keys_path())));
|
||||||
Ok(BackupRepository {
|
Ok(BackupRepository {
|
||||||
|
crypto: crypto.clone(),
|
||||||
layout: layout.clone(),
|
layout: layout.clone(),
|
||||||
repo: try!(Repository::create(layout, config, remote))
|
repo: try!(Repository::create(layout, config, crypto, remote))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unknown_lints, useless_let_if_seq)]
|
#[allow(unknown_lints, useless_let_if_seq)]
|
||||||
pub fn open<P: AsRef<Path>>(path: P, online: bool) -> Result<Self, RepositoryError> {
|
pub fn open<P: AsRef<Path>>(path: P, online: bool) -> Result<Self, RepositoryError> {
|
||||||
let layout = Arc::new(RepositoryLayout::new(path.as_ref()));
|
let layout = Arc::new(RepositoryLayout::new(path.as_ref()));
|
||||||
|
let crypto = Arc::new(try!(Crypto::open(layout.keys_path())));
|
||||||
Ok(BackupRepository {
|
Ok(BackupRepository {
|
||||||
|
crypto: crypto.clone(),
|
||||||
layout: layout.clone(),
|
layout: layout.clone(),
|
||||||
repo: try!(Repository::open(layout, online))
|
repo: try!(Repository::open(layout, crypto, online))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn import<P: AsRef<Path>, R: AsRef<Path>>(path: P, remote: R, key_files: Vec<String>) -> Result<Self, RepositoryError> {
|
pub fn import<P: AsRef<Path>, R: AsRef<Path>>(path: P, remote: R, key_files: Vec<String>) -> Result<Self, RepositoryError> {
|
||||||
let layout = Arc::new(RepositoryLayout::new(path.as_ref()));
|
let config = Config::default();
|
||||||
Ok(BackupRepository {
|
let mut repo = try!(Self::create(&path, &config, remote));
|
||||||
layout: layout.clone(),
|
for file in key_files {
|
||||||
repo: try!(Repository::import(layout, remote, key_files))
|
try!(repo.crypto.register_keyfile(file));
|
||||||
})
|
}
|
||||||
|
repo = try!(Self::open(&path, true));
|
||||||
|
let mut backups: Vec<(String, BackupFile)> = try!(repo.get_all_backups()).into_iter().collect();
|
||||||
|
backups.sort_by_key(|&(_, ref b)| b.timestamp);
|
||||||
|
if let Some((name, backup)) = backups.pop() {
|
||||||
|
tr_info!("Taking configuration from the last backup '{}'", name);
|
||||||
|
repo.repo.set_config(backup.config);
|
||||||
|
try!(repo.save_config())
|
||||||
|
} else {
|
||||||
|
tr_warn!(
|
||||||
|
"No backup found in the repository to take configuration from, please set the configuration manually."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(repo)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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> {
|
||||||
self.repo.register_key(public, secret)
|
try!(self.repo.write_mode());
|
||||||
|
try!(self.crypto.register_secret_key(public, secret));
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn save_config(&mut self) -> Result<(), RepositoryError> {
|
pub fn save_config(&mut self) -> Result<(), RepositoryError> {
|
||||||
self.repo.save_config()
|
self.repo.save_config()
|
||||||
|
@ -53,29 +96,11 @@ impl BackupRepository {
|
||||||
self.repo.set_encryption(public)
|
self.repo.set_encryption(public)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn has_backup(&self, name: &str) -> bool {
|
|
||||||
self.repo.has_backup(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_backup(&self, name: &str) -> Result<Backup, RepositoryError> {
|
|
||||||
self.repo.get_backup(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn get_backup_inode<P: AsRef<Path>>(&mut self, backup: &Backup, path: P) -> Result<Inode, RepositoryError> {
|
|
||||||
self.repo.get_backup_inode(backup, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_inode(&mut self, chunks: &[Chunk]) -> Result<Inode, RepositoryError> {
|
pub fn get_inode(&mut self, chunks: &[Chunk]) -> Result<Inode, RepositoryError> {
|
||||||
self.repo.get_inode(chunks)
|
self.repo.get_inode(chunks)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_all_backups(&self) -> Result<HashMap<String, Backup>, RepositoryError> {
|
|
||||||
self.repo.get_all_backups()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_config(&self) -> &Config {
|
pub fn get_config(&self) -> &Config {
|
||||||
self.repo.get_config()
|
self.repo.get_config()
|
||||||
}
|
}
|
||||||
|
@ -88,77 +113,15 @@ impl BackupRepository {
|
||||||
&self.layout
|
&self.layout
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_backup_recursively<P: AsRef<Path>>(&mut self, path: P, reference: Option<&Backup>, options: &BackupOptions) -> Result<Backup, RepositoryError> {
|
|
||||||
self.repo.create_backup_recursively(path, reference, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn import_tarfile<P: AsRef<Path>>(&mut self, tarfile: P) -> Result<Backup, RepositoryError> {
|
|
||||||
self.repo.import_tarfile(tarfile)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn save_backup(&mut self, backup: &Backup, name: &str) -> Result<(), RepositoryError> {
|
|
||||||
self.repo.save_backup(backup, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn export_tarfile<P: AsRef<Path>>(&mut self, backup: &Backup, inode: Inode, tarfile: P) -> Result<(), RepositoryError> {
|
|
||||||
self.repo.export_tarfile(backup, inode, tarfile)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn restore_inode_tree<P: AsRef<Path>>(&mut self, backup: &Backup, inode: Inode, path: P) -> Result<(), RepositoryError> {
|
|
||||||
self.repo.restore_inode_tree(backup, inode, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove_backup_path<P: AsRef<Path>>(&mut self, backup: &mut Backup, path: P) -> Result<(), RepositoryError> {
|
|
||||||
self.repo.remove_backup_path(backup, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_backups<P: AsRef<Path>>(&self, path: P) -> Result<HashMap<String, Backup>, RepositoryError> {
|
|
||||||
self.repo.get_backups(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn delete_backup(&mut self, name: &str) -> Result<(), RepositoryError> {
|
|
||||||
self.repo.delete_backup(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn prune_backups(&mut self, prefix: &str, daily: usize, weekly: usize, monthly: usize, yearly: usize, force: bool) -> Result<(), RepositoryError> {
|
|
||||||
self.repo.prune_backups(prefix, daily, weekly, monthly, yearly, force)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn info(&self) -> RepositoryInfo {
|
pub fn info(&self) -> RepositoryInfo {
|
||||||
self.repo.info()
|
self.repo.info()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn vacuum(&mut self, ratio: f32, combine: bool, force: bool) -> Result<(), RepositoryError> {
|
|
||||||
self.repo.vacuum(ratio, combine, force)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn check_repository(&mut self, repair: bool) -> Result<(), RepositoryError> {
|
|
||||||
self.repo.check_repository(repair)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn check_bundles(&mut self, full: bool, repair: bool) -> Result<(), RepositoryError> {
|
|
||||||
self.repo.check_bundles(full, repair)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn check_index(&mut self, repair: bool) -> Result<(), RepositoryError> {
|
pub fn check_index(&mut self, repair: bool) -> Result<(), RepositoryError> {
|
||||||
self.repo.check_index(repair)
|
self.repo.check_index(repair)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_backup_inode(&mut self, name: &str, backup: &mut Backup, path: &Path, repair: bool) -> Result<(), RepositoryError> {
|
|
||||||
self.repo.check_backup_inode(name, backup, path, repair)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn check_backup(&mut self, name: &str, backup: &mut Backup, repair: bool) -> Result<(), RepositoryError> {
|
|
||||||
self.repo.check_backup(name, backup, repair)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn check_backups(&mut self, repair: bool) -> Result<(), RepositoryError> {
|
|
||||||
self.repo.check_backups(repair)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_clean(&mut self) {
|
pub fn set_clean(&mut self) {
|
||||||
self.repo.set_clean()
|
self.repo.set_clean()
|
||||||
|
@ -168,14 +131,6 @@ impl BackupRepository {
|
||||||
self.repo.statistics()
|
self.repo.statistics()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_duplicates(&mut self, inode: &Inode, min_size: u64) -> Result<Vec<(Vec<PathBuf>, u64)>, RepositoryError> {
|
|
||||||
self.repo.find_duplicates(inode, min_size)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn analyze_usage(&mut self) -> Result<HashMap<u32, BundleAnalysis>, RepositoryError> {
|
|
||||||
self.repo.analyze_usage()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn list_bundles(&self) -> Vec<&BundleInfo> {
|
pub fn list_bundles(&self) -> Vec<&BundleInfo> {
|
||||||
self.repo.list_bundles()
|
self.repo.list_bundles()
|
||||||
|
@ -186,15 +141,6 @@ impl BackupRepository {
|
||||||
self.repo.get_bundle(bundle)
|
self.repo.get_bundle(bundle)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_versions<P: AsRef<Path>>(&mut self, path: P) -> Result<Vec<(String, Inode)>, RepositoryError> {
|
|
||||||
self.repo.find_versions(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn find_differences(&mut self, inode1: &Inode, inode2: &Inode) -> Result<Vec<(DiffType, PathBuf)>, RepositoryError> {
|
|
||||||
self.repo.find_differences(inode1, inode2)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_chunk(&mut self, hash: Hash) -> Result<Option<Vec<u8>>, RepositoryError> {
|
pub fn get_chunk(&mut self, hash: Hash) -> Result<Option<Vec<u8>>, RepositoryError> {
|
||||||
self.repo.get_chunk(hash)
|
self.repo.get_chunk(hash)
|
||||||
}
|
}
|
||||||
|
|
|
@ -197,7 +197,7 @@ impl<'a> FuseFilesystem<'a> {
|
||||||
|
|
||||||
pub fn from_backup(
|
pub fn from_backup(
|
||||||
repository: &'a mut BackupRepository,
|
repository: &'a mut BackupRepository,
|
||||||
backup: Backup,
|
backup: BackupFile,
|
||||||
) -> Result<Self, RepositoryError> {
|
) -> Result<Self, RepositoryError> {
|
||||||
let inode = try!(repository.get_inode(&backup.root));
|
let inode = try!(repository.get_inode(&backup.root));
|
||||||
let mut fs = try!(FuseFilesystem::new(repository));
|
let mut fs = try!(FuseFilesystem::new(repository));
|
||||||
|
@ -207,7 +207,7 @@ impl<'a> FuseFilesystem<'a> {
|
||||||
|
|
||||||
pub fn from_inode(
|
pub fn from_inode(
|
||||||
repository: &'a mut BackupRepository,
|
repository: &'a mut BackupRepository,
|
||||||
backup: Backup,
|
backup: BackupFile,
|
||||||
inode: Inode,
|
inode: Inode,
|
||||||
) -> Result<Self, RepositoryError> {
|
) -> Result<Self, RepositoryError> {
|
||||||
let mut fs = try!(FuseFilesystem::new(repository));
|
let mut fs = try!(FuseFilesystem::new(repository));
|
||||||
|
|
|
@ -133,7 +133,8 @@ fn inode_from_entry<R: Read>(entry: &mut tar::Entry<R>) -> Result<Inode, Reposit
|
||||||
Ok(inode)
|
Ok(inode)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Repository {
|
|
||||||
|
impl BackupRepository {
|
||||||
fn import_tar_entry<R: Read>(
|
fn import_tar_entry<R: Read>(
|
||||||
&mut self,
|
&mut self,
|
||||||
entry: &mut tar::Entry<R>,
|
entry: &mut tar::Entry<R>,
|
||||||
|
@ -144,13 +145,13 @@ impl Repository {
|
||||||
try!(entry.read_to_end(&mut data));
|
try!(entry.read_to_end(&mut data));
|
||||||
inode.data = Some(FileData::Inline(data.into()));
|
inode.data = Some(FileData::Inline(data.into()));
|
||||||
} else {
|
} else {
|
||||||
let mut chunks = try!(self.put_stream(BundleMode::Data, entry));
|
let mut chunks = try!(self.repo.put_stream(BundleMode::Data, entry));
|
||||||
if chunks.len() < 10 {
|
if chunks.len() < 10 {
|
||||||
inode.data = Some(FileData::ChunkedDirect(chunks));
|
inode.data = Some(FileData::ChunkedDirect(chunks));
|
||||||
} else {
|
} else {
|
||||||
let mut chunk_data = Vec::with_capacity(chunks.encoded_size());
|
let mut chunk_data = Vec::with_capacity(chunks.encoded_size());
|
||||||
chunks.write_to(&mut chunk_data).unwrap();
|
chunks.write_to(&mut chunk_data).unwrap();
|
||||||
chunks = try!(self.put_data(BundleMode::Meta, &chunk_data));
|
chunks = try!(self.repo.put_data(BundleMode::Meta, &chunk_data));
|
||||||
inode.data = Some(FileData::ChunkedIndirect(chunks));
|
inode.data = Some(FileData::ChunkedIndirect(chunks));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -159,7 +160,7 @@ impl Repository {
|
||||||
|
|
||||||
fn import_tarfile_as_inode<R: Read>(
|
fn import_tarfile_as_inode<R: Read>(
|
||||||
&mut self,
|
&mut self,
|
||||||
backup: &mut Backup,
|
backup: &mut BackupFile,
|
||||||
input: R,
|
input: R,
|
||||||
failed_paths: &mut Vec<PathBuf>,
|
failed_paths: &mut Vec<PathBuf>,
|
||||||
) -> Result<(Inode, ChunkList), RepositoryError> {
|
) -> Result<(Inode, ChunkList), RepositoryError> {
|
||||||
|
@ -218,7 +219,7 @@ impl Repository {
|
||||||
}
|
}
|
||||||
for path in childless {
|
for path in childless {
|
||||||
let (inode, _) = inodes.remove(&path).unwrap();
|
let (inode, _) = inodes.remove(&path).unwrap();
|
||||||
let chunks = try!(self.put_inode(&inode));
|
let chunks = try!(self.repo.put_inode(&inode));
|
||||||
if let Some(parent_path) = path.parent() {
|
if let Some(parent_path) = path.parent() {
|
||||||
if let Some(&mut (ref mut parent_inode, ref mut children)) =
|
if let Some(&mut (ref mut parent_inode, ref mut children)) =
|
||||||
inodes.get_mut(parent_path)
|
inodes.get_mut(parent_path)
|
||||||
|
@ -264,7 +265,7 @@ impl Repository {
|
||||||
children.insert(inode.name, chunks);
|
children.insert(inode.name, chunks);
|
||||||
}
|
}
|
||||||
root_inode.children = Some(children);
|
root_inode.children = Some(children);
|
||||||
let chunks = try!(self.put_inode(&root_inode));
|
let chunks = try!(self.repo.put_inode(&root_inode));
|
||||||
Ok((root_inode, chunks))
|
Ok((root_inode, chunks))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -272,15 +273,15 @@ impl Repository {
|
||||||
pub fn import_tarfile<P: AsRef<Path>>(
|
pub fn import_tarfile<P: AsRef<Path>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
tarfile: P,
|
tarfile: P,
|
||||||
) -> Result<Backup, RepositoryError> {
|
) -> Result<BackupFile, RepositoryError> {
|
||||||
try!(self.write_mode());
|
try!(self.repo.write_mode());
|
||||||
let _lock = try!(self.lock(false));
|
let _lock = try!(self.repo.lock(false));
|
||||||
if self.dirty {
|
if self.repo.is_dirty() {
|
||||||
return Err(RepositoryError::Dirty);
|
return Err(RepositoryError::Dirty);
|
||||||
}
|
}
|
||||||
try!(self.set_dirty());
|
try!(self.repo.set_dirty());
|
||||||
let mut backup = Backup::default();
|
let mut backup = BackupFile::default();
|
||||||
backup.config = self.config.clone();
|
backup.config = self.repo.get_config().clone();
|
||||||
backup.host = get_hostname().unwrap_or_else(|_| "".to_string());
|
backup.host = get_hostname().unwrap_or_else(|_| "".to_string());
|
||||||
backup.path = tarfile.as_ref().to_string_lossy().to_string();
|
backup.path = tarfile.as_ref().to_string_lossy().to_string();
|
||||||
let info_before = self.info();
|
let info_before = self.info();
|
||||||
|
@ -301,7 +302,7 @@ impl Repository {
|
||||||
))
|
))
|
||||||
};
|
};
|
||||||
backup.root = chunks;
|
backup.root = chunks;
|
||||||
try!(self.flush());
|
try!(self.repo.flush());
|
||||||
let elapsed = Local::now().signed_duration_since(start);
|
let elapsed = Local::now().signed_duration_since(start);
|
||||||
backup.timestamp = start.timestamp();
|
backup.timestamp = start.timestamp();
|
||||||
backup.total_data_size = root_inode.cum_size;
|
backup.total_data_size = root_inode.cum_size;
|
||||||
|
@ -314,7 +315,7 @@ impl Repository {
|
||||||
backup.bundle_count = info_after.bundle_count - info_before.bundle_count;
|
backup.bundle_count = info_after.bundle_count - info_before.bundle_count;
|
||||||
backup.chunk_count = info_after.chunk_count - info_before.chunk_count;
|
backup.chunk_count = info_after.chunk_count - info_before.chunk_count;
|
||||||
backup.avg_chunk_size = backup.deduplicated_data_size as f32 / backup.chunk_count as f32;
|
backup.avg_chunk_size = backup.deduplicated_data_size as f32 / backup.chunk_count as f32;
|
||||||
self.dirty = false;
|
self.repo.set_clean();
|
||||||
if failed_paths.is_empty() {
|
if failed_paths.is_empty() {
|
||||||
Ok(backup)
|
Ok(backup)
|
||||||
} else {
|
} else {
|
||||||
|
@ -340,7 +341,7 @@ impl Repository {
|
||||||
|
|
||||||
fn export_tarfile_recurse<W: Write>(
|
fn export_tarfile_recurse<W: Write>(
|
||||||
&mut self,
|
&mut self,
|
||||||
backup: &Backup,
|
backup: &BackupFile,
|
||||||
path: &Path,
|
path: &Path,
|
||||||
inode: Inode,
|
inode: Inode,
|
||||||
tarfile: &mut tar::Builder<W>,
|
tarfile: &mut tar::Builder<W>,
|
||||||
|
@ -396,11 +397,11 @@ impl Repository {
|
||||||
None => try!(tarfile.append(&header, Cursor::new(&[]))),
|
None => try!(tarfile.append(&header, Cursor::new(&[]))),
|
||||||
Some(FileData::Inline(data)) => try!(tarfile.append(&header, Cursor::new(data))),
|
Some(FileData::Inline(data)) => try!(tarfile.append(&header, Cursor::new(data))),
|
||||||
Some(FileData::ChunkedDirect(chunks)) => {
|
Some(FileData::ChunkedDirect(chunks)) => {
|
||||||
try!(tarfile.append(&header, self.get_reader(chunks)))
|
try!(tarfile.append(&header, self.repo.get_reader(chunks)))
|
||||||
}
|
}
|
||||||
Some(FileData::ChunkedIndirect(chunks)) => {
|
Some(FileData::ChunkedIndirect(chunks)) => {
|
||||||
let chunks = ChunkList::read_from(&try!(self.get_data(&chunks)));
|
let chunks = ChunkList::read_from(&try!(self.get_data(&chunks)));
|
||||||
try!(tarfile.append(&header, self.get_reader(chunks)))
|
try!(tarfile.append(&header, self.repo.get_reader(chunks)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -421,7 +422,7 @@ impl Repository {
|
||||||
|
|
||||||
pub fn export_tarfile<P: AsRef<Path>>(
|
pub fn export_tarfile<P: AsRef<Path>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
backup: &Backup,
|
backup: &BackupFile,
|
||||||
inode: Inode,
|
inode: Inode,
|
||||||
tarfile: P,
|
tarfile: P,
|
||||||
) -> Result<(), RepositoryError> {
|
) -> Result<(), RepositoryError> {
|
|
@ -0,0 +1,158 @@
|
||||||
|
use ::prelude::*;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use std::collections::{VecDeque, HashSet};
|
||||||
|
|
||||||
|
|
||||||
|
impl BackupRepository {
|
||||||
|
fn mark_used(
|
||||||
|
&self,
|
||||||
|
bundles: &mut HashMap<u32, BundleAnalysis>,
|
||||||
|
chunks: &[Chunk],
|
||||||
|
) -> Result<bool, RepositoryError> {
|
||||||
|
let mut new = false;
|
||||||
|
for &(hash, len) in chunks {
|
||||||
|
if let Some(pos) = self.repo.get_chunk_location(hash) {
|
||||||
|
let bundle = pos.bundle;
|
||||||
|
if let Some(bundle) = bundles.get_mut(&bundle) {
|
||||||
|
if !bundle.chunk_usage.get(pos.chunk as usize) {
|
||||||
|
new = true;
|
||||||
|
bundle.chunk_usage.set(pos.chunk as usize);
|
||||||
|
bundle.used_raw_size += len as usize;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(IntegrityError::MissingBundleId(pos.bundle).into());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(IntegrityError::MissingChunk(hash).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(new)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn analyze_usage(&mut self) -> Result<HashMap<u32, BundleAnalysis>, RepositoryError> {
|
||||||
|
if self.repo.is_dirty() {
|
||||||
|
return Err(RepositoryError::Dirty);
|
||||||
|
}
|
||||||
|
try!(self.repo.set_dirty());
|
||||||
|
let mut usage = HashMap::new();
|
||||||
|
for (id, bundle) in try!(self.repo.get_bundle_map()) {
|
||||||
|
usage.insert(
|
||||||
|
id,
|
||||||
|
BundleAnalysis {
|
||||||
|
chunk_usage: Bitmap::new(bundle.chunk_count),
|
||||||
|
info: bundle.clone(),
|
||||||
|
used_raw_size: 0
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let backups = try!(self.get_all_backups());
|
||||||
|
let mut todo = VecDeque::new();
|
||||||
|
for (_name, backup) in backups {
|
||||||
|
todo.push_back(backup.root);
|
||||||
|
}
|
||||||
|
while let Some(chunks) = todo.pop_back() {
|
||||||
|
if !try!(self.mark_used(&mut usage, &chunks)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let inode = try!(self.get_inode(&chunks));
|
||||||
|
// Mark the content chunks as used
|
||||||
|
match inode.data {
|
||||||
|
None |
|
||||||
|
Some(FileData::Inline(_)) => (),
|
||||||
|
Some(FileData::ChunkedDirect(chunks)) => {
|
||||||
|
try!(self.mark_used(&mut usage, &chunks));
|
||||||
|
}
|
||||||
|
Some(FileData::ChunkedIndirect(chunks)) => {
|
||||||
|
if try!(self.mark_used(&mut usage, &chunks)) {
|
||||||
|
let chunk_data = try!(self.get_data(&chunks));
|
||||||
|
let chunks = ChunkList::read_from(&chunk_data);
|
||||||
|
try!(self.mark_used(&mut usage, &chunks));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Put children in to do
|
||||||
|
if let Some(children) = inode.children {
|
||||||
|
for (_name, chunks) in children {
|
||||||
|
todo.push_back(chunks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.repo.set_clean();
|
||||||
|
Ok(usage)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn vacuum(
|
||||||
|
&mut self,
|
||||||
|
ratio: f32,
|
||||||
|
combine: bool,
|
||||||
|
force: bool,
|
||||||
|
) -> Result<(), RepositoryError> {
|
||||||
|
try!(self.repo.flush());
|
||||||
|
tr_info!("Locking repository");
|
||||||
|
try!(self.repo.write_mode());
|
||||||
|
let _lock = try!(self.repo.lock(true));
|
||||||
|
// analyze_usage will set the dirty flag
|
||||||
|
tr_info!("Analyzing chunk usage");
|
||||||
|
let usage = try!(self.analyze_usage());
|
||||||
|
let mut data_total = 0;
|
||||||
|
let mut data_used = 0;
|
||||||
|
for bundle in usage.values() {
|
||||||
|
data_total += bundle.info.encoded_size;
|
||||||
|
data_used += bundle.get_used_size();
|
||||||
|
}
|
||||||
|
tr_info!(
|
||||||
|
"Usage: {} of {}, {:.1}%",
|
||||||
|
to_file_size(data_used as u64),
|
||||||
|
to_file_size(data_total as u64),
|
||||||
|
data_used as f32 / data_total as f32 * 100.0
|
||||||
|
);
|
||||||
|
let mut rewrite_bundles = HashSet::new();
|
||||||
|
let mut reclaim_space = 0;
|
||||||
|
let mut rewrite_data = 0;
|
||||||
|
for (id, bundle) in &usage {
|
||||||
|
if bundle.get_usage_ratio() <= ratio {
|
||||||
|
rewrite_bundles.insert(*id);
|
||||||
|
reclaim_space += bundle.get_unused_size();
|
||||||
|
rewrite_data += bundle.get_used_size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if combine {
|
||||||
|
let mut small_meta = vec![];
|
||||||
|
let mut small_data = vec![];
|
||||||
|
for (id, bundle) in &usage {
|
||||||
|
if bundle.info.encoded_size * 4 < self.repo.get_config().bundle_size {
|
||||||
|
match bundle.info.mode {
|
||||||
|
BundleMode::Meta => small_meta.push(*id),
|
||||||
|
BundleMode::Data => small_data.push(*id),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if small_meta.len() >= 2 {
|
||||||
|
for bundle in small_meta {
|
||||||
|
rewrite_bundles.insert(bundle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if small_data.len() >= 2 {
|
||||||
|
for bundle in small_data {
|
||||||
|
rewrite_bundles.insert(bundle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tr_info!(
|
||||||
|
"Reclaiming about {} by rewriting {} bundles ({})",
|
||||||
|
to_file_size(reclaim_space as u64),
|
||||||
|
rewrite_bundles.len(),
|
||||||
|
to_file_size(rewrite_data as u64)
|
||||||
|
);
|
||||||
|
if !force {
|
||||||
|
self.repo.set_clean();
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let rewrite_bundles: Vec<_> = rewrite_bundles.into_iter().collect();
|
||||||
|
try!(self.repo.rewrite_bundles(&rewrite_bundles, &usage));
|
||||||
|
self.repo.set_clean();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -123,7 +123,7 @@ fn open_repository(path: &Path, online: bool) -> Result<BackupRepository, ErrorC
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_backup(repo: &BackupRepository, backup_name: &str) -> Result<Backup, ErrorCode> {
|
fn get_backup(repo: &BackupRepository, backup_name: &str) -> Result<BackupFile, ErrorCode> {
|
||||||
if !repo.has_backup(backup_name) {
|
if !repo.has_backup(backup_name) {
|
||||||
tr_error!("A backup with that name does not exist");
|
tr_error!("A backup with that name does not exist");
|
||||||
return Err(ErrorCode::NoSuchBackup);
|
return Err(ErrorCode::NoSuchBackup);
|
||||||
|
@ -135,7 +135,7 @@ fn get_backup(repo: &BackupRepository, backup_name: &str) -> Result<Backup, Erro
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_inode(repo: &mut BackupRepository, backup: &Backup, inode: Option<&String>) -> Result<Inode, ErrorCode> {
|
fn get_inode(repo: &mut BackupRepository, backup: &BackupFile, inode: Option<&String>) -> Result<Inode, ErrorCode> {
|
||||||
Ok(if let Some(inode) = inode {
|
Ok(if let Some(inode) = inode {
|
||||||
checked!(
|
checked!(
|
||||||
repo.get_backup_inode(backup, &inode),
|
repo.get_backup_inode(backup, &inode),
|
||||||
|
@ -154,7 +154,7 @@ fn get_inode(repo: &mut BackupRepository, backup: &Backup, inode: Option<&String
|
||||||
fn find_reference_backup(
|
fn find_reference_backup(
|
||||||
repo: &BackupRepository,
|
repo: &BackupRepository,
|
||||||
path: &str,
|
path: &str,
|
||||||
) -> Result<Option<(String, Backup)>, ErrorCode> {
|
) -> Result<Option<(String, BackupFile)>, ErrorCode> {
|
||||||
let mut matching = Vec::new();
|
let mut matching = Vec::new();
|
||||||
let hostname = match get_hostname() {
|
let hostname = match get_hostname() {
|
||||||
Ok(hostname) => hostname,
|
Ok(hostname) => hostname,
|
||||||
|
@ -181,7 +181,7 @@ fn find_reference_backup(
|
||||||
Ok(matching.pop())
|
Ok(matching.pop())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_backup(backup: &Backup) {
|
fn print_backup(backup: &BackupFile) {
|
||||||
if backup.modified {
|
if backup.modified {
|
||||||
tr_warn!("This backup has been modified");
|
tr_warn!("This backup has been modified");
|
||||||
}
|
}
|
||||||
|
@ -299,7 +299,7 @@ fn print_inode(inode: &Inode) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_backups(backup_map: &HashMap<String, Backup>) {
|
fn print_backups(backup_map: &HashMap<String, BackupFile>) {
|
||||||
let mut backups: Vec<_> = backup_map.into_iter().collect();
|
let mut backups: Vec<_> = backup_map.into_iter().collect();
|
||||||
backups.sort_by_key(|b| b.0);
|
backups.sort_by_key(|b| b.0);
|
||||||
for (name, backup) in backups {
|
for (name, backup) in backups {
|
||||||
|
|
|
@ -2,12 +2,14 @@ pub use util::*;
|
||||||
pub use repository::bundledb::{BundleReader, BundleMode, BundleWriter, BundleInfo, BundleId, BundleDbError,
|
pub use repository::bundledb::{BundleReader, BundleMode, BundleWriter, BundleInfo, BundleId, BundleDbError,
|
||||||
BundleDb, BundleWriterError, StoredBundle, BundleStatistics};
|
BundleDb, BundleWriterError, StoredBundle, BundleStatistics};
|
||||||
pub use repository::chunking::{ChunkerType, Chunker, ChunkerStatus, ChunkerError};
|
pub use repository::chunking::{ChunkerType, Chunker, ChunkerStatus, ChunkerError};
|
||||||
pub use repository::{Repository, Backup, Config, RepositoryError, RepositoryInfo, Inode, FileType,
|
pub use repository::{Repository, Config, RepositoryError, RepositoryInfo,
|
||||||
IntegrityError, BackupFileError, BackupError, BackupOptions, BundleAnalysis,
|
IntegrityError, BundleAnalysis,
|
||||||
FileData, DiffType, InodeError, RepositoryLayout, Location,
|
RepositoryLayout, Location,
|
||||||
RepositoryStatistics, ChunkRepositoryLayout};
|
RepositoryStatistics, ChunkRepositoryLayout};
|
||||||
pub use repository::index::{Index, IndexError, IndexStatistics};
|
pub use repository::index::{Index, IndexError, IndexStatistics};
|
||||||
pub use backups::mount::FuseFilesystem;
|
pub use backups::mount::FuseFilesystem;
|
||||||
|
pub use backups::{BackupFile, BackupFileError, Inode, FileType, FileData, InodeError, BackupError,
|
||||||
|
BackupOptions, DiffType, InodeIntegrityError};
|
||||||
pub use translation::CowStr;
|
pub use translation::CowStr;
|
||||||
pub use backups::BackupRepository;
|
pub use backups::BackupRepository;
|
||||||
|
|
||||||
|
|
|
@ -3,11 +3,8 @@ use prelude::*;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use super::backup_file::BackupFileError;
|
|
||||||
use super::backup::BackupError;
|
|
||||||
use super::bundle_map::BundleMapError;
|
use super::bundle_map::BundleMapError;
|
||||||
use super::config::ConfigError;
|
use super::config::ConfigError;
|
||||||
use super::metadata::InodeError;
|
|
||||||
|
|
||||||
|
|
||||||
quick_error!{
|
quick_error!{
|
||||||
|
@ -72,6 +69,12 @@ quick_error!{
|
||||||
description(tr!("Bundle map error"))
|
description(tr!("Bundle map error"))
|
||||||
display("{}", tr_format!("Repository error: bundle map error\n\tcaused by: {}", err))
|
display("{}", tr_format!("Repository error: bundle map error\n\tcaused by: {}", err))
|
||||||
}
|
}
|
||||||
|
InodeIntegrity(err: InodeIntegrityError) {
|
||||||
|
from()
|
||||||
|
cause(err)
|
||||||
|
description(tr!("Integrity error"))
|
||||||
|
display("{}", tr_format!("Repository error: integrity error\n\tcaused by: {}", err))
|
||||||
|
}
|
||||||
Integrity(err: IntegrityError) {
|
Integrity(err: IntegrityError) {
|
||||||
from()
|
from()
|
||||||
cause(err)
|
cause(err)
|
||||||
|
@ -101,7 +104,7 @@ quick_error!{
|
||||||
description(tr!("IO error"))
|
description(tr!("IO error"))
|
||||||
display("{}", tr_format!("IO error: {}", err))
|
display("{}", tr_format!("IO error: {}", err))
|
||||||
}
|
}
|
||||||
NoSuchFileInBackup(backup: Backup, path: PathBuf) {
|
NoSuchFileInBackup(backup: BackupFile, path: PathBuf) {
|
||||||
description(tr!("No such file in backup"))
|
description(tr!("No such file in backup"))
|
||||||
display("{}", tr_format!("The backup does not contain the file {:?}", path))
|
display("{}", tr_format!("The backup does not contain the file {:?}", path))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use prelude::*;
|
use prelude::*;
|
||||||
|
|
||||||
use std::collections::{HashMap, VecDeque};
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
|
||||||
pub struct BundleAnalysis {
|
pub struct BundleAnalysis {
|
||||||
|
@ -47,91 +47,22 @@ pub struct RepositoryStatistics {
|
||||||
|
|
||||||
|
|
||||||
impl Repository {
|
impl Repository {
|
||||||
fn mark_used(
|
|
||||||
&self,
|
|
||||||
bundles: &mut HashMap<u32, BundleAnalysis>,
|
|
||||||
chunks: &[Chunk],
|
|
||||||
) -> Result<bool, RepositoryError> {
|
|
||||||
let mut new = false;
|
|
||||||
for &(hash, len) in chunks {
|
|
||||||
if let Some(pos) = self.index.get(&hash) {
|
|
||||||
let bundle = pos.bundle;
|
|
||||||
if let Some(bundle) = bundles.get_mut(&bundle) {
|
|
||||||
if !bundle.chunk_usage.get(pos.chunk as usize) {
|
|
||||||
new = true;
|
|
||||||
bundle.chunk_usage.set(pos.chunk as usize);
|
|
||||||
bundle.used_raw_size += len as usize;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(IntegrityError::MissingBundleId(pos.bundle).into());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(IntegrityError::MissingChunk(hash).into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(new)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn analyze_usage(&mut self) -> Result<HashMap<u32, BundleAnalysis>, RepositoryError> {
|
|
||||||
if self.dirty {
|
|
||||||
return Err(RepositoryError::Dirty);
|
|
||||||
}
|
|
||||||
try!(self.set_dirty());
|
|
||||||
let mut usage = HashMap::new();
|
|
||||||
for (id, bundle) in self.bundle_map.bundles() {
|
|
||||||
let bundle = try!(self.bundles.get_bundle_info(&bundle).ok_or_else(|| {
|
|
||||||
IntegrityError::MissingBundle(bundle)
|
|
||||||
}));
|
|
||||||
usage.insert(
|
|
||||||
id,
|
|
||||||
BundleAnalysis {
|
|
||||||
chunk_usage: Bitmap::new(bundle.info.chunk_count),
|
|
||||||
info: bundle.info.clone(),
|
|
||||||
used_raw_size: 0
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let backups = try!(self.get_all_backups());
|
|
||||||
let mut todo = VecDeque::new();
|
|
||||||
for (_name, backup) in backups {
|
|
||||||
todo.push_back(backup.root);
|
|
||||||
}
|
|
||||||
while let Some(chunks) = todo.pop_back() {
|
|
||||||
if !try!(self.mark_used(&mut usage, &chunks)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let inode = try!(self.get_inode(&chunks));
|
|
||||||
// Mark the content chunks as used
|
|
||||||
match inode.data {
|
|
||||||
None |
|
|
||||||
Some(FileData::Inline(_)) => (),
|
|
||||||
Some(FileData::ChunkedDirect(chunks)) => {
|
|
||||||
try!(self.mark_used(&mut usage, &chunks));
|
|
||||||
}
|
|
||||||
Some(FileData::ChunkedIndirect(chunks)) => {
|
|
||||||
if try!(self.mark_used(&mut usage, &chunks)) {
|
|
||||||
let chunk_data = try!(self.get_data(&chunks));
|
|
||||||
let chunks = ChunkList::read_from(&chunk_data);
|
|
||||||
try!(self.mark_used(&mut usage, &chunks));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Put children in to do
|
|
||||||
if let Some(children) = inode.children {
|
|
||||||
for (_name, chunks) in children {
|
|
||||||
todo.push_back(chunks);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.dirty = false;
|
|
||||||
Ok(usage)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn list_bundles(&self) -> Vec<&BundleInfo> {
|
pub fn list_bundles(&self) -> Vec<&BundleInfo> {
|
||||||
self.bundles.list_bundles()
|
self.bundles.list_bundles()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_bundle_map(&self) -> Result<HashMap<u32, &BundleInfo>, RepositoryError> {
|
||||||
|
let mut map = HashMap::with_capacity(self.bundle_map.len());
|
||||||
|
for (id, bundle_id) in self.bundle_map.bundles() {
|
||||||
|
let info = try!(self.bundles.get_bundle_info(&bundle_id).ok_or_else(|| {
|
||||||
|
IntegrityError::MissingBundle(bundle_id)
|
||||||
|
}));
|
||||||
|
map.insert(id, &info.info);
|
||||||
|
}
|
||||||
|
Ok(map)
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_bundle(&self, bundle: &BundleId) -> Option<&StoredBundle> {
|
pub fn get_bundle(&self, bundle: &BundleId) -> Option<&StoredBundle> {
|
||||||
self.bundles.get_bundle_info(bundle)
|
self.bundles.get_bundle_info(bundle)
|
||||||
|
|
|
@ -2,7 +2,6 @@ use prelude::*;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use pbr::ProgressBar;
|
use pbr::ProgressBar;
|
||||||
|
@ -33,20 +32,35 @@ quick_error!{
|
||||||
MapContainsDuplicates {
|
MapContainsDuplicates {
|
||||||
description(tr!("Map contains duplicates"))
|
description(tr!("Map contains duplicates"))
|
||||||
}
|
}
|
||||||
BrokenInode(path: PathBuf, err: Box<RepositoryError>) {
|
|
||||||
cause(err)
|
|
||||||
description(tr!("Broken inode"))
|
|
||||||
display("{}", tr_format!("Broken inode: {:?}\n\tcaused by: {}", path, err))
|
|
||||||
}
|
|
||||||
MissingInodeData(path: PathBuf, err: Box<RepositoryError>) {
|
|
||||||
cause(err)
|
|
||||||
description(tr!("Missing inode data"))
|
|
||||||
display("{}", tr_format!("Missing inode data in: {:?}\n\tcaused by: {}", path, err))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl Repository {
|
impl Repository {
|
||||||
|
pub fn get_chunk_marker(&self) -> Bitmap {
|
||||||
|
Bitmap::new(self.index.capacity())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_chunks(
|
||||||
|
&self,
|
||||||
|
checked: &mut Bitmap,
|
||||||
|
chunks: &[Chunk],
|
||||||
|
mark: bool,
|
||||||
|
) -> Result<bool, RepositoryError> {
|
||||||
|
let mut new = false;
|
||||||
|
for &(hash, _len) in chunks {
|
||||||
|
if let Some(pos) = self.index.pos(&hash) {
|
||||||
|
new |= !checked.get(pos);
|
||||||
|
if mark {
|
||||||
|
checked.set(pos);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(IntegrityError::MissingChunk(hash).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(new)
|
||||||
|
}
|
||||||
|
|
||||||
fn check_index_chunks(&self) -> Result<(), RepositoryError> {
|
fn check_index_chunks(&self) -> Result<(), RepositoryError> {
|
||||||
let mut progress = ProgressBar::new(self.index.len() as u64);
|
let mut progress = ProgressBar::new(self.index.len() as u64);
|
||||||
progress.message(tr!("checking index: "));
|
progress.message(tr!("checking index: "));
|
||||||
|
@ -76,354 +90,6 @@ impl Repository {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_chunks(
|
|
||||||
&self,
|
|
||||||
checked: &mut Bitmap,
|
|
||||||
chunks: &[Chunk],
|
|
||||||
mark: bool,
|
|
||||||
) -> Result<bool, RepositoryError> {
|
|
||||||
let mut new = false;
|
|
||||||
for &(hash, _len) in chunks {
|
|
||||||
if let Some(pos) = self.index.pos(&hash) {
|
|
||||||
new |= !checked.get(pos);
|
|
||||||
if mark {
|
|
||||||
checked.set(pos);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(IntegrityError::MissingChunk(hash).into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(new)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_inode_contents(
|
|
||||||
&mut self,
|
|
||||||
inode: &Inode,
|
|
||||||
checked: &mut Bitmap,
|
|
||||||
) -> Result<(), RepositoryError> {
|
|
||||||
match inode.data {
|
|
||||||
None |
|
|
||||||
Some(FileData::Inline(_)) => (),
|
|
||||||
Some(FileData::ChunkedDirect(ref chunks)) => {
|
|
||||||
try!(self.check_chunks(checked, chunks, true));
|
|
||||||
}
|
|
||||||
Some(FileData::ChunkedIndirect(ref chunks)) => {
|
|
||||||
if try!(self.check_chunks(checked, chunks, false)) {
|
|
||||||
let chunk_data = try!(self.get_data(chunks));
|
|
||||||
let chunks2 = ChunkList::read_from(&chunk_data);
|
|
||||||
try!(self.check_chunks(checked, &chunks2, true));
|
|
||||||
try!(self.check_chunks(checked, chunks, true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_subtree(
|
|
||||||
&mut self,
|
|
||||||
path: PathBuf,
|
|
||||||
chunks: &[Chunk],
|
|
||||||
checked: &mut Bitmap,
|
|
||||||
repair: bool,
|
|
||||||
) -> Result<Option<ChunkList>, RepositoryError> {
|
|
||||||
let mut modified = false;
|
|
||||||
match self.check_chunks(checked, chunks, false) {
|
|
||||||
Ok(false) => return Ok(None),
|
|
||||||
Ok(true) => (),
|
|
||||||
Err(err) => return Err(IntegrityError::BrokenInode(path, Box::new(err)).into()),
|
|
||||||
}
|
|
||||||
let mut inode = try!(self.get_inode(chunks));
|
|
||||||
// Mark the content chunks as used
|
|
||||||
if let Err(err) = self.check_inode_contents(&inode, checked) {
|
|
||||||
if repair {
|
|
||||||
tr_warn!(
|
|
||||||
"Problem detected: data of {:?} is corrupt\n\tcaused by: {}",
|
|
||||||
path,
|
|
||||||
err
|
|
||||||
);
|
|
||||||
tr_info!("Removing inode data");
|
|
||||||
inode.data = Some(FileData::Inline(vec![].into()));
|
|
||||||
inode.size = 0;
|
|
||||||
modified = true;
|
|
||||||
} else {
|
|
||||||
return Err(IntegrityError::MissingInodeData(path, Box::new(err)).into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Put children in to do
|
|
||||||
if let Some(ref mut children) = inode.children {
|
|
||||||
let mut removed = vec![];
|
|
||||||
for (name, chunks) in children.iter_mut() {
|
|
||||||
match self.check_subtree(path.join(name), chunks, checked, repair) {
|
|
||||||
Ok(None) => (),
|
|
||||||
Ok(Some(c)) => {
|
|
||||||
*chunks = c;
|
|
||||||
modified = true;
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
if repair {
|
|
||||||
tr_warn!(
|
|
||||||
"Problem detected: inode {:?} is corrupt\n\tcaused by: {}",
|
|
||||||
path.join(name),
|
|
||||||
err
|
|
||||||
);
|
|
||||||
tr_info!("Removing broken inode from backup");
|
|
||||||
removed.push(name.to_string());
|
|
||||||
modified = true;
|
|
||||||
} else {
|
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for name in removed {
|
|
||||||
children.remove(&name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if modified {
|
|
||||||
Ok(Some(try!(self.put_inode(&inode))))
|
|
||||||
} else {
|
|
||||||
try!(self.check_chunks(checked, chunks, true));
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn evacuate_broken_backup(&self, name: &str) -> Result<(), RepositoryError> {
|
|
||||||
tr_warn!(
|
|
||||||
"The backup {} was corrupted and needed to be modified.",
|
|
||||||
name
|
|
||||||
);
|
|
||||||
let src = self.layout.backup_path(name);
|
|
||||||
let mut dst = src.with_extension("backup.broken");
|
|
||||||
let mut num = 1;
|
|
||||||
while dst.exists() {
|
|
||||||
dst = src.with_extension(&format!("backup.{}.broken", num));
|
|
||||||
num += 1;
|
|
||||||
}
|
|
||||||
if fs::rename(&src, &dst).is_err() {
|
|
||||||
try!(fs::copy(&src, &dst));
|
|
||||||
try!(fs::remove_file(&src));
|
|
||||||
}
|
|
||||||
tr_info!("The original backup was renamed to {:?}", dst);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn check_backup(
|
|
||||||
&mut self,
|
|
||||||
name: &str,
|
|
||||||
backup: &mut Backup,
|
|
||||||
repair: bool,
|
|
||||||
) -> Result<(), RepositoryError> {
|
|
||||||
let _lock = if repair {
|
|
||||||
try!(self.write_mode());
|
|
||||||
Some(self.lock(false))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
tr_info!("Checking backup...");
|
|
||||||
let mut checked = Bitmap::new(self.index.capacity());
|
|
||||||
match self.check_subtree(
|
|
||||||
Path::new("").to_path_buf(),
|
|
||||||
&backup.root,
|
|
||||||
&mut checked,
|
|
||||||
repair
|
|
||||||
) {
|
|
||||||
Ok(None) => (),
|
|
||||||
Ok(Some(chunks)) => {
|
|
||||||
try!(self.flush());
|
|
||||||
backup.root = chunks;
|
|
||||||
backup.modified = true;
|
|
||||||
try!(self.evacuate_broken_backup(name));
|
|
||||||
try!(self.save_backup(backup, name));
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
if repair {
|
|
||||||
tr_warn!(
|
|
||||||
"The root of the backup {} has been corrupted\n\tcaused by: {}",
|
|
||||||
name,
|
|
||||||
err
|
|
||||||
);
|
|
||||||
try!(self.evacuate_broken_backup(name));
|
|
||||||
} else {
|
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn check_backup_inode(
|
|
||||||
&mut self,
|
|
||||||
name: &str,
|
|
||||||
backup: &mut Backup,
|
|
||||||
path: &Path,
|
|
||||||
repair: bool,
|
|
||||||
) -> Result<(), RepositoryError> {
|
|
||||||
let _lock = if repair {
|
|
||||||
try!(self.write_mode());
|
|
||||||
Some(self.lock(false))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
tr_info!("Checking inode...");
|
|
||||||
let mut checked = Bitmap::new(self.index.capacity());
|
|
||||||
let mut inodes = try!(self.get_backup_path(backup, path));
|
|
||||||
let mut inode = inodes.pop().unwrap();
|
|
||||||
let mut modified = false;
|
|
||||||
if let Err(err) = self.check_inode_contents(&inode, &mut checked) {
|
|
||||||
if repair {
|
|
||||||
tr_warn!(
|
|
||||||
"Problem detected: data of {:?} is corrupt\n\tcaused by: {}",
|
|
||||||
path,
|
|
||||||
err
|
|
||||||
);
|
|
||||||
tr_info!("Removing inode data");
|
|
||||||
inode.data = Some(FileData::Inline(vec![].into()));
|
|
||||||
inode.size = 0;
|
|
||||||
modified = true;
|
|
||||||
} else {
|
|
||||||
return Err(
|
|
||||||
IntegrityError::MissingInodeData(path.to_path_buf(), Box::new(err)).into()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(ref mut children) = inode.children {
|
|
||||||
let mut removed = vec![];
|
|
||||||
for (name, chunks) in children.iter_mut() {
|
|
||||||
match self.check_subtree(path.join(name), chunks, &mut checked, repair) {
|
|
||||||
Ok(None) => (),
|
|
||||||
Ok(Some(c)) => {
|
|
||||||
*chunks = c;
|
|
||||||
modified = true;
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
if repair {
|
|
||||||
tr_warn!(
|
|
||||||
"Problem detected: inode {:?} is corrupt\n\tcaused by: {}",
|
|
||||||
path.join(name),
|
|
||||||
err
|
|
||||||
);
|
|
||||||
tr_info!("Removing broken inode from backup");
|
|
||||||
removed.push(name.to_string());
|
|
||||||
modified = true;
|
|
||||||
} else {
|
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for name in removed {
|
|
||||||
children.remove(&name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let mut chunks = try!(self.put_inode(&inode));
|
|
||||||
while let Some(mut parent) = inodes.pop() {
|
|
||||||
parent.children.as_mut().unwrap().insert(inode.name, chunks);
|
|
||||||
inode = parent;
|
|
||||||
chunks = try!(self.put_inode(&inode));
|
|
||||||
}
|
|
||||||
if modified {
|
|
||||||
try!(self.flush());
|
|
||||||
backup.root = chunks;
|
|
||||||
backup.modified = true;
|
|
||||||
try!(self.evacuate_broken_backup(name));
|
|
||||||
try!(self.save_backup(backup, name));
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn check_backups(&mut self, repair: bool) -> Result<(), RepositoryError> {
|
|
||||||
let _lock = if repair {
|
|
||||||
try!(self.write_mode());
|
|
||||||
Some(self.lock(false))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
tr_info!("Checking backups...");
|
|
||||||
let mut checked = Bitmap::new(self.index.capacity());
|
|
||||||
let backup_map = match self.get_all_backups() {
|
|
||||||
Ok(backup_map) => backup_map,
|
|
||||||
Err(RepositoryError::BackupFile(BackupFileError::PartialBackupsList(backup_map,
|
|
||||||
_failed))) => {
|
|
||||||
tr_warn!("Some backups could not be read, ignoring them");
|
|
||||||
backup_map
|
|
||||||
}
|
|
||||||
Err(err) => return Err(err),
|
|
||||||
};
|
|
||||||
for (name, mut backup) in
|
|
||||||
ProgressIter::new(tr!("checking backups"), backup_map.len(), backup_map.into_iter())
|
|
||||||
{
|
|
||||||
let path = format!("{}::", name);
|
|
||||||
match self.check_subtree(
|
|
||||||
Path::new(&path).to_path_buf(),
|
|
||||||
&backup.root,
|
|
||||||
&mut checked,
|
|
||||||
repair
|
|
||||||
) {
|
|
||||||
Ok(None) => (),
|
|
||||||
Ok(Some(chunks)) => {
|
|
||||||
try!(self.flush());
|
|
||||||
backup.root = chunks;
|
|
||||||
backup.modified = true;
|
|
||||||
try!(self.evacuate_broken_backup(&name));
|
|
||||||
try!(self.save_backup(&backup, &name));
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
if repair {
|
|
||||||
tr_warn!(
|
|
||||||
"The root of the backup {} has been corrupted\n\tcaused by: {}",
|
|
||||||
name,
|
|
||||||
err
|
|
||||||
);
|
|
||||||
try!(self.evacuate_broken_backup(&name));
|
|
||||||
} else {
|
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn check_repository(&mut self, repair: bool) -> Result<(), RepositoryError> {
|
|
||||||
tr_info!("Checking repository integrity...");
|
|
||||||
let mut rebuild = false;
|
|
||||||
for (_id, bundle_id) in self.bundle_map.bundles() {
|
|
||||||
if self.bundles.get_bundle_info(&bundle_id).is_none() {
|
|
||||||
if repair {
|
|
||||||
tr_warn!(
|
|
||||||
"Problem detected: bundle map contains unknown bundle {}",
|
|
||||||
bundle_id
|
|
||||||
);
|
|
||||||
rebuild = true;
|
|
||||||
} else {
|
|
||||||
return Err(IntegrityError::MissingBundle(bundle_id).into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if self.bundle_map.len() < self.bundles.len() {
|
|
||||||
if repair {
|
|
||||||
tr_warn!("Problem detected: bundle map does not contain all remote bundles");
|
|
||||||
rebuild = true;
|
|
||||||
} else {
|
|
||||||
return Err(IntegrityError::RemoteBundlesNotInMap.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if self.bundle_map.len() > self.bundles.len() {
|
|
||||||
if repair {
|
|
||||||
tr_warn!("Problem detected: bundle map contains bundles multiple times");
|
|
||||||
rebuild = true;
|
|
||||||
} else {
|
|
||||||
return Err(IntegrityError::MapContainsDuplicates.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if rebuild {
|
|
||||||
try!(self.rebuild_bundle_map());
|
|
||||||
try!(self.rebuild_index());
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn rebuild_bundle_map(&mut self) -> Result<(), RepositoryError> {
|
pub fn rebuild_bundle_map(&mut self) -> Result<(), RepositoryError> {
|
||||||
tr_info!("Rebuilding bundle map from bundles");
|
tr_info!("Rebuilding bundle map from bundles");
|
||||||
self.bundle_map = BundleMap::create();
|
self.bundle_map = BundleMap::create();
|
||||||
|
@ -509,4 +175,44 @@ impl Repository {
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn check_repository(&mut self, repair: bool) -> Result<(), RepositoryError> {
|
||||||
|
tr_info!("Checking repository integrity...");
|
||||||
|
let mut rebuild = false;
|
||||||
|
for (_id, bundle_id) in self.bundle_map.bundles() {
|
||||||
|
if self.bundles.get_bundle_info(&bundle_id).is_none() {
|
||||||
|
if repair {
|
||||||
|
tr_warn!(
|
||||||
|
"Problem detected: bundle map contains unknown bundle {}",
|
||||||
|
bundle_id
|
||||||
|
);
|
||||||
|
rebuild = true;
|
||||||
|
} else {
|
||||||
|
return Err(IntegrityError::MissingBundle(bundle_id).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.bundle_map.len() < self.bundles.len() {
|
||||||
|
if repair {
|
||||||
|
tr_warn!("Problem detected: bundle map does not contain all remote bundles");
|
||||||
|
rebuild = true;
|
||||||
|
} else {
|
||||||
|
return Err(IntegrityError::RemoteBundlesNotInMap.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.bundle_map.len() > self.bundles.len() {
|
||||||
|
if repair {
|
||||||
|
tr_warn!("Problem detected: bundle map contains bundles multiple times");
|
||||||
|
rebuild = true;
|
||||||
|
} else {
|
||||||
|
return Err(IntegrityError::MapContainsDuplicates.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if rebuild {
|
||||||
|
try!(self.rebuild_bundle_map());
|
||||||
|
try!(self.rebuild_index());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,6 @@ pub trait ChunkRepositoryLayout {
|
||||||
|
|
||||||
|
|
||||||
fn config_path(&self) -> PathBuf;
|
fn config_path(&self) -> PathBuf;
|
||||||
fn keys_path(&self) -> PathBuf;
|
|
||||||
fn excludes_path(&self) -> PathBuf;
|
fn excludes_path(&self) -> PathBuf;
|
||||||
fn backups_path(&self) -> PathBuf;
|
fn backups_path(&self) -> PathBuf;
|
||||||
fn backup_path(&self, name: &str) -> PathBuf;
|
fn backup_path(&self, name: &str) -> PathBuf;
|
||||||
|
@ -182,11 +181,6 @@ impl ChunkRepositoryLayout for RepositoryLayout {
|
||||||
self.0.join("config.yaml")
|
self.0.join("config.yaml")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn keys_path(&self) -> PathBuf {
|
|
||||||
self.0.join("keys")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn excludes_path(&self) -> PathBuf {
|
fn excludes_path(&self) -> PathBuf {
|
||||||
self.0.join("excludes")
|
self.0.join("excludes")
|
||||||
|
|
|
@ -1,353 +1,8 @@
|
||||||
use prelude::*;
|
use prelude::*;
|
||||||
|
|
||||||
use filetime::{self, FileTime};
|
use std::path::Path;
|
||||||
use xattr;
|
use std::fs::File;
|
||||||
use libc;
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use std::fs::{self, File, Permissions};
|
|
||||||
use std::os::linux::fs::MetadataExt;
|
|
||||||
use std::os::unix::fs::{FileTypeExt, PermissionsExt, MetadataExt as UnixMetadataExt, symlink};
|
|
||||||
use std::io::{self, Read, Write};
|
|
||||||
use std::os::unix::ffi::OsStrExt;
|
|
||||||
use std::fmt;
|
|
||||||
use std::ffi;
|
|
||||||
|
|
||||||
|
|
||||||
quick_error!{
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum InodeError {
|
|
||||||
UnsupportedFiletype(path: PathBuf) {
|
|
||||||
description(tr!("Unsupported file type"))
|
|
||||||
display("{}", tr_format!("Inode error: file {:?} has an unsupported type", path))
|
|
||||||
}
|
|
||||||
ReadMetadata(err: io::Error, path: PathBuf) {
|
|
||||||
cause(err)
|
|
||||||
description(tr!("Failed to obtain metadata for file"))
|
|
||||||
display("{}", tr_format!("Inode error: failed to obtain metadata for file {:?}\n\tcaused by: {}", path, err))
|
|
||||||
}
|
|
||||||
ReadXattr(err: io::Error, path: PathBuf) {
|
|
||||||
cause(err)
|
|
||||||
description(tr!("Failed to obtain xattr for file"))
|
|
||||||
display("{}", tr_format!("Inode error: failed to obtain xattr for file {:?}\n\tcaused by: {}", path, err))
|
|
||||||
}
|
|
||||||
ReadLinkTarget(err: io::Error, path: PathBuf) {
|
|
||||||
cause(err)
|
|
||||||
description(tr!("Failed to obtain link target for file"))
|
|
||||||
display("{}", tr_format!("Inode error: failed to obtain link target for file {:?}\n\tcaused by: {}", path, err))
|
|
||||||
}
|
|
||||||
Create(err: io::Error, path: PathBuf) {
|
|
||||||
cause(err)
|
|
||||||
description(tr!("Failed to create entity"))
|
|
||||||
display("{}", tr_format!("Inode error: failed to create entity {:?}\n\tcaused by: {}", path, err))
|
|
||||||
}
|
|
||||||
Integrity(reason: &'static str) {
|
|
||||||
description(tr!("Integrity error"))
|
|
||||||
display("{}", tr_format!("Inode error: inode integrity error: {}", reason))
|
|
||||||
}
|
|
||||||
Decode(err: msgpack::DecodeError) {
|
|
||||||
from()
|
|
||||||
cause(err)
|
|
||||||
description(tr!("Failed to decode metadata"))
|
|
||||||
display("{}", tr_format!("Inode error: failed to decode metadata\n\tcaused by: {}", err))
|
|
||||||
}
|
|
||||||
Encode(err: msgpack::EncodeError) {
|
|
||||||
from()
|
|
||||||
cause(err)
|
|
||||||
description(tr!("Failed to encode metadata"))
|
|
||||||
display("{}", tr_format!("Inode error: failed to encode metadata\n\tcaused by: {}", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)]
|
|
||||||
pub enum FileType {
|
|
||||||
File,
|
|
||||||
Directory,
|
|
||||||
Symlink,
|
|
||||||
BlockDevice,
|
|
||||||
CharDevice,
|
|
||||||
NamedPipe
|
|
||||||
}
|
|
||||||
serde_impl!(FileType(u8) {
|
|
||||||
File => 0,
|
|
||||||
Directory => 1,
|
|
||||||
Symlink => 2,
|
|
||||||
BlockDevice => 3,
|
|
||||||
CharDevice => 4,
|
|
||||||
NamedPipe => 5
|
|
||||||
});
|
|
||||||
impl fmt::Display for FileType {
|
|
||||||
fn fmt(&self, format: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
|
||||||
match *self {
|
|
||||||
FileType::File => write!(format, "{}", tr!("file")),
|
|
||||||
FileType::Directory => write!(format, "{}", tr!("directory")),
|
|
||||||
FileType::Symlink => write!(format, "{}", tr!("symlink")),
|
|
||||||
FileType::BlockDevice => write!(format, "{}", tr!("block device")),
|
|
||||||
FileType::CharDevice => write!(format, "{}", tr!("char device")),
|
|
||||||
FileType::NamedPipe => write!(format, "{}", tr!("named pipe")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
|
||||||
pub enum FileData {
|
|
||||||
Inline(msgpack::Bytes),
|
|
||||||
ChunkedDirect(ChunkList),
|
|
||||||
ChunkedIndirect(ChunkList)
|
|
||||||
}
|
|
||||||
serde_impl!(FileData(u8) {
|
|
||||||
Inline(ByteBuf) => 0,
|
|
||||||
ChunkedDirect(ChunkList) => 1,
|
|
||||||
ChunkedIndirect(ChunkList) => 2
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Hash, Eq, PartialEq)]
|
|
||||||
pub struct Inode {
|
|
||||||
pub name: String,
|
|
||||||
pub size: u64,
|
|
||||||
pub file_type: FileType,
|
|
||||||
pub mode: u32,
|
|
||||||
pub user: u32,
|
|
||||||
pub group: u32,
|
|
||||||
pub timestamp: i64,
|
|
||||||
pub symlink_target: Option<String>,
|
|
||||||
pub data: Option<FileData>,
|
|
||||||
pub children: Option<BTreeMap<String, ChunkList>>,
|
|
||||||
pub cum_size: u64,
|
|
||||||
pub cum_dirs: usize,
|
|
||||||
pub cum_files: usize,
|
|
||||||
pub xattrs: BTreeMap<String, msgpack::Bytes>,
|
|
||||||
pub device: Option<(u32, u32)>
|
|
||||||
}
|
|
||||||
impl Default for Inode {
|
|
||||||
fn default() -> Self {
|
|
||||||
Inode {
|
|
||||||
name: "".to_string(),
|
|
||||||
size: 0,
|
|
||||||
file_type: FileType::File,
|
|
||||||
mode: 0o644,
|
|
||||||
user: 1000,
|
|
||||||
group: 1000,
|
|
||||||
timestamp: 0,
|
|
||||||
symlink_target: None,
|
|
||||||
data: None,
|
|
||||||
children: None,
|
|
||||||
cum_size: 0,
|
|
||||||
cum_dirs: 0,
|
|
||||||
cum_files: 0,
|
|
||||||
xattrs: BTreeMap::new(),
|
|
||||||
device: None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
serde_impl!(Inode(u8?) {
|
|
||||||
name: String => 0,
|
|
||||||
size: u64 => 1,
|
|
||||||
file_type: FileType => 2,
|
|
||||||
mode: u32 => 3,
|
|
||||||
user: u32 => 4,
|
|
||||||
group: u32 => 5,
|
|
||||||
timestamp: i64 => 7,
|
|
||||||
symlink_target: Option<String> => 9,
|
|
||||||
data: Option<FileData> => 10,
|
|
||||||
children: Option<BTreeMap<String, ChunkList>> => 11,
|
|
||||||
cum_size: u64 => 12,
|
|
||||||
cum_dirs: usize => 13,
|
|
||||||
cum_files: usize => 14,
|
|
||||||
xattrs: BTreeMap<String, msgpack::Bytes> => 15,
|
|
||||||
device: Option<(u32, u32)> => 16
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
impl Inode {
|
|
||||||
pub fn get_from<P: AsRef<Path>>(path: P) -> Result<Self, InodeError> {
|
|
||||||
let path = path.as_ref();
|
|
||||||
let name = path.file_name()
|
|
||||||
.map(|s| s.to_string_lossy().to_string())
|
|
||||||
.unwrap_or_else(|| "_".to_string());
|
|
||||||
let meta = try!(fs::symlink_metadata(path).map_err(|e| {
|
|
||||||
InodeError::ReadMetadata(e, path.to_owned())
|
|
||||||
}));
|
|
||||||
let mut inode = Inode::default();
|
|
||||||
inode.name = name;
|
|
||||||
if meta.is_file() {
|
|
||||||
inode.size = meta.len();
|
|
||||||
}
|
|
||||||
inode.file_type = if meta.is_file() {
|
|
||||||
FileType::File
|
|
||||||
} else if meta.is_dir() {
|
|
||||||
FileType::Directory
|
|
||||||
} else if meta.file_type().is_symlink() {
|
|
||||||
FileType::Symlink
|
|
||||||
} else if meta.file_type().is_block_device() {
|
|
||||||
FileType::BlockDevice
|
|
||||||
} else if meta.file_type().is_char_device() {
|
|
||||||
FileType::CharDevice
|
|
||||||
} else if meta.file_type().is_fifo() {
|
|
||||||
FileType::NamedPipe
|
|
||||||
} else {
|
|
||||||
return Err(InodeError::UnsupportedFiletype(path.to_owned()));
|
|
||||||
};
|
|
||||||
if meta.file_type().is_symlink() {
|
|
||||||
inode.symlink_target = Some(
|
|
||||||
try!(fs::read_link(path).map_err(|e| {
|
|
||||||
InodeError::ReadLinkTarget(e, path.to_owned())
|
|
||||||
})).to_string_lossy()
|
|
||||||
.to_string()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if meta.file_type().is_block_device() || meta.file_type().is_char_device() {
|
|
||||||
let rdev = meta.rdev();
|
|
||||||
let major = (rdev >> 8) as u32;
|
|
||||||
let minor = (rdev & 0xff) as u32;
|
|
||||||
inode.device = Some((major, minor));
|
|
||||||
}
|
|
||||||
inode.mode = meta.permissions().mode();
|
|
||||||
inode.user = meta.st_uid();
|
|
||||||
inode.group = meta.st_gid();
|
|
||||||
inode.timestamp = meta.st_mtime();
|
|
||||||
if xattr::SUPPORTED_PLATFORM {
|
|
||||||
if let Ok(attrs) = xattr::list(path) {
|
|
||||||
for name in attrs {
|
|
||||||
if let Some(data) = try!(xattr::get(path, &name).map_err(|e| {
|
|
||||||
InodeError::ReadXattr(e, path.to_owned())
|
|
||||||
}))
|
|
||||||
{
|
|
||||||
inode.xattrs.insert(
|
|
||||||
name.to_string_lossy().to_string(),
|
|
||||||
data.into()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(inode)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_at<P: AsRef<Path>>(&self, path: P) -> Result<Option<File>, InodeError> {
|
|
||||||
let full_path = path.as_ref().join(&self.name);
|
|
||||||
let mut file = None;
|
|
||||||
match self.file_type {
|
|
||||||
FileType::File => {
|
|
||||||
file = Some(try!(File::create(&full_path).map_err(|e| {
|
|
||||||
InodeError::Create(e, full_path.clone())
|
|
||||||
})));
|
|
||||||
}
|
|
||||||
FileType::Directory => {
|
|
||||||
try!(fs::create_dir(&full_path).map_err(|e| {
|
|
||||||
InodeError::Create(e, full_path.clone())
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
FileType::Symlink => {
|
|
||||||
if let Some(ref src) = self.symlink_target {
|
|
||||||
try!(symlink(src, &full_path).map_err(|e| {
|
|
||||||
InodeError::Create(e, full_path.clone())
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
return Err(InodeError::Integrity(tr!("Symlink without target")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
FileType::NamedPipe => {
|
|
||||||
let name = try!(
|
|
||||||
ffi::CString::new(full_path.as_os_str().as_bytes())
|
|
||||||
.map_err(|_| InodeError::Integrity(tr!("Name contains nulls")))
|
|
||||||
);
|
|
||||||
let mode = self.mode | libc::S_IFIFO;
|
|
||||||
if unsafe { libc::mkfifo(name.as_ptr(), mode) } != 0 {
|
|
||||||
return Err(InodeError::Create(
|
|
||||||
io::Error::last_os_error(),
|
|
||||||
full_path.clone()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
FileType::BlockDevice | FileType::CharDevice => {
|
|
||||||
let name = try!(
|
|
||||||
ffi::CString::new(full_path.as_os_str().as_bytes())
|
|
||||||
.map_err(|_| InodeError::Integrity(tr!("Name contains nulls")))
|
|
||||||
);
|
|
||||||
let mode = self.mode |
|
|
||||||
match self.file_type {
|
|
||||||
FileType::BlockDevice => libc::S_IFBLK,
|
|
||||||
FileType::CharDevice => libc::S_IFCHR,
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
let device = if let Some((major, minor)) = self.device {
|
|
||||||
unsafe { libc::makedev(major, minor) }
|
|
||||||
} else {
|
|
||||||
return Err(InodeError::Integrity(tr!("Device without id")));
|
|
||||||
};
|
|
||||||
if unsafe { libc::mknod(name.as_ptr(), mode, device) } != 0 {
|
|
||||||
return Err(InodeError::Create(
|
|
||||||
io::Error::last_os_error(),
|
|
||||||
full_path.clone()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let time = FileTime::from_seconds_since_1970(self.timestamp as u64, 0);
|
|
||||||
if let Err(err) = filetime::set_file_times(&full_path, time, time) {
|
|
||||||
tr_warn!("Failed to set file time on {:?}: {}", full_path, err);
|
|
||||||
}
|
|
||||||
if !self.xattrs.is_empty() {
|
|
||||||
if xattr::SUPPORTED_PLATFORM {
|
|
||||||
for (name, data) in &self.xattrs {
|
|
||||||
if let Err(err) = xattr::set(&full_path, name, data) {
|
|
||||||
tr_warn!("Failed to set xattr {} on {:?}: {}", name, full_path, err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tr_warn!("Not setting xattr on {:?}", full_path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Err(err) = fs::set_permissions(&full_path, Permissions::from_mode(self.mode)) {
|
|
||||||
tr_warn!(
|
|
||||||
"Failed to set permissions {:o} on {:?}: {}",
|
|
||||||
self.mode,
|
|
||||||
full_path,
|
|
||||||
err
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if let Err(err) = chown(&full_path, self.user, self.group) {
|
|
||||||
tr_warn!(
|
|
||||||
"Failed to set user {} and group {} on {:?}: {}",
|
|
||||||
self.user,
|
|
||||||
self.group,
|
|
||||||
full_path,
|
|
||||||
err
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Ok(file)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn is_same_meta(&self, other: &Inode) -> bool {
|
|
||||||
self.file_type == other.file_type && self.size == other.size &&
|
|
||||||
self.mode == other.mode && self.user == other.user &&
|
|
||||||
self.group == other.group && self.name == other.name &&
|
|
||||||
self.timestamp == other.timestamp && self.symlink_target == other.symlink_target
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn is_same_meta_quick(&self, other: &Inode) -> bool {
|
|
||||||
self.timestamp == other.timestamp && self.file_type == other.file_type &&
|
|
||||||
self.size == other.size
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn encode(&self) -> Result<Vec<u8>, InodeError> {
|
|
||||||
Ok(try!(msgpack::encode(&self)))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn decode(data: &[u8]) -> Result<Self, InodeError> {
|
|
||||||
Ok(try!(msgpack::decode(data)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl Repository {
|
impl Repository {
|
||||||
|
|
|
@ -4,11 +4,8 @@ mod integrity;
|
||||||
mod basic_io;
|
mod basic_io;
|
||||||
mod info;
|
mod info;
|
||||||
mod metadata;
|
mod metadata;
|
||||||
mod backup;
|
|
||||||
mod error;
|
mod error;
|
||||||
mod vacuum;
|
mod vacuum;
|
||||||
mod backup_file;
|
|
||||||
mod tarfile;
|
|
||||||
mod layout;
|
mod layout;
|
||||||
pub mod bundledb;
|
pub mod bundledb;
|
||||||
pub mod index;
|
pub mod index;
|
||||||
|
@ -26,9 +23,6 @@ use std::io::Write;
|
||||||
|
|
||||||
pub use self::error::RepositoryError;
|
pub use self::error::RepositoryError;
|
||||||
pub use self::config::Config;
|
pub use self::config::Config;
|
||||||
pub use self::metadata::{Inode, FileType, FileData, InodeError};
|
|
||||||
pub use self::backup::{BackupError, BackupOptions, DiffType};
|
|
||||||
pub use self::backup_file::{Backup, BackupFileError};
|
|
||||||
pub use self::integrity::IntegrityError;
|
pub use self::integrity::IntegrityError;
|
||||||
pub use self::info::{RepositoryInfo, BundleAnalysis, RepositoryStatistics};
|
pub use self::info::{RepositoryInfo, BundleAnalysis, RepositoryStatistics};
|
||||||
pub use self::layout::{RepositoryLayout, ChunkRepositoryLayout};
|
pub use self::layout::{RepositoryLayout, ChunkRepositoryLayout};
|
||||||
|
@ -36,7 +30,6 @@ use self::bundle_map::BundleMap;
|
||||||
|
|
||||||
|
|
||||||
const REPOSITORY_README: &[u8] = include_bytes!("../../docs/repository_readme.md");
|
const REPOSITORY_README: &[u8] = include_bytes!("../../docs/repository_readme.md");
|
||||||
const DEFAULT_EXCLUDES: &[u8] = include_bytes!("../../docs/excludes.default");
|
|
||||||
|
|
||||||
const INDEX_MAGIC: [u8; 7] = *b"zvault\x02";
|
const INDEX_MAGIC: [u8; 7] = *b"zvault\x02";
|
||||||
const INDEX_VERSION: u8 = 1;
|
const INDEX_VERSION: u8 = 1;
|
||||||
|
@ -96,13 +89,9 @@ impl Repository {
|
||||||
pub fn create<R: AsRef<Path>>(
|
pub fn create<R: AsRef<Path>>(
|
||||||
layout: Arc<ChunkRepositoryLayout>,
|
layout: Arc<ChunkRepositoryLayout>,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
|
crypto: Arc<Crypto>,
|
||||||
remote: R,
|
remote: R,
|
||||||
) -> Result<Self, RepositoryError> {
|
) -> Result<Self, RepositoryError> {
|
||||||
try!(fs::create_dir(layout.base_path()));
|
|
||||||
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.local_locks_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(
|
try!(File::create(layout.remote_readme_path()).and_then(
|
||||||
|
@ -120,11 +109,11 @@ impl Repository {
|
||||||
));
|
));
|
||||||
try!(BundleMap::create().save(layout.bundle_map_path()));
|
try!(BundleMap::create().save(layout.bundle_map_path()));
|
||||||
try!(fs::create_dir_all(layout.backups_path()));
|
try!(fs::create_dir_all(layout.backups_path()));
|
||||||
Self::open(layout, true)
|
Self::open(layout, crypto, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unknown_lints, useless_let_if_seq)]
|
#[allow(unknown_lints, useless_let_if_seq)]
|
||||||
pub fn open(layout: Arc<ChunkRepositoryLayout>, online: bool) -> Result<Self, RepositoryError> {
|
pub fn open(layout: Arc<ChunkRepositoryLayout>, crypto: Arc<Crypto>, online: bool) -> Result<Self, RepositoryError> {
|
||||||
if !layout.remote_exists() {
|
if !layout.remote_exists() {
|
||||||
return Err(RepositoryError::NoRemote);
|
return Err(RepositoryError::NoRemote);
|
||||||
}
|
}
|
||||||
|
@ -133,7 +122,6 @@ impl Repository {
|
||||||
try!(fs::create_dir_all(layout.local_locks_path())); // Added after v0.1.0
|
try!(fs::create_dir_all(layout.local_locks_path())); // Added after v0.1.0
|
||||||
let local_locks = LockFolder::new(layout.local_locks_path());
|
let local_locks = LockFolder::new(layout.local_locks_path());
|
||||||
let lock = try!(local_locks.lock(false));
|
let lock = try!(local_locks.lock(false));
|
||||||
let crypto = Arc::new(try!(Crypto::open(layout.keys_path())));
|
|
||||||
let (bundles, new, gone) = try!(BundleDb::open(layout.clone(), crypto.clone(), online));
|
let (bundles, new, gone) = try!(BundleDb::open(layout.clone(), crypto.clone(), online));
|
||||||
let (index, mut rebuild_index) =
|
let (index, mut rebuild_index) =
|
||||||
match unsafe { Index::open(layout.index_path(), &INDEX_MAGIC, INDEX_VERSION) } {
|
match unsafe { Index::open(layout.index_path(), &INDEX_MAGIC, INDEX_VERSION) } {
|
||||||
|
@ -218,44 +206,6 @@ impl Repository {
|
||||||
Ok(repo)
|
Ok(repo)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn import<R: AsRef<Path>>(
|
|
||||||
layout: Arc<ChunkRepositoryLayout>,
|
|
||||||
remote: R,
|
|
||||||
key_files: Vec<String>,
|
|
||||||
) -> Result<Self, RepositoryError> {
|
|
||||||
let mut repo = try!(Repository::create(layout.clone(), &Config::default(), remote));
|
|
||||||
for file in key_files {
|
|
||||||
try!(repo.crypto.register_keyfile(file));
|
|
||||||
}
|
|
||||||
repo = try!(Repository::open(layout, true));
|
|
||||||
let mut backups: Vec<(String, Backup)> = try!(repo.get_all_backups()).into_iter().collect();
|
|
||||||
backups.sort_by_key(|&(_, ref b)| b.timestamp);
|
|
||||||
if let Some((name, backup)) = backups.pop() {
|
|
||||||
tr_info!("Taking configuration from the last backup '{}'", name);
|
|
||||||
repo.config = backup.config;
|
|
||||||
try!(repo.save_config())
|
|
||||||
} else {
|
|
||||||
tr_warn!(
|
|
||||||
"No backup found in the repository to take configuration from, please set the configuration manually."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Ok(repo)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn register_key(
|
|
||||||
&mut self,
|
|
||||||
public: PublicKey,
|
|
||||||
secret: SecretKey,
|
|
||||||
) -> Result<(), RepositoryError> {
|
|
||||||
try!(self.write_mode());
|
|
||||||
try!(self.crypto.register_secret_key(
|
|
||||||
public,
|
|
||||||
secret
|
|
||||||
));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn save_config(&mut self) -> Result<(), RepositoryError> {
|
pub fn save_config(&mut self) -> Result<(), RepositoryError> {
|
||||||
try!(self.write_mode());
|
try!(self.write_mode());
|
||||||
|
@ -278,7 +228,7 @@ impl Repository {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn save_bundle_map(&self) -> Result<(), RepositoryError> {
|
pub fn save_bundle_map(&self) -> Result<(), RepositoryError> {
|
||||||
try!(self.bundle_map.save(self.layout.bundle_map_path()));
|
try!(self.bundle_map.save(self.layout.bundle_map_path()));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -385,16 +335,24 @@ impl Repository {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn write_mode(&mut self) -> Result<(), RepositoryError> {
|
pub fn write_mode(&mut self) -> Result<(), RepositoryError> {
|
||||||
try!(self.local_locks.upgrade(&mut self.lock));
|
try!(self.local_locks.upgrade(&mut self.lock));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn lock(&self, exclusive: bool) -> Result<LockHandle, RepositoryError> {
|
pub fn lock(&self, exclusive: bool) -> Result<LockHandle, RepositoryError> {
|
||||||
Ok(try!(self.remote_locks.lock(exclusive)))
|
Ok(try!(self.remote_locks.lock(exclusive)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_dirty(&self) -> bool {
|
||||||
|
self.dirty
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_chunk_location(&self, chunk: Hash) -> Option<Location> {
|
||||||
|
self.index.get(&chunk)
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_clean(&mut self) {
|
pub fn set_clean(&mut self) {
|
||||||
self.dirty = false;
|
self.dirty = false;
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use prelude::*;
|
use prelude::*;
|
||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
|
||||||
impl Repository {
|
impl Repository {
|
||||||
fn delete_bundle(&mut self, id: u32) -> Result<(), RepositoryError> {
|
pub fn delete_bundle(&mut self, id: u32) -> Result<(), RepositoryError> {
|
||||||
if let Some(bundle) = self.bundle_map.remove(id) {
|
if let Some(bundle) = self.bundle_map.remove(id) {
|
||||||
try!(self.bundles.delete_bundle(&bundle));
|
try!(self.bundles.delete_bundle(&bundle));
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -13,92 +13,26 @@ impl Repository {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn vacuum(
|
pub fn rewrite_bundles(&mut self, rewrite_bundles: &[u32], usage: &HashMap<u32, BundleAnalysis>) -> Result<(), RepositoryError> {
|
||||||
&mut self,
|
for &id in ProgressIter::new(
|
||||||
ratio: f32,
|
|
||||||
combine: bool,
|
|
||||||
force: bool,
|
|
||||||
) -> Result<(), RepositoryError> {
|
|
||||||
try!(self.flush());
|
|
||||||
tr_info!("Locking repository");
|
|
||||||
try!(self.write_mode());
|
|
||||||
let _lock = try!(self.lock(true));
|
|
||||||
// analyze_usage will set the dirty flag
|
|
||||||
tr_info!("Analyzing chunk usage");
|
|
||||||
let usage = try!(self.analyze_usage());
|
|
||||||
let mut data_total = 0;
|
|
||||||
let mut data_used = 0;
|
|
||||||
for bundle in usage.values() {
|
|
||||||
data_total += bundle.info.encoded_size;
|
|
||||||
data_used += bundle.get_used_size();
|
|
||||||
}
|
|
||||||
tr_info!(
|
|
||||||
"Usage: {} of {}, {:.1}%",
|
|
||||||
to_file_size(data_used as u64),
|
|
||||||
to_file_size(data_total as u64),
|
|
||||||
data_used as f32 / data_total as f32 * 100.0
|
|
||||||
);
|
|
||||||
let mut rewrite_bundles = HashSet::new();
|
|
||||||
let mut reclaim_space = 0;
|
|
||||||
let mut rewrite_data = 0;
|
|
||||||
for (id, bundle) in &usage {
|
|
||||||
if bundle.get_usage_ratio() <= ratio {
|
|
||||||
rewrite_bundles.insert(*id);
|
|
||||||
reclaim_space += bundle.get_unused_size();
|
|
||||||
rewrite_data += bundle.get_used_size();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if combine {
|
|
||||||
let mut small_meta = vec![];
|
|
||||||
let mut small_data = vec![];
|
|
||||||
for (id, bundle) in &usage {
|
|
||||||
if bundle.info.encoded_size * 4 < self.config.bundle_size {
|
|
||||||
match bundle.info.mode {
|
|
||||||
BundleMode::Meta => small_meta.push(*id),
|
|
||||||
BundleMode::Data => small_data.push(*id),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if small_meta.len() >= 2 {
|
|
||||||
for bundle in small_meta {
|
|
||||||
rewrite_bundles.insert(bundle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if small_data.len() >= 2 {
|
|
||||||
for bundle in small_data {
|
|
||||||
rewrite_bundles.insert(bundle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tr_info!(
|
|
||||||
"Reclaiming about {} by rewriting {} bundles ({})",
|
|
||||||
to_file_size(reclaim_space as u64),
|
|
||||||
rewrite_bundles.len(),
|
|
||||||
to_file_size(rewrite_data as u64)
|
|
||||||
);
|
|
||||||
if !force {
|
|
||||||
self.dirty = false;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
for id in ProgressIter::new(
|
|
||||||
tr!("rewriting bundles"),
|
tr!("rewriting bundles"),
|
||||||
rewrite_bundles.len(),
|
rewrite_bundles.len(),
|
||||||
rewrite_bundles.iter()
|
rewrite_bundles.iter()
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
let bundle = &usage[id];
|
let bundle = &usage[&id];
|
||||||
let bundle_id = self.bundle_map.get(*id).unwrap();
|
let bundle_id = self.bundle_map.get(id).unwrap();
|
||||||
let chunks = try!(self.bundles.get_chunk_list(&bundle_id));
|
let chunks = try!(self.bundles.get_chunk_list(&bundle_id));
|
||||||
let mode = usage[id].info.mode;
|
let mode = usage[&id].info.mode;
|
||||||
for (chunk, &(hash, _len)) in chunks.into_iter().enumerate() {
|
for (chunk, &(hash, _len)) in chunks.into_iter().enumerate() {
|
||||||
if !bundle.chunk_usage.get(chunk) {
|
if !bundle.chunk_usage.get(chunk) {
|
||||||
try!(self.index.delete(&hash));
|
try!(self.index.delete(&hash));
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
let data = try!(self.bundles.get_chunk(&bundle_id, chunk));
|
||||||
|
try!(self.put_chunk_override(mode, hash, &data));
|
||||||
}
|
}
|
||||||
let data = try!(self.bundles.get_chunk(&bundle_id, chunk));
|
|
||||||
try!(self.put_chunk_override(mode, hash, &data));
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
try!(self.flush());
|
try!(self.flush());
|
||||||
tr_info!("Checking index");
|
tr_info!("Checking index");
|
||||||
for (hash, location) in self.index.iter() {
|
for (hash, location) in self.index.iter() {
|
||||||
|
@ -114,11 +48,10 @@ impl Repository {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tr_info!("Deleting {} bundles", rewrite_bundles.len());
|
tr_info!("Deleting {} bundles", rewrite_bundles.len());
|
||||||
for id in rewrite_bundles {
|
for &id in rewrite_bundles {
|
||||||
try!(self.delete_bundle(id));
|
try!(self.delete_bundle(id));
|
||||||
}
|
}
|
||||||
try!(self.save_bundle_map());
|
try!(self.save_bundle_map());
|
||||||
self.dirty = false;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue