use ::prelude::*; use std::fs; use std::path::{self, Path, PathBuf}; use std::collections::{HashMap, BTreeMap, VecDeque}; use std::os::linux::fs::MetadataExt; use chrono::prelude::*; use regex::RegexSet; quick_error!{ #[derive(Debug)] #[allow(unknown_lints,large_enum_variant)] pub enum BackupError { FailedPaths(backup: Backup, failed: Vec) { description("Some paths could not be backed up") display("Backup error: some paths could not be backed up") } RemoveRoot { description("The root of a backup can not be removed") display("Backup error: the root of a backup can not be removed") } } } pub struct BackupOptions { pub same_device: bool, pub excludes: Option } impl Repository { pub fn get_backups(&self) -> Result, RepositoryError> { Ok(try!(Backup::get_all_from(&self.crypto.lock().unwrap(), &self.backups_path))) } pub fn get_backup(&self, name: &str) -> Result { Ok(try!(Backup::read_from(&self.crypto.lock().unwrap(), self.backups_path.join(name)))) } pub fn save_backup(&mut self, backup: &Backup, name: &str) -> Result<(), RepositoryError> { let path = &self.backups_path.join(name); try!(fs::create_dir_all(path.parent().unwrap())); Ok(try!(backup.save_to(&self.crypto.lock().unwrap(), self.config.encryption.clone(), path))) } pub fn delete_backup(&self, name: &str) -> Result<(), RepositoryError> { let mut path = self.backups_path.join(name); try!(fs::remove_file(&path)); loop { path = path.parent().unwrap().to_owned(); if fs::remove_dir(&path).is_err() { break } } Ok(()) } pub fn prune_backups(&self, prefix: &str, daily: Option, weekly: Option, monthly: Option, yearly: Option, force: bool) -> Result<(), RepositoryError> { let mut backups = Vec::new(); let backup_map = match self.get_backups() { Ok(backup_map) => backup_map, Err(RepositoryError::BackupFile(BackupFileError::PartialBackupsList(backup_map, _failed))) => { warn!("Some backups could not be read, ignoring them"); backup_map }, Err(err) => return Err(err) }; for (name, backup) in backup_map { if name.starts_with(prefix) { let date = Local.timestamp(backup.date, 0); backups.push((name, date, backup)); } } backups.sort_by_key(|backup| backup.2.date); let mut keep = Bitmap::new(backups.len()); fn mark_needed) -> K>(backups: &[(String, DateTime, Backup)], keep: &mut Bitmap, max: usize, keyfn: F) { let mut unique = VecDeque::with_capacity(max+1); let mut last = None; for (i, backup) in backups.iter().enumerate() { let val = keyfn(&backup.1); let cur = Some(val); if cur != last { last = cur; unique.push_back(i); if unique.len() > max { unique.pop_front(); } } } for i in unique { keep.set(i); } } if let Some(max) = yearly { mark_needed(&backups, &mut keep, max, |d| d.year()); } if let Some(max) = monthly { mark_needed(&backups, &mut keep, max, |d| (d.year(), d.month())); } if let Some(max) = weekly { mark_needed(&backups, &mut keep, max, |d| (d.isoweekdate().0, d.isoweekdate().1)); } if let Some(max) = daily { mark_needed(&backups, &mut keep, max, |d| (d.year(), d.month(), d.day())); } let mut remove = Vec::new(); for (i, backup) in backups.into_iter().enumerate() { if !keep.get(i) { remove.push(backup.0); } } info!("Removing the following backups: {:?}", remove); if force { for name in remove { try!(self.delete_backup(&name)); } } Ok(()) } pub fn restore_inode_tree>(&mut self, inode: Inode, path: P) -> Result<(), RepositoryError> { let _lock = try!(self.lock(false)); let mut queue = VecDeque::new(); queue.push_back((path.as_ref().to_owned(), inode)); while let Some((path, inode)) = queue.pop_front() { try!(self.save_inode_at(&inode, &path)); if inode.file_type == FileType::Directory { let path = path.join(inode.name); for chunks in inode.children.unwrap().values() { let inode = try!(self.get_inode(&chunks)); queue.push_back((path.clone(), inode)); } } } Ok(()) } #[inline] pub fn restore_backup>(&mut self, backup: &Backup, path: P) -> Result<(), RepositoryError> { let _lock = try!(self.lock(false)); let inode = try!(self.get_inode(&backup.root)); self.restore_inode_tree(inode, path) } pub fn create_backup_recurse>( &mut self, path: P, reference: Option<&Inode>, options: &BackupOptions, backup: &mut Backup, failed_paths: &mut Vec ) -> Result { let path = path.as_ref(); let mut inode = try!(self.create_inode(path, reference)); let meta_size = 1000; // add 1000 for encoded metadata backup.total_data_size += inode.size + meta_size; if let Some(ref_inode) = reference { if !ref_inode.is_unchanged(&inode) { backup.changed_data_size += inode.size + meta_size; } } else { backup.changed_data_size += inode.size + meta_size; } if inode.file_type == FileType::Directory { backup.dir_count +=1; let mut children = BTreeMap::new(); let parent_dev = try!(path.metadata()).st_dev(); for ch in try!(fs::read_dir(path)) { let child = try!(ch); let child_path = child.path(); if options.same_device { let child_dev = try!(child.metadata()).st_dev(); if child_dev != parent_dev { continue } } if let Some(ref excludes) = options.excludes { let child_path_str = child_path.to_string_lossy(); if excludes.is_match(&child_path_str) { continue } } let name = child.file_name().to_string_lossy().to_string(); let ref_child = reference.as_ref() .and_then(|inode| inode.children.as_ref()) .and_then(|map| map.get(&name)) .and_then(|chunks| self.get_inode(chunks).ok()); let chunks = match self.create_backup_recurse(&child_path, ref_child.as_ref(), options, backup, failed_paths) { Ok(chunks) => chunks, Err(_) => { warn!("Failed to backup {:?}", child_path); failed_paths.push(child_path); continue } }; children.insert(name, chunks); } inode.children = Some(children); } else { backup.file_count +=1; } self.put_inode(&inode) } #[allow(dead_code)] pub fn create_backup_recursively>(&mut self, path: P, reference: Option<&Backup>, options: &BackupOptions) -> Result { let _lock = try!(self.lock(false)); let reference_inode = reference.and_then(|b| self.get_inode(&b.root).ok()); let mut backup = Backup::default(); backup.config = self.config.clone(); backup.host = get_hostname().unwrap_or_else(|_| "".to_string()); backup.path = path.as_ref().to_string_lossy().to_string(); let info_before = self.info(); let start = Local::now(); let mut failed_paths = vec![]; backup.root = try!(self.create_backup_recurse(path, reference_inode.as_ref(), options, &mut backup, &mut failed_paths)); try!(self.flush()); let elapsed = Local::now().signed_duration_since(start); backup.date = start.timestamp(); backup.duration = elapsed.num_milliseconds() as f32 / 1_000.0; let info_after = self.info(); backup.deduplicated_data_size = info_after.raw_data_size - info_before.raw_data_size; backup.encoded_data_size = info_after.encoded_data_size - info_before.encoded_data_size; backup.bundle_count = info_after.bundle_count - info_before.bundle_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; if failed_paths.is_empty() { Ok(backup) } else { Err(BackupError::FailedPaths(backup, failed_paths).into()) } } pub fn remove_backup_path>(&mut self, backup: &mut Backup, path: P) -> Result<(), RepositoryError> { let _lock = try!(self.lock(false)); let mut inodes = try!(self.get_backup_path(backup, path)); let to_remove = inodes.pop().unwrap(); let mut remove_from = match inodes.pop() { Some(inode) => inode, None => return Err(BackupError::RemoveRoot.into()) }; remove_from.children.as_mut().unwrap().remove(&to_remove.name); let mut last_inode_chunks = try!(self.put_inode(&remove_from)); let mut last_inode_name = remove_from.name; while let Some(mut inode) = inodes.pop() { inode.children.as_mut().unwrap().insert(last_inode_name, last_inode_chunks); last_inode_chunks = try!(self.put_inode(&inode)); last_inode_name = inode.name; } backup.root = last_inode_chunks; Ok(()) } pub fn get_backup_path>(&mut self, backup: &Backup, path: P) -> Result, RepositoryError> { let mut inodes = vec![]; let mut inode = try!(self.get_inode(&backup.root)); for c in path.as_ref().components() { if let path::Component::Normal(name) = c { let name = name.to_string_lossy(); if let Some(chunks) = inode.children.as_mut().and_then(|c| c.remove(&name as &str)) { inodes.push(inode); inode = try!(self.get_inode(&chunks)); } else { return Err(RepositoryError::NoSuchFileInBackup(backup.clone(), path.as_ref().to_owned())); } } } inodes.push(inode); Ok(inodes) } #[inline] pub fn get_backup_inode>(&mut self, backup: &Backup, path: P) -> Result { self.get_backup_path(backup, path).map(|mut inodes| inodes.pop().unwrap()) } }