zvault/src/cli/args.rs

472 lines
18 KiB
Rust
Raw Normal View History

2017-03-21 10:28:11 +00:00
use ::prelude::*;
2017-03-22 08:19:16 +00:00
use super::*;
2017-03-16 19:05:58 +00:00
2017-03-17 06:15:19 +00:00
use std::process::exit;
2017-03-16 19:05:58 +00:00
pub enum Arguments {
Init {
repo_path: String,
bundle_size: usize,
chunker: ChunkerType,
2017-03-17 10:03:07 +00:00
compression: Option<Compression>,
2017-03-18 16:22:11 +00:00
encryption: bool,
2017-03-22 13:10:42 +00:00
hash: HashMethod,
2017-03-22 13:42:27 +00:00
remote_path: String
2017-03-16 19:05:58 +00:00
},
Backup {
repo_path: String,
backup_name: String,
src_path: String,
2017-03-20 21:24:53 +00:00
full: bool,
reference: Option<String>
2017-03-16 19:05:58 +00:00
},
Restore {
repo_path: String,
backup_name: String,
inode: Option<String>,
dst_path: String
},
2017-03-17 11:58:22 +00:00
Remove {
repo_path: String,
backup_name: String,
inode: Option<String>
},
2017-03-20 14:38:33 +00:00
Prune {
repo_path: String,
prefix: String,
daily: Option<usize>,
weekly: Option<usize>,
monthly: Option<usize>,
yearly: Option<usize>,
2017-03-20 17:11:03 +00:00
force: bool
2017-03-20 14:38:33 +00:00
},
2017-03-17 11:58:22 +00:00
Vacuum {
repo_path: String,
2017-03-20 13:03:29 +00:00
ratio: f32,
2017-03-20 17:11:03 +00:00
force: bool
2017-03-17 11:58:22 +00:00
},
2017-03-16 19:05:58 +00:00
Check {
repo_path: String,
backup_name: Option<String>,
inode: Option<String>,
full: bool
},
List {
repo_path: String,
backup_name: Option<String>,
inode: Option<String>
},
Info {
repo_path: String,
backup_name: Option<String>,
inode: Option<String>
},
ListBundles {
repo_path: String
},
2017-03-17 11:58:22 +00:00
Import {
repo_path: String,
2017-03-22 16:28:45 +00:00
remote_path: String,
key_files: Vec<String>
2017-03-17 11:58:22 +00:00
},
2017-03-18 16:22:11 +00:00
Configure {
repo_path: String,
bundle_size: Option<usize>,
chunker: Option<ChunkerType>,
compression: Option<Option<Compression>>,
encryption: Option<Option<PublicKey>>,
hash: Option<HashMethod>
},
GenKey {
2017-03-22 16:28:45 +00:00
file: Option<String>
2017-03-18 16:22:11 +00:00
},
AddKey {
repo_path: String,
2017-03-22 16:28:45 +00:00
file: Option<String>,
2017-03-18 16:22:11 +00:00
set_default: bool
},
2017-03-16 19:05:58 +00:00
AlgoTest {
2017-03-17 10:03:07 +00:00
file: String,
bundle_size: usize,
chunker: ChunkerType,
compression: Option<Compression>,
2017-03-18 16:22:11 +00:00
encrypt: bool,
2017-03-17 10:03:07 +00:00
hash: HashMethod
2017-03-16 19:05:58 +00:00
}
}
2017-03-17 10:03:07 +00:00
pub fn split_repo_path(repo_path: &str) -> (&str, Option<&str>, Option<&str>) {
let mut parts = repo_path.splitn(3, "::");
let repo = parts.next().unwrap();
let backup = parts.next();
let inode = parts.next();
(repo, backup, inode)
2017-03-16 19:05:58 +00:00
}
2017-03-17 06:15:19 +00:00
2017-03-17 10:03:07 +00:00
fn parse_num(num: &str, name: &str) -> u64 {
if let Ok(num) = num.parse::<u64>() {
num
} else {
error!("{} must be a number, was '{}'", name, num);
exit(1);
}
}
2017-03-17 11:58:22 +00:00
fn parse_float(num: &str, name: &str) -> f64 {
if let Ok(num) = num.parse::<f64>() {
num
} else {
error!("{} must be a floating-point number, was '{}'", name, num);
exit(1);
}
}
2017-03-18 16:22:11 +00:00
fn parse_chunker(val: &str) -> ChunkerType {
if let Ok(chunker) = ChunkerType::from_string(val) {
2017-03-17 10:03:07 +00:00
chunker
} else {
2017-03-18 16:22:11 +00:00
error!("Invalid chunker method/size: {}", val);
2017-03-17 10:03:07 +00:00
exit(1);
}
}
2017-03-18 16:22:11 +00:00
fn parse_compression(val: &str) -> Option<Compression> {
2017-03-17 10:03:07 +00:00
if val == "none" {
return None
}
if let Ok(compression) = Compression::from_string(val) {
Some(compression)
} else {
error!("Invalid compression method/level: {}", val);
exit(1);
}
}
2017-03-18 16:22:11 +00:00
fn parse_public_key(val: &str) -> PublicKey {
let bytes = match parse_hex(val) {
Ok(bytes) => bytes,
Err(_) => {
error!("Invalid key: {}", val);
exit(1);
}
};
if let Some(key) = PublicKey::from_slice(&bytes) {
key
} else {
error!("Invalid key: {}", val);
exit(1);
}
}
fn parse_hash(val: &str) -> HashMethod {
if let Ok(hash) = HashMethod::from(val) {
2017-03-17 10:03:07 +00:00
hash
} else {
2017-03-18 16:22:11 +00:00
error!("Invalid hash method: {}", val);
2017-03-17 10:03:07 +00:00
exit(1);
}
}
pub fn parse() -> Arguments {
2017-03-17 06:15:19 +00:00
let args = clap_app!(zvault =>
2017-03-18 16:22:11 +00:00
(version: crate_version!())
(author: crate_authors!(",\n"))
(about: crate_description!())
2017-03-17 10:03:07 +00:00
(@setting SubcommandRequiredElseHelp)
(@setting GlobalVersion)
(@setting VersionlessSubcommands)
(@setting UnifiedHelpMessage)
2017-03-17 06:15:19 +00:00
(@subcommand init =>
(about: "initializes a new repository")
2017-03-18 16:22:11 +00:00
(@arg bundle_size: --bundlesize +takes_value "maximal bundle size in MiB [default: 25]")
2017-03-17 10:03:07 +00:00
(@arg chunker: --chunker +takes_value "chunker algorithm [default: fastcdc/8]")
(@arg compression: --compression -c +takes_value "compression to use [default: brotli/3]")
2017-03-18 16:22:11 +00:00
(@arg encryption: --encryption -e "generate a keypair and enable encryption")
2017-03-17 10:03:07 +00:00
(@arg hash: --hash +takes_value "hash method to use [default: blake2]")
2017-03-22 13:10:42 +00:00
(@arg remote: --remote -r +takes_value +required "path to the mounted remote storage")
2017-03-17 06:15:19 +00:00
(@arg REPO: +required "path of the repository")
)
(@subcommand backup =>
(about: "creates a new backup")
(@arg full: --full "create a full backup")
2017-03-20 21:24:53 +00:00
(@arg reference: --ref +takes_value "the reference backup to use for partial backup")
2017-03-17 06:15:19 +00:00
(@arg SRC: +required "source path to backup")
2017-03-18 15:37:45 +00:00
(@arg BACKUP: +required "repository::backup path")
2017-03-17 06:15:19 +00:00
)
(@subcommand restore =>
2017-03-17 11:58:22 +00:00
(about: "restores a backup (or subpath)")
2017-03-17 06:15:19 +00:00
(@arg BACKUP: +required "repository::backup[::subpath] path")
(@arg DST: +required "destination path for backup")
)
2017-03-17 11:58:22 +00:00
(@subcommand remove =>
(about: "removes a backup or a subpath")
(@arg BACKUP: +required "repository::backup[::subpath] path")
)
2017-03-20 14:38:33 +00:00
(@subcommand prune =>
(about: "removes backups based on age")
(@arg prefix: --prefix +takes_value "only consider backups starting with this prefix")
(@arg daily: --daily +takes_value "keep this number of daily backups")
(@arg weekly: --weekly +takes_value "keep this number of weekly backups")
(@arg monthly: --monthly +takes_value "keep this number of monthly backups")
(@arg yearly: --yearly +takes_value "keep this number of yearly backups")
2017-03-20 17:11:03 +00:00
(@arg force: --force -f "actually run the prunce instead of simulating it")
2017-03-20 14:38:33 +00:00
(@arg REPO: +required "path of the repository")
)
2017-03-17 11:58:22 +00:00
(@subcommand vacuum =>
(about: "saves space by combining and recompressing bundles")
2017-03-20 13:03:29 +00:00
(@arg ratio: --ratio -r +takes_value "ratio of unused chunks in a bundle to rewrite that bundle")
2017-03-20 17:11:03 +00:00
(@arg force: --force -f "actually run the vacuum instead of simulating it")
2017-03-17 11:58:22 +00:00
(@arg REPO: +required "path of the repository")
)
2017-03-17 06:15:19 +00:00
(@subcommand check =>
2017-03-17 11:58:22 +00:00
(about: "checks the repository, a backup or a backup subpath")
2017-03-17 06:15:19 +00:00
(@arg full: --full "also check file contents")
(@arg PATH: +required "repository[::backup] path")
)
(@subcommand list =>
(about: "lists backups or backup contents")
(@arg PATH: +required "repository[::backup[::subpath]] path")
)
(@subcommand listbundles =>
(about: "lists bundles in a repository")
2017-03-17 11:58:22 +00:00
(@arg REPO: +required "path of the repository")
)
(@subcommand import =>
(about: "reconstruct a repository from the remote files")
2017-03-22 16:28:45 +00:00
(@arg key: --key -k ... +takes_value "a file with a needed to read the bundles")
2017-03-17 11:58:22 +00:00
(@arg REMOTE: +required "remote repository path")
2017-03-18 15:37:45 +00:00
(@arg REPO: +required "path of the local repository to create")
2017-03-17 06:15:19 +00:00
)
(@subcommand info =>
(about: "displays information on a repository, a backup or a path in a backup")
(@arg PATH: +required "repository[::backup[::subpath]] path")
)
2017-03-18 16:22:11 +00:00
(@subcommand configure =>
(about: "changes the configuration")
(@arg REPO: +required "path of the repository")
(@arg bundle_size: --bundlesize +takes_value "maximal bundle size in MiB [default: 25]")
2017-03-22 08:19:16 +00:00
(@arg chunker: --chunker +takes_value "chunker algorithm [default: fastcdc/16]")
2017-03-18 16:22:11 +00:00
(@arg compression: --compression -c +takes_value "compression to use [default: brotli/3]")
(@arg encryption: --encryption -e +takes_value "the public key to use for encryption")
(@arg hash: --hash +takes_value "hash method to use [default: blake2]")
)
(@subcommand genkey =>
(about: "generates a new key pair")
2017-03-22 16:28:45 +00:00
(@arg FILE: +takes_value "the destination file for the keypair")
2017-03-18 16:22:11 +00:00
)
(@subcommand addkey =>
(about: "adds a key to the respository")
(@arg REPO: +required "path of the repository")
2017-03-22 17:21:48 +00:00
(@arg generate: --generate -g "generate a new key")
(@arg set_default: --default -d "set this key as default")
2017-03-22 16:28:45 +00:00
(@arg FILE: +takes_value "the file containing the keypair")
2017-03-18 16:22:11 +00:00
)
2017-03-17 06:15:19 +00:00
(@subcommand algotest =>
(about: "test a specific algorithm combination")
2017-03-18 16:22:11 +00:00
(@arg bundle_size: --bundlesize +takes_value "maximal bundle size in MiB [default: 25]")
2017-03-22 08:19:16 +00:00
(@arg chunker: --chunker +takes_value "chunker algorithm [default: fastcdc/16]")
2017-03-17 10:03:07 +00:00
(@arg compression: --compression -c +takes_value "compression to use [default: brotli/3]")
2017-03-18 16:22:11 +00:00
(@arg encrypt: --encrypt -e "enable encryption")
2017-03-17 10:03:07 +00:00
(@arg hash: --hash +takes_value "hash method to use [default: blake2]")
2017-03-17 06:15:19 +00:00
(@arg FILE: +required "the file to test the algorithms with")
)
).get_matches();
if let Some(args) = args.subcommand_matches("init") {
2017-03-17 10:03:07 +00:00
let (repository, backup, inode) = split_repo_path(args.value_of("REPO").unwrap());
if backup.is_some() || inode.is_some() {
println!("No backups or subpaths may be given here");
exit(1);
}
return Arguments::Init {
2017-03-22 08:19:16 +00:00
bundle_size: (parse_num(args.value_of("bundle_size").unwrap_or(&DEFAULT_BUNDLE_SIZE.to_string()), "Bundle size") * 1024 * 1024) as usize,
chunker: parse_chunker(args.value_of("chunker").unwrap_or(DEFAULT_CHUNKER)),
compression: parse_compression(args.value_of("compression").unwrap_or(DEFAULT_COMPRESSION)),
2017-03-18 16:22:11 +00:00
encryption: args.is_present("encryption"),
2017-03-22 08:19:16 +00:00
hash: parse_hash(args.value_of("hash").unwrap_or(DEFAULT_HASH)),
2017-03-17 10:03:07 +00:00
repo_path: repository.to_string(),
2017-03-22 13:42:27 +00:00
remote_path: args.value_of("remote").unwrap().to_string()
2017-03-17 10:03:07 +00:00
}
}
if let Some(args) = args.subcommand_matches("backup") {
let (repository, backup, inode) = split_repo_path(args.value_of("BACKUP").unwrap());
if backup.is_none() {
println!("A backup must be specified");
exit(1);
}
if inode.is_some() {
println!("No subpaths may be given here");
exit(1);
}
return Arguments::Backup {
repo_path: repository.to_string(),
backup_name: backup.unwrap().to_string(),
full: args.is_present("full"),
2017-03-20 21:24:53 +00:00
src_path: args.value_of("SRC").unwrap().to_string(),
reference: args.value_of("reference").map(|v| v.to_string())
2017-03-17 10:03:07 +00:00
}
}
if let Some(args) = args.subcommand_matches("restore") {
let (repository, backup, inode) = split_repo_path(args.value_of("BACKUP").unwrap());
if backup.is_none() {
println!("A backup must be specified");
exit(1);
}
return Arguments::Restore {
repo_path: repository.to_string(),
backup_name: backup.unwrap().to_string(),
inode: inode.map(|v| v.to_string()),
dst_path: args.value_of("DST").unwrap().to_string()
}
}
2017-03-17 11:58:22 +00:00
if let Some(args) = args.subcommand_matches("remove") {
let (repository, backup, inode) = split_repo_path(args.value_of("BACKUP").unwrap());
if backup.is_none() {
println!("A backup must be specified");
exit(1);
}
return Arguments::Remove {
repo_path: repository.to_string(),
backup_name: backup.unwrap().to_string(),
inode: inode.map(|v| v.to_string())
}
}
2017-03-20 14:38:33 +00:00
if let Some(args) = args.subcommand_matches("prune") {
let (repository, backup, inode) = split_repo_path(args.value_of("REPO").unwrap());
if backup.is_some() || inode.is_some() {
println!("No backups or subpaths may be given here");
exit(1);
}
return Arguments::Prune {
repo_path: repository.to_string(),
prefix: args.value_of("prefix").unwrap_or("").to_string(),
2017-03-20 17:11:03 +00:00
force: args.is_present("force"),
2017-03-20 14:38:33 +00:00
daily: args.value_of("daily").map(|v| parse_num(v, "daily backups") as usize),
weekly: args.value_of("weekly").map(|v| parse_num(v, "weekly backups") as usize),
monthly: args.value_of("monthly").map(|v| parse_num(v, "monthly backups") as usize),
yearly: args.value_of("yearly").map(|v| parse_num(v, "yearly backups") as usize),
}
}
2017-03-17 11:58:22 +00:00
if let Some(args) = args.subcommand_matches("vacuum") {
let (repository, backup, inode) = split_repo_path(args.value_of("REPO").unwrap());
if backup.is_some() || inode.is_some() {
println!("No backups or subpaths may be given here");
exit(1);
}
return Arguments::Vacuum {
repo_path: repository.to_string(),
2017-03-20 17:11:03 +00:00
force: args.is_present("force"),
2017-03-22 08:19:16 +00:00
ratio: parse_float(args.value_of("ratio").unwrap_or(&DEFAULT_VACUUM_RATIO.to_string()), "ratio") as f32
2017-03-17 11:58:22 +00:00
}
}
2017-03-17 10:03:07 +00:00
if let Some(args) = args.subcommand_matches("check") {
let (repository, backup, inode) = split_repo_path(args.value_of("PATH").unwrap());
return Arguments::Check {
repo_path: repository.to_string(),
backup_name: backup.map(|v| v.to_string()),
inode: inode.map(|v| v.to_string()),
full: args.is_present("full")
}
}
if let Some(args) = args.subcommand_matches("list") {
let (repository, backup, inode) = split_repo_path(args.value_of("PATH").unwrap());
return Arguments::List {
repo_path: repository.to_string(),
backup_name: backup.map(|v| v.to_string()),
inode: inode.map(|v| v.to_string())
}
}
if let Some(args) = args.subcommand_matches("listbundles") {
2017-03-17 11:58:22 +00:00
let (repository, backup, inode) = split_repo_path(args.value_of("REPO").unwrap());
2017-03-17 10:03:07 +00:00
if backup.is_some() || inode.is_some() {
println!("No backups or subpaths may be given here");
exit(1);
}
return Arguments::ListBundles {
repo_path: repository.to_string(),
}
}
if let Some(args) = args.subcommand_matches("info") {
let (repository, backup, inode) = split_repo_path(args.value_of("PATH").unwrap());
return Arguments::Info {
repo_path: repository.to_string(),
backup_name: backup.map(|v| v.to_string()),
inode: inode.map(|v| v.to_string())
}
}
2017-03-17 11:58:22 +00:00
if let Some(args) = args.subcommand_matches("import") {
let (repository, backup, inode) = split_repo_path(args.value_of("REPO").unwrap());
if backup.is_some() || inode.is_some() {
println!("No backups or subpaths may be given here");
exit(1);
}
return Arguments::Import {
repo_path: repository.to_string(),
2017-03-22 16:28:45 +00:00
remote_path: args.value_of("REMOTE").unwrap().to_string(),
key_files: args.values_of("key").map(|v| v.map(|k| k.to_string()).collect()).unwrap_or_else(|| vec![])
2017-03-17 11:58:22 +00:00
}
}
2017-03-18 16:22:11 +00:00
if let Some(args) = args.subcommand_matches("configure") {
let (repository, backup, inode) = split_repo_path(args.value_of("REPO").unwrap());
if backup.is_some() || inode.is_some() {
println!("No backups or subpaths may be given here");
exit(1);
}
return Arguments::Configure {
bundle_size: args.value_of("bundle_size").map(|v| (parse_num(v, "Bundle size") * 1024 * 1024) as usize),
chunker: args.value_of("chunker").map(|v| parse_chunker(v)),
compression: args.value_of("compression").map(|v| parse_compression(v)),
encryption: args.value_of("encryption").map(|v| {
if v == "none" {
None
} else {
Some(parse_public_key(v))
}
}),
hash: args.value_of("hash").map(|v| parse_hash(v)),
repo_path: repository.to_string(),
}
}
2017-03-22 16:28:45 +00:00
if let Some(args) = args.subcommand_matches("genkey") {
return Arguments::GenKey {
file: args.value_of("FILE").map(|v| v.to_string())
}
2017-03-18 16:22:11 +00:00
}
if let Some(args) = args.subcommand_matches("addkey") {
let (repository, backup, inode) = split_repo_path(args.value_of("REPO").unwrap());
if backup.is_some() || inode.is_some() {
println!("No backups or subpaths may be given here");
exit(1);
}
let generate = args.is_present("generate");
2017-03-22 16:28:45 +00:00
if !generate && !args.is_present("FILE") {
println!("Without --generate, a file containing the key pair must be given");
2017-03-18 16:22:11 +00:00
exit(1);
}
2017-03-22 16:28:45 +00:00
if generate && args.is_present("FILE") {
println!("With --generate, no file may be given");
2017-03-18 16:22:11 +00:00
exit(1);
}
return Arguments::AddKey {
repo_path: repository.to_string(),
set_default: args.is_present("set_default"),
2017-03-22 16:28:45 +00:00
file: args.value_of("FILE").map(|v| v.to_string())
2017-03-18 16:22:11 +00:00
}
}
2017-03-17 10:03:07 +00:00
if let Some(args) = args.subcommand_matches("algotest") {
return Arguments::AlgoTest {
2017-03-22 08:19:16 +00:00
bundle_size: (parse_num(args.value_of("bundle_size").unwrap_or(&DEFAULT_BUNDLE_SIZE.to_string()), "Bundle size") * 1024 * 1024) as usize,
chunker: parse_chunker(args.value_of("chunker").unwrap_or(DEFAULT_CHUNKER)),
compression: parse_compression(args.value_of("compression").unwrap_or(DEFAULT_COMPRESSION)),
2017-03-18 16:22:11 +00:00
encrypt: args.is_present("encrypt"),
2017-03-22 08:19:16 +00:00
hash: parse_hash(args.value_of("hash").unwrap_or(DEFAULT_HASH)),
2017-03-17 10:03:07 +00:00
file: args.value_of("FILE").unwrap().to_string(),
}
2017-03-17 06:15:19 +00:00
}
2017-03-17 10:03:07 +00:00
error!("No subcommand given");
exit(1);
2017-03-17 06:15:19 +00:00
}