From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 4BA5F939BF for ; Tue, 9 Apr 2024 14:15:55 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 2E8261D2D0 for ; Tue, 9 Apr 2024 14:15:25 +0200 (CEST) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [94.136.29.106]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS for ; Tue, 9 Apr 2024 14:15:24 +0200 (CEST) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 19FB64170C for ; Tue, 9 Apr 2024 14:15:24 +0200 (CEST) From: Filip Schauer To: pbs-devel@lists.proxmox.com Date: Tue, 9 Apr 2024 14:14:22 +0200 Message-Id: <20240409121423.168627-9-f.schauer@proxmox.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240409121423.168627-1-f.schauer@proxmox.com> References: <20240409121423.168627-1-f.schauer@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL -0.078 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Subject: [pbs-devel] [PATCH v7 vma-to-pbs 8/9] switch argument handling from clap to pico-args X-BeenThere: pbs-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Backup Server development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 09 Apr 2024 12:15:55 -0000 Signed-off-by: Filip Schauer --- Cargo.toml | 2 +- src/main.rs | 238 ++++++++++++++++++++++++++++--------------------- src/vma2pbs.rs | 4 +- 3 files changed, 139 insertions(+), 105 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fa574c9..fb2c641 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [dependencies] anyhow = "1.0" bincode = "1.3" -clap = { version = "4.0.32", features = ["cargo", "env"] } +pico-args = "0.4" md5 = "0.7.0" scopeguard = "1.1.0" serde = "1.0" diff --git a/src/main.rs b/src/main.rs index e2aed8e..26b0ae9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,91 +1,106 @@ -use anyhow::{Context, Error}; -use clap::{command, Arg, ArgAction}; +use std::ffi::OsString; + +use anyhow::{bail, Context, Error}; use proxmox_sys::linux::tty; mod vma; mod vma2pbs; use vma2pbs::{backup_vma_to_pbs, BackupVmaToPbsArgs}; -fn main() -> Result<(), Error> { - let matches = command!() - .arg( - Arg::new("repository") - .long("repository") - .value_name("auth_id@host:port:datastore") - .help("Repository URL") - .required(true), - ) - .arg( - Arg::new("vmid") - .long("vmid") - .value_name("VMID") - .help("Backup ID") - .required(true), - ) - .arg( - Arg::new("fingerprint") - .long("fingerprint") - .value_name("FINGERPRINT") - .help("Proxmox Backup Server Fingerprint") - .env("PBS_FINGERPRINT"), - ) - .arg( - Arg::new("keyfile") - .long("keyfile") - .value_name("KEYFILE") - .help("Key file"), - ) - .arg( - Arg::new("master_keyfile") - .long("master_keyfile") - .value_name("MASTER_KEYFILE") - .help("Master key file"), - ) - .arg( - Arg::new("compress") - .long("compress") - .short('c') - .help("Compress the Backup") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new("encrypt") - .long("encrypt") - .short('e') - .help("Encrypt the Backup") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new("password-file") - .long("password-file") - .value_name("PASSWORD_FILE") - .help("Password file"), - ) - .arg( - Arg::new("key-password-file") - .long("key-password-file") - .value_name("KEY_PASSWORD_FILE") - .help("Key password file"), - ) - .arg(Arg::new("vma_file")) - .get_matches(); - - let pbs_repository = matches.get_one::("repository").unwrap().to_string(); - let vmid = matches.get_one::("vmid").unwrap().to_string(); - - let fingerprint = matches - .get_one::("fingerprint") - .context("Fingerprint not set. Use $PBS_FINGERPRINT or --fingerprint")? - .to_string(); - - let keyfile = matches.get_one::("keyfile"); - let master_keyfile = matches.get_one::("master_keyfile"); - let compress = matches.get_flag("compress"); - let encrypt = matches.get_flag("encrypt"); - - let vma_file_path = matches.get_one::("vma_file"); - - let password_file = matches.get_one::("password-file"); +const CMD_HELP: &str = "\ +Usage: vma-to-pbs [OPTIONS] --repository --vmid [vma_file] + +Arguments: + [vma_file] + +Options: + --repository + Repository URL + --vmid + Backup ID + --fingerprint + Proxmox Backup Server Fingerprint [env: PBS_FINGERPRINT=] + --keyfile + Key file + --master_keyfile + Master key file + -c, --compress + Compress the Backup + -e, --encrypt + Encrypt the Backup + --password_file + Password file + --key_password_file + Key password file + -h, --help + Print help + -V, --version + Print version +"; + +fn parse_args() -> Result { + let mut args: Vec<_> = std::env::args_os().collect(); + args.remove(0); // remove the executable path. + + let mut first_later_args_index = 0; + let options = ["-h", "--help", "-c", "--compress", "-e", "--encrypt"]; + + for (i, arg) in args.iter().enumerate() { + if let Some(arg) = arg.to_str() { + if arg.starts_with('-') { + if arg == "--" { + args.remove(i); + first_later_args_index = i; + break; + } + + first_later_args_index = i + 1; + + if !options.contains(&arg) { + first_later_args_index += 1; + } + } + } + } + + let forwarded_args = if first_later_args_index > args.len() { + Vec::new() + } else { + args.split_off(first_later_args_index) + }; + + let mut args = pico_args::Arguments::from_vec(args); + + if args.contains(["-h", "--help"]) { + print!("{CMD_HELP}"); + std::process::exit(0); + } + + let pbs_repository = args.value_from_str("--repository")?; + let vmid = args.value_from_str("--vmid")?; + let fingerprint = args.opt_value_from_str("--fingerprint")?; + let keyfile = args.opt_value_from_str("--keyfile")?; + let master_keyfile = args.opt_value_from_str("--master_keyfile")?; + let compress = args.contains(["-c", "--compress"]); + let encrypt = args.contains(["-e", "--encrypt"]); + let password_file: Option = args.opt_value_from_str("--password-file")?; + let key_password_file: Option = args.opt_value_from_str("--key-password-file")?; + + if !args.finish().is_empty() { + bail!("unexpected extra arguments, use '-h' for usage"); + } + + let fingerprint = match fingerprint { + Some(v) => v, + None => std::env::var("PBS_FINGERPRINT") + .context("Fingerprint not set. Use $PBS_FINGERPRINT or --fingerprint")?, + }; + + if forwarded_args.len() > 1 { + bail!("too many arguments"); + } + + let vma_file_path = forwarded_args.first(); let pbs_password = match password_file { Some(password_file) => { @@ -101,46 +116,65 @@ fn main() -> Result<(), Error> { password } - None => String::from_utf8(tty::read_password("Password: ")?)?, + None => { + if vma_file_path.is_none() { + bail!( + "Please use --password-file to provide the password \ + when passing the VMA file to stdin" + ); + } + + String::from_utf8(tty::read_password("Password: ")?)? + } }; let key_password = match keyfile { - Some(_) => { - let key_password_file = matches.get_one::("key_password_file"); - - Some(match key_password_file { - Some(key_password_file) => { - let mut key_password = std::fs::read_to_string(key_password_file) - .context("Could not read key password file")?; - - if key_password.ends_with('\n') || key_password.ends_with('\r') { + Some(_) => Some(match key_password_file { + Some(key_password_file) => { + let mut key_password = std::fs::read_to_string(key_password_file) + .context("Could not read key password file")?; + + if key_password.ends_with('\n') || key_password.ends_with('\r') { + key_password.pop(); + if key_password.ends_with('\r') { key_password.pop(); - if key_password.ends_with('\r') { - key_password.pop(); - } } + } - key_password + key_password + } + None => { + if vma_file_path.is_none() { + bail!( + "Please use --key-password-file to provide the password \ + when passing the VMA file to stdin" + ); } - None => String::from_utf8(tty::read_password("Key Password: ")?)?, - }) - } + + String::from_utf8(tty::read_password("Key Password: ")?)? + } + }), None => None, }; - let args = BackupVmaToPbsArgs { + let options = BackupVmaToPbsArgs { vma_file_path: vma_file_path.cloned(), pbs_repository, backup_id: vmid, pbs_password, - keyfile: keyfile.cloned(), + keyfile, key_password, - master_keyfile: master_keyfile.cloned(), + master_keyfile, fingerprint, compress, encrypt, }; + Ok(options) +} + +fn main() -> Result<(), Error> { + let args = parse_args()?; backup_vma_to_pbs(args)?; Ok(()) diff --git a/src/vma2pbs.rs b/src/vma2pbs.rs index 8422502..ed573d8 100644 --- a/src/vma2pbs.rs +++ b/src/vma2pbs.rs @@ -1,7 +1,7 @@ use std::cell::RefCell; use std::collections::hash_map::Entry; use std::collections::HashMap; -use std::ffi::{c_char, CStr, CString}; +use std::ffi::{c_char, CStr, CString, OsString}; use std::fs::File; use std::io::{stdin, BufRead, BufReader, Read}; use std::ptr; @@ -22,7 +22,7 @@ use crate::vma::VmaReader; const VMA_CLUSTER_SIZE: usize = 65536; pub struct BackupVmaToPbsArgs { - pub vma_file_path: Option, + pub vma_file_path: Option, pub pbs_repository: String, pub backup_id: String, pub pbs_password: String, -- 2.39.2