diff --git a/src/cli/args.rs b/src/cli/args.rs index 7801b5b..8418134 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -76,6 +76,14 @@ pub enum Arguments { repo_path: String, path: String }, + Diff { + repo_path_old: String, + backup_name_old: String, + inode_old: Option, + repo_path_new: String, + backup_name_new: String, + inode_new: Option + }, Analyze { repo_path: String }, @@ -313,6 +321,11 @@ pub fn parse() -> Arguments { (@arg REPO: +required "repository path") (@arg PATH: +required "the file path") ) + (@subcommand diff => + (about: "display difference between two backup versions") + (@arg OLD: +required "old repository::backup[::subpath] path") + (@arg NEW: +required "new repository::backup[::subpath] path") + ) (@subcommand config => (about: "changes the configuration") (@arg REPO: +required "path of the repository") @@ -460,6 +473,18 @@ pub fn parse() -> Arguments { path: args.value_of("PATH").unwrap().to_string() } } + if let Some(args) = args.subcommand_matches("diff") { + let (repository_old, backup_old, inode_old) = parse_repo_path(args.value_of("OLD").unwrap(), Some(true), None); + let (repository_new, backup_new, inode_new) = parse_repo_path(args.value_of("NEW").unwrap(), Some(true), None); + return Arguments::Diff { + repo_path_old: repository_old.to_string(), + backup_name_old: backup_old.unwrap().to_string(), + inode_old: inode_old.map(|v| v.to_string()), + repo_path_new: repository_new.to_string(), + backup_name_new: backup_new.unwrap().to_string(), + inode_new: inode_new.map(|v| v.to_string()), + } + } if let Some(args) = args.subcommand_matches("analyze") { let (repository, _backup, _inode) = parse_repo_path(args.value_of("REPO").unwrap(), Some(false), Some(false)); return Arguments::Analyze { diff --git a/src/cli/mod.rs b/src/cli/mod.rs index c1407fc..962e89d 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -95,7 +95,6 @@ fn print_inode(inode: &Inode) { println!("Permissions: {:3o}", inode.mode); println!("User: {}", inode.user); println!("Group: {}", inode.group); - println!("Access time: {}", Local.timestamp(inode.access_time, 0).to_rfc2822()); println!("Modification time: {}", Local.timestamp(inode.modify_time, 0).to_rfc2822()); if let Some(ref target) = inode.symlink_target { println!("Symlink target: {}", target); @@ -425,6 +424,25 @@ pub fn run() { println!("{}", format_inode_one_line(&inode)); } }, + Arguments::Diff{repo_path_old, backup_name_old, inode_old, repo_path_new, backup_name_new, inode_new} => { + if repo_path_old != repo_path_new { + error!("Can only run diff on same repository"); + exit(2) + } + let mut repo = open_repository(&repo_path_old); + let backup_old = get_backup(&repo, &backup_name_old); + let backup_new = get_backup(&repo, &backup_name_new); + let inode1 = checked(repo.get_backup_inode(&backup_old, inode_old.unwrap_or_else(|| "/".to_string())), "load subpath inode"); + let inode2 = checked(repo.get_backup_inode(&backup_new, inode_new.unwrap_or_else(|| "/".to_string())), "load subpath inode"); + let diffs = checked(repo.find_differences(&inode1, &inode2), "find differences"); + for diff in diffs { + println!("{} {:?}", match diff.0 { + DiffType::Add => "add", + DiffType::Mod => "mod", + DiffType::Del => "del" + }, diff.1); + } + }, Arguments::Config{repo_path, bundle_size, chunker, compression, encryption, hash} => { let mut repo = open_repository(&repo_path); if let Some(bundle_size) = bundle_size { diff --git a/src/mount.rs b/src/mount.rs index c406498..b0f8465 100644 --- a/src/mount.rs +++ b/src/mount.rs @@ -87,7 +87,7 @@ impl FuseInode { ino: self.num, size: self.inode.size, blocks: self.inode.size / 512, - atime: Timespec::new(self.inode.access_time, 0), + atime: Timespec::new(self.inode.modify_time, 0), mtime: Timespec::new(self.inode.modify_time, 0), ctime: Timespec::new(0, 0), crtime: Timespec::new(0, 0), diff --git a/src/prelude.rs b/src/prelude.rs index cc63c92..04d9007 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,7 +1,7 @@ pub use ::util::*; pub use ::bundledb::{BundleReader, BundleMode, BundleWriter, BundleInfo, BundleId, BundleDbError, BundleDb, BundleWriterError}; pub use ::chunker::{ChunkerType, Chunker, ChunkerStatus, IChunker, ChunkerError}; -pub use ::repository::{Repository, Backup, Config, RepositoryError, RepositoryInfo, Inode, FileType, RepositoryIntegrityError, BackupFileError, BackupError, BackupOptions, BundleAnalysis, FileContents}; +pub use ::repository::{Repository, Backup, Config, RepositoryError, RepositoryInfo, Inode, FileType, RepositoryIntegrityError, BackupFileError, BackupError, BackupOptions, BundleAnalysis, FileContents, DiffType}; pub use ::index::{Index, Location, IndexError}; pub use ::mount::FuseFilesystem; diff --git a/src/repository/backup.rs b/src/repository/backup.rs index 4fdbc9c..220b4d7 100644 --- a/src/repository/backup.rs +++ b/src/repository/backup.rs @@ -31,6 +31,11 @@ pub struct BackupOptions { } +pub enum DiffType { + Add, Mod, Del +} + + impl Repository { pub fn get_backups(&self) -> Result, RepositoryError> { Ok(try!(Backup::get_all_from(&self.crypto.lock().unwrap(), &self.backups_path))) @@ -159,7 +164,7 @@ impl Repository { 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) { + if !ref_inode.is_same_meta_quick(&inode) { backup.changed_data_size += inode.size + meta_size; } } else { @@ -295,4 +300,52 @@ impl Repository { versions.sort_by_key(|v| v.1.modify_time); Ok(versions) } + + #[inline] + fn find_differences_recurse(&mut self, inode1: &Inode, inode2: &Inode, path: PathBuf, diffs: &mut Vec<(DiffType, PathBuf)>) -> Result<(), RepositoryError> { + if !inode1.is_same_meta(inode2) || inode1.contents != inode2.contents { + diffs.push((DiffType::Mod, path.clone())); + } + if let Some(ref children1) = inode1.children { + if let Some(ref children2) = inode2.children { + for name in children1.keys() { + if !children2.contains_key(name) { + diffs.push((DiffType::Del, path.join(name))); + } + } + } else { + for name in children1.keys() { + diffs.push((DiffType::Del, path.join(name))); + } + } + } + if let Some(ref children2) = inode2.children { + if let Some(ref children1) = inode1.children { + for (name, chunks2) in children2 { + if let Some(chunks1) = children1.get(name) { + if chunks1 != chunks2 { + let inode1 = try!(self.get_inode(chunks1)); + let inode2 = try!(self.get_inode(chunks2)); + try!(self.find_differences_recurse(&inode1, &inode2, path.join(name), diffs)); + } + } else { + diffs.push((DiffType::Add, path.join(name))); + } + } + } else { + for name in children2.keys() { + diffs.push((DiffType::Add, path.join(name))); + } + } + } + Ok(()) + } + + #[inline] + pub fn find_differences(&mut self, inode1: &Inode, inode2: &Inode) -> Result, RepositoryError> { + let mut diffs = vec![]; + let path = PathBuf::from("/"); + try!(self.find_differences_recurse(inode1, inode2, path, &mut diffs)); + Ok(diffs) + } } diff --git a/src/repository/metadata.rs b/src/repository/metadata.rs index 6b45511..5b356cd 100644 --- a/src/repository/metadata.rs +++ b/src/repository/metadata.rs @@ -111,9 +111,9 @@ pub struct Inode { pub mode: u32, pub user: u32, pub group: u32, - pub access_time: i64, + pub __old_access_time: i64, pub modify_time: i64, - pub create_time: i64, + pub __old_create_time: i64, pub symlink_target: Option, pub contents: Option, pub children: Option> @@ -127,9 +127,9 @@ impl Default for Inode { mode: 0o644, user: 1000, group: 1000, - access_time: 0, + __old_access_time: 0, modify_time: 0, - create_time: 0, + __old_create_time: 0, symlink_target: None, contents: None, children: None @@ -143,9 +143,9 @@ serde_impl!(Inode(u8) { mode: u32 => 3, user: u32 => 4, group: u32 => 5, - access_time: i64 => 6, + __old_access_time: i64 => 6, modify_time: i64 => 7, - create_time: i64 => 8, + __old_create_time: i64 => 8, symlink_target: Option => 9, contents: Option => 10, children: BTreeMap => 11 @@ -213,7 +213,13 @@ impl Inode { Ok(file) } - pub fn is_unchanged(&self, other: &Inode) -> bool { + 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.modify_time == other.modify_time && self.symlink_target == other.symlink_target + } + + pub fn is_same_meta_quick(&self, other: &Inode) -> bool { self.modify_time == other.modify_time && self.file_type == other.file_type && self.size == other.size @@ -236,7 +242,7 @@ impl Repository { let mut inode = try!(Inode::get_from(path.as_ref())); if inode.file_type == FileType::File && inode.size > 0 { if let Some(reference) = reference { - if reference.is_unchanged(&inode) { + if reference.is_same_meta_quick(&inode) { inode.contents = reference.contents.clone(); return Ok(inode) } diff --git a/src/repository/mod.rs b/src/repository/mod.rs index d6b55cf..8363716 100644 --- a/src/repository/mod.rs +++ b/src/repository/mod.rs @@ -22,7 +22,7 @@ use std::io::Write; pub use self::error::RepositoryError; pub use self::config::Config; pub use self::metadata::{Inode, FileType, FileContents}; -pub use self::backup::{BackupError, BackupOptions}; +pub use self::backup::{BackupError, BackupOptions, DiffType}; pub use self::backup_file::{Backup, BackupFileError}; pub use self::integrity::RepositoryIntegrityError; pub use self::info::{RepositoryInfo, BundleAnalysis};