#![deny(missing_copy_implementations,
missing_docs,
trivial_casts, trivial_numeric_casts,
unsafe_code,
unstable_features,
unused_import_braces, unused_qualifications)]
#![cfg_attr(feature = "nightly", allow(unstable_features))]
#![cfg_attr(feature = "lints", feature(plugin))]
#![cfg_attr(feature = "lints", plugin(clippy))]
extern crate byteorder;
extern crate flate2;
extern crate regex;
extern crate tabwriter;
extern crate tar;
extern crate time;
#[macro_use]
extern crate try_opt;
mod macros;
mod tarext; pub mod time_utils;
pub mod backend;
pub mod collections;
pub mod signatures;
use std::cell::{Ref, RefCell};
use std::fmt::{self, Display, Formatter};
use std::io;
use time::Timespec;
pub use backend::Backend;
use collections::{BackupChain, BackupSet, Collections};
use signatures::Chain;
#[derive(Debug)]
pub struct Backup<B> {
backend: B,
collections: Collections,
signatures: Vec<RefCell<Option<Chain>>>,
}
pub struct Snapshots<'a> {
set_iter: CollectionsIter<'a>,
chain_id: usize,
sig_id: usize,
backup: &'a ResourceCache,
}
pub struct Snapshot<'a> {
set: &'a BackupSet,
chain_id: usize,
sig_id: usize,
backup: &'a ResourceCache,
}
pub struct SnapshotEntries<'a> {
chain: Ref<'a, Option<Chain>>,
sig_id: usize,
}
struct CollectionsIter<'a> {
chain_iter: collections::ChainIter<'a, BackupChain>,
incset_iter: Option<collections::BackupSetIter<'a>>,
}
trait ResourceCache {
fn _collections(&self) -> &Collections;
fn _signature_chain(&self, chain_id: usize) -> io::Result<Ref<Option<Chain>>>;
}
impl<B: Backend> Backup<B> {
pub fn new(backend: B) -> io::Result<Self> {
let files = try!(backend.file_names());
let collections = Collections::from_filenames(files);
let signatures = collections.signature_chains().map(|_| RefCell::new(None)).collect();
Ok(Backup {
backend: backend,
collections: collections,
signatures: signatures,
})
}
pub fn snapshots(&self) -> io::Result<Snapshots> {
let set_iter = CollectionsIter {
chain_iter: self.collections.backup_chains(),
incset_iter: None,
};
Ok(Snapshots {
set_iter: set_iter,
chain_id: 0,
sig_id: 0,
backup: self,
})
}
}
impl<'a> Iterator for Snapshots<'a> {
type Item = Snapshot<'a>;
fn next(&mut self) -> Option<Self::Item> {
if let Some(ref mut incset_iter) = self.set_iter.incset_iter {
if let Some(inc_set) = incset_iter.next() {
self.sig_id += 1;
return Some(Snapshot {
set: inc_set,
chain_id: self.chain_id - 1,
sig_id: self.sig_id,
backup: self.backup,
});
}
}
if let Some(chain) = self.set_iter.chain_iter.next() {
self.chain_id += 1;
self.sig_id = 0;
self.set_iter.incset_iter = Some(chain.inc_sets());
Some(Snapshot {
set: chain.full_set(),
chain_id: self.chain_id - 1,
sig_id: self.sig_id,
backup: self.backup,
})
} else {
None
}
}
}
impl<'a> Snapshots<'a> {
pub fn as_collections(&self) -> &Collections {
self.backup._collections()
}
}
impl<'a> Snapshot<'a> {
pub fn time(&self) -> Timespec {
self.set.end_time()
}
pub fn is_full(&self) -> bool {
self.set.is_full()
}
pub fn is_incremental(&self) -> bool {
self.set.is_incremental()
}
pub fn num_volumes(&self) -> usize {
self.set.num_volumes()
}
pub fn as_backup_set(&self) -> &BackupSet {
self.set
}
pub fn entries(&self) -> io::Result<SnapshotEntries> {
let sig = try!(self.backup._signature_chain(self.chain_id));
if self.sig_id < sig.as_ref().unwrap().snapshots().len() {
Ok(SnapshotEntries {
chain: sig,
sig_id: self.sig_id,
})
} else {
Err(not_found("The signature chain is incomplete"))
}
}
}
impl<'a> SnapshotEntries<'a> {
pub fn as_signature(&self) -> signatures::SnapshotEntries {
self.chain.as_ref().unwrap().snapshots().nth(self.sig_id).unwrap().files()
}
}
impl<'a> Display for SnapshotEntries<'a> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
self.as_signature().into_display().fmt(f)
}
}
impl<B: Backend> ResourceCache for Backup<B> {
fn _collections(&self) -> &Collections {
&self.collections
}
fn _signature_chain(&self, chain_id: usize) -> io::Result<Ref<Option<Chain>>> {
{
let mut sig = self.signatures[chain_id].borrow_mut();
if sig.is_none() {
if let Some(sigchain) = self.collections.signature_chains().nth(chain_id) {
let new_sig = try!(Chain::from_sigchain(sigchain, &self.backend));
*sig = Some(new_sig);
} else {
return Err(not_found("The given backup snapshot does not have a \
corresponding signature"));
}
}
}
Ok(self.signatures[chain_id].borrow())
}
}
fn not_found(msg: &str) -> io::Error {
io::Error::new(io::ErrorKind::NotFound, msg)
}
#[cfg(test)]
mod test {
use super::*;
use backend::local::LocalBackend;
use collections::{BackupSet, Collections};
use signatures::{Chain, Entry};
use time_utils::parse_time_str;
use std::path::{Path, PathBuf};
use time::Timespec;
#[derive(Debug, Eq, PartialEq)]
struct SnapshotTest {
time: Timespec,
is_full: bool,
num_volumes: usize,
}
#[derive(Debug, Clone, Eq, PartialEq)]
struct EntryTest {
path: PathBuf,
mtime: Timespec,
uname: String,
gname: String,
}
impl EntryTest {
pub fn from_entry(file: &Entry) -> Self {
EntryTest {
path: file.path().to_owned(),
mtime: file.mtime(),
uname: file.username().unwrap().to_owned(),
gname: file.groupname().unwrap().to_owned(),
}
}
pub fn from_info(path: &str,
mtime: &str,
uname: &str,
gname: &str)
-> Self {
EntryTest {
path: Path::new(path).to_path_buf(),
mtime: parse_time_str(mtime).unwrap(),
uname: uname.to_owned(),
gname: gname.to_owned(),
}
}
}
fn from_backup_set(set: &BackupSet, full: bool) -> SnapshotTest {
SnapshotTest {
time: set.end_time(),
is_full: full,
num_volumes: set.num_volumes(),
}
}
fn from_collection(coll: &Collections) -> Vec<SnapshotTest> {
let mut result = Vec::new();
for chain in coll.backup_chains() {
result.push(from_backup_set(chain.full_set(), true));
for set in chain.inc_sets() {
result.push(from_backup_set(set, false));
}
}
result
}
fn to_test_snapshot<B: Backend>(backup: &Backup<B>) -> Vec<SnapshotTest> {
backup.snapshots()
.unwrap()
.map(|s| {
assert!(s.is_full() != s.is_incremental());
SnapshotTest {
time: s.time(),
is_full: s.is_full(),
num_volumes: s.num_volumes(),
}
})
.collect()
}
fn single_vol_signature_chain() -> Chain {
let backend = LocalBackend::new("tests/backups/single_vol");
let filenames = backend.file_names().unwrap();
let coll = Collections::from_filenames(filenames);
Chain::from_sigchain(coll.signature_chains().next().unwrap(), &backend).unwrap()
}
fn from_sigchain(chain: &Chain) -> Vec<Vec<EntryTest>> {
chain.snapshots()
.map(|s| {
s.files()
.map(|f| EntryTest::from_entry(&f))
.filter(|f| f.path.to_str().is_some())
.collect::<Vec<_>>()
})
.collect::<Vec<_>>()
}
fn from_backup<B: Backend>(backup: &Backup<B>) -> Vec<Vec<EntryTest>> {
backup.snapshots()
.unwrap()
.map(|s| {
s.entries()
.unwrap()
.as_signature()
.map(|f| EntryTest::from_entry(&f))
.filter(|f| f.path.to_str().is_some())
.collect::<Vec<_>>()
})
.collect::<Vec<_>>()
}
#[test]
fn same_collections_single_vol() {
let backend = LocalBackend::new("tests/backups/single_vol");
let filenames = backend.file_names().unwrap();
let coll = Collections::from_filenames(filenames);
let backup = Backup::new(backend).unwrap();
let expected = from_collection(&coll);
let actual = to_test_snapshot(&backup);
assert_eq!(actual, expected);
}
#[test]
fn same_collections_multi_chain() {
let backend = LocalBackend::new("tests/backups/multi_chain");
let filenames = backend.file_names().unwrap();
let coll = Collections::from_filenames(filenames);
let backup = Backup::new(backend).unwrap();
let expected = from_collection(&coll);
let actual = to_test_snapshot(&backup);
assert_eq!(actual, expected);
}
#[test]
fn same_files() {
let sigchain = single_vol_signature_chain();
let expected = from_sigchain(&sigchain);
let backend = LocalBackend::new("tests/backups/single_vol");
let backup = Backup::new(backend).unwrap();
let actual = from_backup(&backup);
assert_eq!(actual, expected);
}
#[test]
fn multi_chain_files() {
let backend = LocalBackend::new("tests/backups/multi_chain");
let backup = Backup::new(backend).unwrap();
let actual = from_backup(&backup);
let expected = vec![vec![make_entry_test("", "20160108t223141z"),
make_entry_test("file", "20160108t222924z")],
vec![make_entry_test("", "20160108t223153z"),
make_entry_test("file", "20160108t223153z")],
vec![make_entry_test("", "20160108t223206z"),
make_entry_test("file", "20160108t223206z")],
vec![make_entry_test("", "20160108t223215z"),
make_entry_test("file", "20160108t223215z")]];
assert_eq!(actual, expected);
fn make_entry_test(path: &str, mtime: &str) -> EntryTest {
EntryTest::from_info(path, mtime, "michele", "michele")
}
}
}