diff --git a/.gitignore b/.gitignore index 69ba049..9d0b1c8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ squash test.tar test_* restored +excludes diff --git a/Cargo.lock b/Cargo.lock index 0e2f42f..ead6281 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,6 +14,7 @@ dependencies = [ "murmurhash3 0.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "quick-error 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "rmp-serde 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)", @@ -23,6 +24,14 @@ dependencies = [ "squash-sys 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "aho-corasick" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "ansi_term" version = "0.9.0" @@ -136,6 +145,14 @@ name = "log" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "memchr" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "mmap" version = "0.1.1" @@ -205,6 +222,23 @@ name = "redox_syscall" version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "regex" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex-syntax" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "rmp" version = "0.8.5" @@ -295,6 +329,24 @@ dependencies = [ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "thread-id" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "thread_local" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "thread-id 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "time" version = "0.1.36" @@ -316,11 +368,29 @@ name = "unicode-width" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "unreachable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "utf8-ranges" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "vec_map" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "winapi" version = "0.2.8" @@ -340,6 +410,7 @@ dependencies = [ ] [metadata] +"checksum aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "500909c4f87a9e52355b26626d890833e9e1d53ac566db76c36faa984b889699" "checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6" "checksum atty 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d912da0db7fa85514874458ca3651fe2cddace8d0b0505571dbdcd41ab490159" "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" @@ -356,6 +427,7 @@ dependencies = [ "checksum libsodium-sys 0.0.14 (registry+https://github.com/rust-lang/crates.io-index)" = "cbbc6e46017815abf8698de0ed4847fad45fd8cad2909ac38ac6de79673c1ad1" "checksum linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6d262045c5b87c0861b3f004610afd0e2c851e2908d08b6c870cbb9d5f494ecd" "checksum log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "5141eca02775a762cc6cd564d8d2c50f67c0ea3a372cbf1c51592b3e029e10ad" +"checksum memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1dbccc0e46f1ea47b9f17e6d67c5a96bd27030519c519c9c91327e31275a47b4" "checksum mmap 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0bc85448a6006dd2ba26a385a564a8a0f1f2c7e78c70f1a70b2e0f4af286b823" "checksum murmurhash3 0.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "a2983372caf4480544083767bf2d27defafe32af49ab4df3a0b7fc90793a3664" "checksum num 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "98b15ba84e910ea7a1973bccd3df7b31ae282bf9d8bd2897779950c9b8303d40" @@ -366,6 +438,8 @@ dependencies = [ "checksum quick-error 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0aad603e8d7fb67da22dbdf1f4b826ce8829e406124109e73cf1b2454b93a71c" "checksum rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "022e0636ec2519ddae48154b028864bdce4eaf7d35226ab8e65c611be97b189d" "checksum redox_syscall 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "29dbdfd4b9df8ab31dec47c6087b7b13cbf4a776f335e4de8efba8288dda075b" +"checksum regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4278c17d0f6d62dfef0ab00028feb45bd7d2102843f80763474eeb1be8a10c01" +"checksum regex-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9191b1f57603095f105d317e375d19b1c9c5c3185ea9633a99a6dcbed04457" "checksum rmp 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)" = "333f01365885cb192edaa22acb06d7e2f196bfd19d6969419e8b61307e0710ea" "checksum rmp-serde 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)" = "06ec4d0cdea2645de5d0e649f90c3e654205d913e14adefa452257314a24e76e" "checksum rustc-serialize 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)" = "684ce48436d6465300c9ea783b6b14c4361d6b8dcbb1375b486a69cc19e2dfb0" @@ -377,10 +451,15 @@ dependencies = [ "checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694" "checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6" "checksum term_size 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "07b6c1ac5b3fffd75073276bca1ceed01f67a28537097a2a9539e116e50fb21a" +"checksum thread-id 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4437c97558c70d129e40629a5b385b3fb1ffac301e63941335e4d354081ec14a" +"checksum thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c85048c6260d17cf486ceae3282d9fb6b90be220bf5b28c400f5485ffc29f0c7" "checksum time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)" = "211b63c112206356ef1ff9b19355f43740fc3f85960c598a93d3a3d3ba7beade" "checksum unicode-segmentation 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18127285758f0e2c6cf325bb3f3d138a12fee27de4f23e146cd6a179f26c2cf3" "checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f" +"checksum unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1f2ae5ddb18e1c92664717616dd9549dde73f539f01bd7b77c2edb2446bdff91" +"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" "checksum vec_map 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8cdc8b93bd0198ed872357fb2e667f7125646b1762f16d60b2c96350d361897" +"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" "checksum yaml-rust 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e66366e18dc58b46801afbf2ca7661a9f59cc8c5962c29892b6039b4f86fa992" diff --git a/Cargo.toml b/Cargo.toml index 5db004b..44e131b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ byteorder = "1.0" ansi_term = "0.9" sodiumoxide = "0.0.14" filetime = "0.1" +regex = "0.2" libc = "*" [build-dependencies] diff --git a/src/cli/args.rs b/src/cli/args.rs index b09bd96..34fe74d 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -20,7 +20,9 @@ pub enum Arguments { src_path: String, full: bool, reference: Option, - same_device: bool + same_device: bool, + excludes: Vec, + excludes_from: Option }, Restore { repo_path: String, @@ -196,6 +198,8 @@ pub fn parse() -> Arguments { (@arg full: --full "create a full backup") (@arg reference: --ref +takes_value "the reference backup to use for partial backup") (@arg same_device: --xdev -x "do not cross filesystem boundaries") + (@arg exclude: --exclude -e ... +takes_value "exclude this path or file") + (@arg excludes_from: --excludesfrom +takes_value "read the list of exludes from this file") (@arg SRC: +required "source path to backup") (@arg BACKUP: +required "repository::backup path") ) @@ -308,6 +312,8 @@ pub fn parse() -> Arguments { backup_name: backup.unwrap().to_string(), full: args.is_present("full"), same_device: args.is_present("same_device"), + excludes: args.values_of("exclude").map(|v| v.map(|k| k.to_string()).collect()).unwrap_or_else(|| vec![]), + excludes_from: args.value_of("excludes_from").map(|v| v.to_string()), src_path: args.value_of("SRC").unwrap().to_string(), reference: args.value_of("reference").map(|v| v.to_string()) } diff --git a/src/cli/mod.rs b/src/cli/mod.rs index da2aef7..a3deb42 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -5,9 +5,13 @@ mod algotest; use ::prelude::*; use chrono::prelude::*; +use regex::{self, RegexSet}; + use std::process::exit; use std::collections::HashMap; use std::fmt::Display; +use std::io::{BufReader, BufRead}; +use std::fs::File; use self::args::Arguments; @@ -174,7 +178,7 @@ pub fn run() { } print_config(&repo.config); }, - Arguments::Backup{repo_path, backup_name, src_path, full, reference, same_device} => { + Arguments::Backup{repo_path, backup_name, src_path, full, reference, same_device, mut excludes, excludes_from} => { let mut repo = open_repository(&repo_path); let mut reference_backup = None; if !full { @@ -188,8 +192,27 @@ pub fn run() { info!("No reference backup found, doing a full scan instead"); } } + if let Some(excludes_from) = excludes_from { + for line in BufReader::new(checked(File::open(excludes_from), "open excludes file")).lines() { + excludes.push(checked(line, "read excludes file")); + } + } + let excludes: Vec = excludes.into_iter().map(|mut exclude| { + exclude = regex::escape(&exclude).replace('?', ".").replace(r"\*\", ".*").replace(r"\*", "[^/]*"); + if exclude.starts_with('/') { + format!(r"^{}($|/)", exclude) + } else { + format!(r"/{}($|/)", exclude) + } + }).collect(); + let excludes = if excludes.is_empty() { + None + } else { + Some(checked(RegexSet::new(excludes), "parse exclude patterns")) + }; let options = BackupOptions { - same_device: same_device + same_device: same_device, + excludes: excludes }; let backup = match repo.create_backup_recursively(&src_path, reference_backup.as_ref(), &options) { Ok(backup) => backup, diff --git a/src/main.rs b/src/main.rs index f1a7dc0..44ebca2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,6 +16,7 @@ extern crate byteorder; extern crate sodiumoxide; extern crate ansi_term; extern crate filetime; +extern crate regex; extern crate libc; pub mod util; diff --git a/src/repository/backup.rs b/src/repository/backup.rs index 2612efd..5fd8144 100644 --- a/src/repository/backup.rs +++ b/src/repository/backup.rs @@ -6,6 +6,7 @@ use std::collections::{HashMap, BTreeMap, VecDeque}; use std::os::linux::fs::MetadataExt; use chrono::prelude::*; +use regex::RegexSet; quick_error!{ @@ -25,7 +26,8 @@ quick_error!{ pub struct BackupOptions { - pub same_device: bool + pub same_device: bool, + pub excludes: Option } @@ -171,18 +173,24 @@ impl Repository { 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 child_path = child.path(); let chunks = match self.create_backup_recurse(&child_path, ref_child.as_ref(), options, backup, failed_paths) { Ok(chunks) => chunks, Err(_) => {