use ::prelude::*; use super::*; use std::path::Path; use clap::{App, AppSettings, Arg, SubCommand}; pub enum Arguments { Init { repo_path: String, bundle_size: usize, chunker: ChunkerType, compression: Option, encryption: bool, hash: HashMethod, remote_path: String }, Backup { repo_path: String, backup_name: String, src_path: String, full: bool, reference: Option, same_device: bool, excludes: Vec, excludes_from: Option, no_default_excludes: bool, tar: bool }, Restore { repo_path: String, backup_name: String, inode: Option, dst_path: String, tar: bool }, Remove { repo_path: String, backup_name: String, inode: Option }, Prune { repo_path: String, prefix: String, daily: Option, weekly: Option, monthly: Option, yearly: Option, force: bool }, Vacuum { repo_path: String, ratio: f32, force: bool }, Check { repo_path: String, backup_name: Option, inode: Option, full: bool }, List { repo_path: String, backup_name: Option, inode: Option }, Info { repo_path: String, backup_name: Option, inode: Option }, Mount { repo_path: String, backup_name: Option, inode: Option, mount_point: String }, Versions { 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 }, BundleList { repo_path: String }, BundleInfo { repo_path: String, bundle_id: BundleId }, Import { repo_path: String, remote_path: String, key_files: Vec }, Config { repo_path: String, bundle_size: Option, chunker: Option, compression: Option>, encryption: Option>, hash: Option }, GenKey { file: Option }, AddKey { repo_path: String, file: Option, set_default: bool }, AlgoTest { file: String, bundle_size: usize, chunker: ChunkerType, compression: Option, encrypt: bool, hash: HashMethod } } fn parse_repo_path(repo_path: &str, existing: bool, backup_restr: Option, path_restr: Option) -> Result<(&str, Option<&str>, Option<&str>), String> { let mut parts = repo_path.splitn(3, "::"); let mut repo = parts.next().unwrap_or(&DEFAULT_REPOSITORY); if repo.is_empty() { repo = &DEFAULT_REPOSITORY; } if existing && !Path::new(repo).join("config.yaml").exists() { return Err("The specified repository does not exist".to_string()); } if !existing && Path::new(repo).exists() { return Err("The specified repository already exists".to_string()); } let mut backup = parts.next(); if let Some(val) = backup { if val.is_empty() { backup = None } } let mut path = parts.next(); if let Some(val) = path { if val.is_empty() { path = None } } if let Some(restr) = backup_restr { if !restr && backup.is_some() { return Err("No backup may be given here".to_string()); } if restr && backup.is_none() { return Err("A backup must be specified".to_string()); } } if let Some(restr) = path_restr { if !restr && path.is_some() { return Err("No subpath may be given here".to_string()); } if restr && path.is_none() { return Err("A subpath must be specified".to_string()); } } Ok((repo, backup, path)) } #[allow(unknown_lints,needless_pass_by_value)] fn validate_repo_path(repo_path: String, existing: bool, backup_restr: Option, path_restr: Option) -> Result<(), String> { parse_repo_path(&repo_path, existing, backup_restr, path_restr).map(|_| ()) } fn parse_num(num: &str) -> Result { if let Ok(num) = num.parse::() { Ok(num) } else { Err("Must be a number".to_string()) } } #[allow(unknown_lints,needless_pass_by_value)] fn validate_num(val: String) -> Result<(), String> { parse_num(&val).map(|_| ()) } fn parse_chunker(val: &str) -> Result { if let Ok(chunker) = ChunkerType::from_string(val) { Ok(chunker) } else { Err("Invalid chunker method/size".to_string()) } } #[allow(unknown_lints,needless_pass_by_value)] fn validate_chunker(val: String) -> Result<(), String> { parse_chunker(&val).map(|_| ()) } fn parse_compression(val: &str) -> Result, String> { if val == "none" { return Ok(None) } if let Ok(compression) = Compression::from_string(val) { Ok(Some(compression)) } else { Err("Invalid compression method/level".to_string()) } } #[allow(unknown_lints,needless_pass_by_value)] fn validate_compression(val: String) -> Result<(), String> { parse_compression(&val).map(|_| ()) } fn parse_public_key(val: &str) -> Result, String> { if val.to_lowercase() == "none" { return Ok(None); } let bytes = match parse_hex(val) { Ok(bytes) => bytes, Err(_) => { return Err("Invalid hexadecimal".to_string()); } }; if let Some(key) = PublicKey::from_slice(&bytes) { Ok(Some(key)) } else { return Err("Invalid key".to_string()) } } #[allow(unknown_lints,needless_pass_by_value)] fn validate_public_key(val: String) -> Result<(), String> { parse_public_key(&val).map(|_| ()) } fn parse_hash(val: &str) -> Result { if let Ok(hash) = HashMethod::from(val) { Ok(hash) } else { Err("Invalid hash method".to_string()) } } #[allow(unknown_lints,needless_pass_by_value)] fn validate_hash(val: String) -> Result<(), String> { parse_hash(&val).map(|_| ()) } fn parse_bundle_id(val: &str) -> Result { if let Ok(hash) = Hash::from_string(val) { Ok(BundleId(hash)) } else { error!("Invalid bundle id: {}", val); Err(ErrorCode::InvalidArgs) } } #[allow(unknown_lints,needless_pass_by_value)] fn validate_existing_path(val: String) -> Result<(), String> { if !Path::new(&val).exists() { Err("Path does not exist".to_string()) } else { Ok(()) } } #[allow(unknown_lints,cyclomatic_complexity)] pub fn parse() -> Result { let args = App::new("zvault").version(crate_version!()).author(crate_authors!(",\n")).about(crate_description!()) .settings(&[AppSettings::AllowMissingPositional, AppSettings::VersionlessSubcommands, AppSettings::SubcommandRequiredElseHelp]) .global_settings(&[AppSettings::UnifiedHelpMessage, AppSettings::ColoredHelp, AppSettings::ColorAuto]) .subcommand(SubCommand::with_name("init").about("Initialize a new repository") .arg(Arg::from_usage("[bundle_size] --bundle-size [SIZE] 'Set the target bundle size in MiB'") .default_value(DEFAULT_BUNDLE_SIZE_STR).validator(validate_num)) .arg(Arg::from_usage("--chunker [CHUNKER] 'Set the chunker algorithm and target chunk size'") .default_value(DEFAULT_CHUNKER).validator(validate_chunker)) .arg(Arg::from_usage("-c --compression [COMPRESSION] 'Set the compression method and level'") .default_value(DEFAULT_COMPRESSION).validator(validate_compression)) .arg(Arg::from_usage("-e --encrypt 'Generate a keypair and enable encryption'")) .arg(Arg::from_usage("--hash [HASH] 'Set the hash method'") .default_value(DEFAULT_HASH).validator(validate_hash)) .arg(Arg::from_usage("-r --remote 'Set the path to the mounted remote storage'") .validator(validate_existing_path)) .arg(Arg::from_usage("[REPO] 'The path for the new repository'") .default_value("").validator(|val| validate_repo_path(val, false, Some(false), Some(false))))) .subcommand(SubCommand::with_name("backup").about("Create a new backup") .arg(Arg::from_usage("--full 'Create a full backup without using a reference'")) .arg(Arg::from_usage("[reference] --ref [REF] 'Base the new backup on this reference'") .conflicts_with("full")) .arg(Arg::from_usage("[cross_device] -x --xdev 'Allow to cross filesystem boundaries'")) .arg(Arg::from_usage("-e --exclude [PATTERN]... 'Exclude this path or file pattern'")) .arg(Arg::from_usage("[excludes_from] --excludes-from [FILE] 'Read the list of excludes from this file'")) .arg(Arg::from_usage("[no_default_excludes] --no-default-excludes 'Do not load the default excludes file'")) .arg(Arg::from_usage("--tar 'Read the source data from a tar file'") .conflicts_with_all(&["reference", "exclude", "excludes_from"])) .arg(Arg::from_usage(" 'Source path to backup'") .validator(validate_existing_path)) .arg(Arg::from_usage(" 'Backup path, [repository]::backup'") .validator(|val| validate_repo_path(val, true, Some(true), Some(false))))) .subcommand(SubCommand::with_name("restore").about("Restore a backup or subtree") .arg(Arg::from_usage("--tar 'Restore in form of a tar file'")) .arg(Arg::from_usage(" 'The backup/subtree path, [repository]::backup[::subtree]'") .validator(|val| validate_repo_path(val, true, Some(true), None))) .arg(Arg::from_usage(" 'Destination path for backup'") .validator(validate_existing_path))) .subcommand(SubCommand::with_name("remove").aliases(&["rm", "delete", "del"]).about("Remove a backup or a subtree") .arg(Arg::from_usage(" 'The backup/subtree path, [repository]::backup[::subtree]'") .validator(|val| validate_repo_path(val, true, Some(true), None)))) .subcommand(SubCommand::with_name("prune").about("Remove backups based on age") .arg(Arg::from_usage("-p --prefix [PREFIX] 'Only consider backups starting with this prefix'")) .arg(Arg::from_usage("-d --daily [NUM] 'Keep this number of daily backups'") .default_value("0").validator(validate_num)) .arg(Arg::from_usage("-w --weekly [NUM] 'Keep this number of weekly backups'") .default_value("0").validator(validate_num)) .arg(Arg::from_usage("-m --monthly [NUM] 'Keep this number of monthly backups'") .default_value("0").validator(validate_num)) .arg(Arg::from_usage("-y --yearly [NUM] 'Keep this number of yearly backups'") .default_value("0").validator(validate_num)) .arg(Arg::from_usage("-f --force 'Actually run the prune instead of simulating it'")) .arg(Arg::from_usage("[REPO] 'Path of the repository'") .validator(|val| validate_repo_path(val, true, Some(false), Some(false))))) .subcommand(SubCommand::with_name("vacuum").about("Reclaim space by rewriting bundles") .arg(Arg::from_usage("-r --ratio [NUM] 'Ratio in % of unused space in a bundle to rewrite that bundle'") .default_value(DEFAULT_VACUUM_RATIO_STR).validator(validate_num)) .arg(Arg::from_usage("-f --force 'Actually run the vacuum instead of simulating it'")) .arg(Arg::from_usage("[REPO] 'Path of the repository'") .validator(|val| validate_repo_path(val, true, Some(false), Some(false))))) .subcommand(SubCommand::with_name("check").about("Check the repository, a backup or a backup subtree") .arg(Arg::from_usage("--full 'Also check file contents (slow)'")) .arg(Arg::from_usage("[PATH] 'Path of the repository/backup/subtree, [repository][::backup[::subtree]]'") .validator(|val| validate_repo_path(val, true, None, None)))) .subcommand(SubCommand::with_name("list").alias("ls").about("List backups or backup contents") .arg(Arg::from_usage("[PATH] 'Path of the repository/backup/subtree, [repository][::backup[::subtree]]'") .validator(|val| validate_repo_path(val, true, None, None)))) .subcommand(SubCommand::with_name("mount").about("Mount the repository, a backup or a subtree") .arg(Arg::from_usage("[PATH] 'Path of the repository/backup/subtree, [repository][::backup[::subtree]]'") .validator(|val| validate_repo_path(val, true, None, None))) .arg(Arg::from_usage(" 'Existing mount point'") .validator(validate_existing_path))) .subcommand(SubCommand::with_name("bundlelist").about("List bundles in a repository") .arg(Arg::from_usage("[REPO] 'Path of the repository'") .validator(|val| validate_repo_path(val, true, Some(false), Some(false))))) .subcommand(SubCommand::with_name("bundleinfo").about("Display information on a bundle") .arg(Arg::from_usage("[REPO] 'Path of the repository'") .validator(|val| validate_repo_path(val, true, Some(false), Some(false)))) .arg(Arg::from_usage(" 'Id of the bundle'"))) .subcommand(SubCommand::with_name("import").about("Reconstruct a repository from the remote storage") .arg(Arg::from_usage("-k --key [FILE]... 'Key file needed to read the bundles'")) .arg(Arg::from_usage(" 'Remote repository path'") .validator(validate_existing_path)) .arg(Arg::from_usage("[REPO] 'The path for the new repository'") .validator(|val| validate_repo_path(val, false, Some(false), Some(false))))) .subcommand(SubCommand::with_name("info").about("Display information on a repository, a backup or a subtree") .arg(Arg::from_usage("[PATH] 'Path of the repository/backup/subtree, [repository][::backup[::subtree]]'") .validator(|val| validate_repo_path(val, true, None, None)))) .subcommand(SubCommand::with_name("analyze").about("Analyze the used and reclaimable space of bundles") .arg(Arg::from_usage("[REPO] 'Path of the repository'") .validator(|val| validate_repo_path(val, true, Some(false), Some(false))))) .subcommand(SubCommand::with_name("versions").about("Find different versions of a file in all backups") .arg(Arg::from_usage("[REPO] 'Path of the repository'") .validator(|val| validate_repo_path(val, true, Some(false), Some(false)))) .arg(Arg::from_usage(" 'Path of the file'"))) .subcommand(SubCommand::with_name("diff").about("Display differences between two backup versions") .arg(Arg::from_usage(" 'Old version, [repository]::backup[::subpath]'") .validator(|val| validate_repo_path(val, true, Some(true), None))) .arg(Arg::from_usage(" 'New version, [repository]::backup[::subpath]'") .validator(|val| validate_repo_path(val, true, Some(true), None)))) .subcommand(SubCommand::with_name("config").about("Display or change the configuration") .arg(Arg::from_usage("[bundle_size] --bundle-size [SIZE] 'Set the target bundle size in MiB'") .validator(validate_num)) .arg(Arg::from_usage("--chunker [CHUNKER] 'Set the chunker algorithm and target chunk size'") .validator(validate_chunker)) .arg(Arg::from_usage("-c --compression [COMPRESSION] 'Set the compression method and level'") .validator(validate_compression)) .arg(Arg::from_usage("-e --encryption [PUBLIC_KEY] 'The public key to use for encryption'") .validator(validate_public_key)) .arg(Arg::from_usage("--hash [HASH] 'Set the hash method'") .validator(validate_hash)) .arg(Arg::from_usage("[REPO] 'Path of the repository'") .validator(|val| validate_repo_path(val, true, Some(false), Some(false))))) .subcommand(SubCommand::with_name("genkey").about("Generate a new key pair") .arg(Arg::from_usage("[FILE] 'Destination file for the keypair'"))) .subcommand(SubCommand::with_name("addkey").about("Add a key pair to the repository") .arg(Arg::from_usage("-g --generate 'Generate a new key pair'") .conflicts_with("FILE")) .arg(Arg::from_usage("[set_default] --default -d 'Set the key pair as default'")) .arg(Arg::from_usage("[FILE] 'File containing the keypair'") .required_unless("generate").validator(validate_existing_path)) .arg(Arg::from_usage("[REPO] 'Path of the repository'") .validator(|val| validate_repo_path(val, true, Some(false), Some(false))))) .subcommand(SubCommand::with_name("algotest").about("Test a specific algorithm combination") .arg(Arg::from_usage("[bundle_size] --bundle-size [SIZE] 'Set the target bundle size in MiB'") .default_value(DEFAULT_BUNDLE_SIZE_STR).validator(validate_num)) .arg(Arg::from_usage("--chunker [CHUNKER] 'Set the chunker algorithm and target chunk size'") .default_value(DEFAULT_CHUNKER).validator(validate_chunker)) .arg(Arg::from_usage("-c --compression [COMPRESSION] 'Set the compression method and level'") .default_value(DEFAULT_COMPRESSION).validator(validate_compression)) .arg(Arg::from_usage("-e --encrypt 'Generate a keypair and enable encryption'")) .arg(Arg::from_usage("--hash [HASH] 'Set the hash method'") .default_value(DEFAULT_HASH).validator(validate_hash)) .arg(Arg::from_usage(" 'File with test data'") .validator(validate_existing_path))).get_matches(); if let Some(args) = args.subcommand_matches("init") { let (repository, _backup, _inode) = parse_repo_path(args.value_of("REPO").unwrap_or(""), false, Some(false), Some(false)).unwrap(); return Ok(Arguments::Init { bundle_size: (parse_num(args.value_of("bundle_size").unwrap()).unwrap() * 1024 * 1024) as usize, chunker: parse_chunker(args.value_of("chunker").unwrap()).unwrap(), compression: parse_compression(args.value_of("compression").unwrap()).unwrap(), encryption: args.is_present("encrypt"), hash: parse_hash(args.value_of("hash").unwrap()).unwrap(), repo_path: repository.to_string(), remote_path: args.value_of("remote").unwrap().to_string() }) } if let Some(args) = args.subcommand_matches("backup") { let (repository, backup, _inode) = parse_repo_path(args.value_of("BACKUP").unwrap(), true, Some(true), Some(false)).unwrap(); return Ok(Arguments::Backup { repo_path: repository.to_string(), backup_name: backup.unwrap().to_string(), full: args.is_present("full"), same_device: !args.is_present("cross_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()), no_default_excludes: args.is_present("no_default_excludes"), tar: args.is_present("tar") }) } if let Some(args) = args.subcommand_matches("restore") { let (repository, backup, inode) = parse_repo_path(args.value_of("BACKUP").unwrap(), true, Some(true), None).unwrap(); return Ok(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(), tar: args.is_present("tar") }) } if let Some(args) = args.subcommand_matches("remove") { let (repository, backup, inode) = parse_repo_path(args.value_of("BACKUP").unwrap(), true, Some(true), None).unwrap(); return Ok(Arguments::Remove { repo_path: repository.to_string(), backup_name: backup.unwrap().to_string(), inode: inode.map(|v| v.to_string()) }) } if let Some(args) = args.subcommand_matches("prune") { let (repository, _backup, _inode) = parse_repo_path(args.value_of("REPO").unwrap_or(""), true, Some(false), Some(false)).unwrap(); return Ok(Arguments::Prune { repo_path: repository.to_string(), prefix: args.value_of("prefix").unwrap_or("").to_string(), force: args.is_present("force"), daily: match args.value_of("daily") { None => None, Some(v) => Some(parse_num(v).unwrap() as usize) }, weekly: match args.value_of("weekly") { None => None, Some(v) => Some(parse_num(v).unwrap() as usize) }, monthly: match args.value_of("monthly") { None => None, Some(v) => Some(parse_num(v).unwrap() as usize) }, yearly: match args.value_of("yearly") { None => None, Some(v) => Some(parse_num(v).unwrap() as usize) } }) } if let Some(args) = args.subcommand_matches("vacuum") { let (repository, _backup, _inode) = parse_repo_path(args.value_of("REPO").unwrap_or(""), true, Some(false), Some(false)).unwrap(); return Ok(Arguments::Vacuum { repo_path: repository.to_string(), force: args.is_present("force"), ratio: parse_num(args.value_of("ratio").unwrap()).unwrap() as f32 / 100.0 }) } if let Some(args) = args.subcommand_matches("check") { let (repository, backup, inode) = parse_repo_path(args.value_of("PATH").unwrap_or(""), true, None, None).unwrap(); return Ok(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) = parse_repo_path(args.value_of("PATH").unwrap_or(""), true, None, None).unwrap(); return Ok(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("bundlelist") { let (repository, _backup, _inode) = parse_repo_path(args.value_of("REPO").unwrap_or(""), true, Some(false), Some(false)).unwrap(); return Ok(Arguments::BundleList { repo_path: repository.to_string(), }) } if let Some(args) = args.subcommand_matches("bundleinfo") { let (repository, _backup, _inode) = parse_repo_path(args.value_of("REPO").unwrap_or(""), true, Some(false), Some(false)).unwrap(); return Ok(Arguments::BundleInfo { repo_path: repository.to_string(), bundle_id: try!(parse_bundle_id(args.value_of("BUNDLE").unwrap())) }) } if let Some(args) = args.subcommand_matches("info") { let (repository, backup, inode) = parse_repo_path(args.value_of("PATH").unwrap_or(""), true, None, None).unwrap(); return Ok(Arguments::Info { 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("mount") { let (repository, backup, inode) = parse_repo_path(args.value_of("PATH").unwrap_or(""), true, None, None).unwrap(); return Ok(Arguments::Mount { repo_path: repository.to_string(), backup_name: backup.map(|v| v.to_string()), inode: inode.map(|v| v.to_string()), mount_point: args.value_of("MOUNTPOINT").unwrap().to_string() }) } if let Some(args) = args.subcommand_matches("versions") { let (repository, _backup, _inode) = parse_repo_path(args.value_of("REPO").unwrap_or(""), true, Some(false), Some(false)).unwrap(); return Ok(Arguments::Versions { repo_path: repository.to_string(), 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(), true, Some(true), None).unwrap(); let (repository_new, backup_new, inode_new) = parse_repo_path(args.value_of("NEW").unwrap(), true, Some(true), None).unwrap(); return Ok(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_or(""), true, Some(false), Some(false)).unwrap(); return Ok(Arguments::Analyze { repo_path: repository.to_string() }) } if let Some(args) = args.subcommand_matches("import") { let (repository, _backup, _inode) = parse_repo_path(args.value_of("REPO").unwrap_or(""), false, Some(false), Some(false)).unwrap(); return Ok(Arguments::Import { repo_path: repository.to_string(), 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![]) }) } if let Some(args) = args.subcommand_matches("config") { let (repository, _backup, _inode) = parse_repo_path(args.value_of("REPO").unwrap_or(""), true, Some(false), Some(false)).unwrap(); return Ok(Arguments::Config { bundle_size: match args.value_of("bundle_size") { None => None, Some(v) => Some((parse_num(v).unwrap() * 1024 * 1024) as usize) }, chunker: match args.value_of("chunker") { None => None, Some(v) => Some(parse_chunker(v).unwrap()) }, compression: match args.value_of("compression") { None => None, Some(v) => Some(parse_compression(v).unwrap()) }, encryption: match args.value_of("encryption") { None => None, Some(v) => Some(parse_public_key(v).unwrap()) }, hash: match args.value_of("hash") { None => None, Some(v) => Some(parse_hash(v).unwrap()) }, repo_path: repository.to_string(), }) } if let Some(args) = args.subcommand_matches("genkey") { return Ok(Arguments::GenKey { file: args.value_of("FILE").map(|v| v.to_string()) }) } if let Some(args) = args.subcommand_matches("addkey") { let (repository, _backup, _inode) = parse_repo_path(args.value_of("REPO").unwrap_or(""), true, Some(false), Some(false)).unwrap(); return Ok(Arguments::AddKey { repo_path: repository.to_string(), set_default: args.is_present("set_default"), file: args.value_of("FILE").map(|v| v.to_string()) }) } if let Some(args) = args.subcommand_matches("algotest") { return Ok(Arguments::AlgoTest { bundle_size: (parse_num(args.value_of("bundle_size").unwrap()).unwrap() * 1024 * 1024) as usize, chunker: parse_chunker(args.value_of("chunker").unwrap()).unwrap(), compression: parse_compression(args.value_of("compression").unwrap()).unwrap(), encrypt: args.is_present("encrypt"), hash: parse_hash(args.value_of("hash").unwrap()).unwrap(), file: args.value_of("FILE").unwrap().to_string(), }) } error!("No subcommand given"); Err(ErrorCode::InvalidArgs) }