* [pbs-devel] [PATCH proxmox-backup 0/3] add proxmox-backup-recovery binary @ 2020-12-15 8:16 Hannes Laimer 2020-12-15 8:16 ` [pbs-devel] [PATCH proxmox-backup 1/3] add inspection of chunks Hannes Laimer ` (2 more replies) 0 siblings, 3 replies; 5+ messages in thread From: Hannes Laimer @ 2020-12-15 8:16 UTC (permalink / raw) To: pbs-devel Adds a (standalone) binary that can be used for inspection of index, blob and chunk files. It can also be used to restore data from an index files without relying on a running pbs instance. Hannes Laimer (3): add inspection of chunks add inspection of .blob, .fidx and .didx files add restore of index files Makefile | 3 +- src/bin/proxmox-backup-recovery.rs | 137 +++++++++ src/bin/proxmox_backup_recovery/inspect.rs | 308 +++++++++++++++++++++ src/bin/proxmox_backup_recovery/mod.rs | 2 + 4 files changed, 449 insertions(+), 1 deletion(-) create mode 100644 src/bin/proxmox-backup-recovery.rs create mode 100644 src/bin/proxmox_backup_recovery/inspect.rs create mode 100644 src/bin/proxmox_backup_recovery/mod.rs -- 2.20.1 ^ permalink raw reply [flat|nested] 5+ messages in thread
* [pbs-devel] [PATCH proxmox-backup 1/3] add inspection of chunks 2020-12-15 8:16 [pbs-devel] [PATCH proxmox-backup 0/3] add proxmox-backup-recovery binary Hannes Laimer @ 2020-12-15 8:16 ` Hannes Laimer 2020-12-15 10:14 ` Thomas Lamprecht 2020-12-15 8:16 ` [pbs-devel] [PATCH proxmox-backup 2/3] add inspection of .blob, .fidx and .didx files Hannes Laimer 2020-12-15 8:16 ` [pbs-devel] [PATCH proxmox-backup 3/3] add restore of index files Hannes Laimer 2 siblings, 1 reply; 5+ messages in thread From: Hannes Laimer @ 2020-12-15 8:16 UTC (permalink / raw) To: pbs-devel Signed-off-by: Hannes Laimer <h.laimer@proxmox.com> --- Adds possibility to inspect chunks and find indexes that reference the chunk. Options: - chunk: path to the chunk file - [opt] decode: path to a file or to stdout(-), if specified, the chunk will be decoded into the specified location - [opt] keyfile: path to a keyfile, needed if decode is specified and the data was encrypted - [opt] reference-filter: path in which indexes that reference the chunk should be searched, can be a group, snapshot or the whole datastore, if not specified no references will be searched Makefile | 3 +- src/bin/proxmox-backup-recovery.rs | 32 ++++ src/bin/proxmox_backup_recovery/inspect.rs | 195 +++++++++++++++++++++ src/bin/proxmox_backup_recovery/mod.rs | 2 + 4 files changed, 231 insertions(+), 1 deletion(-) create mode 100644 src/bin/proxmox-backup-recovery.rs create mode 100644 src/bin/proxmox_backup_recovery/inspect.rs create mode 100644 src/bin/proxmox_backup_recovery/mod.rs diff --git a/Makefile b/Makefile index a1af9d51..a9c2a711 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,8 @@ USR_BIN := \ # Binaries usable by admins USR_SBIN := \ - proxmox-backup-manager + proxmox-backup-manager \ + proxmox-backup-recovery \ # Binaries for services: SERVICE_BIN := \ diff --git a/src/bin/proxmox-backup-recovery.rs b/src/bin/proxmox-backup-recovery.rs new file mode 100644 index 00000000..ae8c18d5 --- /dev/null +++ b/src/bin/proxmox-backup-recovery.rs @@ -0,0 +1,32 @@ +use anyhow::Error; +use proxmox::api::schema::{Schema, StringSchema}; +use proxmox::api::cli::*; + +use proxmox::sys::linux::tty; +use proxmox_backup_recovery::*; +mod proxmox_backup_recovery; + +pub fn get_encryption_key_password() -> Result<Vec<u8>, Error> { + tty::read_password("Encryption Key Password: ") +} + +pub const PATH_SCHEMA: Schema = StringSchema::new("Path to a file or a directory").schema(); + +pub const KEYFILE_SCHEMA: Schema = StringSchema::new( + "Path to encryption key. If the data was encrypted, this key will be used for decryption.", +) +.schema(); + +fn main() { + proxmox_backup::tools::setup_safe_path_env(); + + let cmd_def = CliCommandMap::new() + .insert("inspect", inspect_commands()); + + let rpcenv = CliEnvironment::new(); + run_cli_command( + cmd_def, + rpcenv, + Some(|future| proxmox_backup::tools::runtime::main(future)), + ); +} diff --git a/src/bin/proxmox_backup_recovery/inspect.rs b/src/bin/proxmox_backup_recovery/inspect.rs new file mode 100644 index 00000000..6bf4a0a3 --- /dev/null +++ b/src/bin/proxmox_backup_recovery/inspect.rs @@ -0,0 +1,195 @@ +use std::collections::HashSet; +use std::fs::File; +use std::io::Write; +use std::path::Path; + +use anyhow::Error; +use proxmox::api::cli::{ + format_and_print_result, get_output_format, CliCommand, CliCommandMap, CommandLineInterface, +}; +use proxmox::api::{api, cli::*, RpcEnvironment}; +use serde_json::{json, Value}; +use walkdir::WalkDir; + +use proxmox_backup::api2::types::SHA256_HEX_REGEX; +use proxmox_backup::backup::{ + load_and_decrypt_key, CryptConfig, DataBlob, DynamicIndexReader, FixedIndexReader, IndexFile, +}; +use proxmox_backup::tools; + +use crate::{get_encryption_key_password, KEYFILE_SCHEMA, PATH_SCHEMA}; + +/// Decodes a blob and writes its content either to stdout or into a file +fn decode_blob( + output_path: Option<&Path>, + key_file: Option<&Path>, + digest: Option<&[u8; 32]>, + blob: &DataBlob, +) -> Result<(), Error> { + let mut crypt_conf_opt = None; + let crypt_conf; + + if blob.is_encrypted() && key_file.is_some() { + let (key, _created, _fingerprint) = + load_and_decrypt_key(&key_file.unwrap(), &get_encryption_key_password)?; + crypt_conf = CryptConfig::new(key)?; + crypt_conf_opt = Some(&crypt_conf); + } + + if output_path.is_some() { + let mut file = File::create(output_path.unwrap())?; + Ok(file.write_all(blob.decode(crypt_conf_opt, digest)?.as_slice())?) + } else { + Ok(println!( + "{}", + String::from_utf8_lossy(blob.decode(crypt_conf_opt, digest)?.as_slice()) + )) + } +} + +#[api( + input: { + properties: { + chunk: { + schema: PATH_SCHEMA, + }, + "reference-filter": { + schema: PATH_SCHEMA, + optional: true, + }, + "decode": { + schema: PATH_SCHEMA, + optional: true, + }, + "keyfile": { + schema: KEYFILE_SCHEMA, + optional: true, + }, + "output-format": { + schema: OUTPUT_FORMAT, + optional: true, + }, + } + } +)] +/// Inspect a chunk +fn inspect_chunk(param: Value, _rpcenv: &mut dyn RpcEnvironment) -> Result<Value, Error> { + let chunk_path = Path::new(tools::required_string_param(¶m, "chunk")?); + let output_format = get_output_format(¶m); + let digest_str = chunk_path.file_name().unwrap().to_str().unwrap(); + + if !SHA256_HEX_REGEX.is_match(digest_str) { + println!("chunk filename is not valid"); + return Ok(Value::Null); + } + + let digest_raw = proxmox::tools::hex_to_digest(digest_str)?; + + let reference_filter_param = param["reference-filter"].as_str(); + let decode_output_param = param["decode"].as_str(); + let key_file_param = param["keyfile"].as_str(); + + let mut search_path = None; + let mut decode_output_path = None; + let mut key_file_path = None; + let mut to_stdout = false; + + if let Some(path) = reference_filter_param { + search_path = Some(Path::new(path)) + }; + + if let Some(path) = decode_output_param { + to_stdout = path.eq("-"); + decode_output_path = Some(Path::new(path)) + }; + + if let Some(path) = key_file_param { + key_file_path = Some(Path::new(path)) + }; + + let mut file = std::fs::File::open(&chunk_path)?; + let blob = DataBlob::load_from_reader(&mut file)?; + + let mut referenced_by = None; + if let Some(search_path) = search_path { + let mut references = Vec::new(); + for entry in WalkDir::new(search_path) + .follow_links(false) + .into_iter() + .filter_map(|e| e.ok()) + { + let file_name = entry.file_name().to_string_lossy(); + let mut in_index = HashSet::new(); + let mut index: Option<Box<dyn IndexFile>> = None; + + if file_name.ends_with(".fidx") { + index = match FixedIndexReader::open(entry.path()) { + Ok(index) => Some(Box::new(index)), + Err(_) => None, + }; + } + + if file_name.ends_with(".didx") { + index = match DynamicIndexReader::open(entry.path()) { + Ok(index) => Some(Box::new(index)), + Err(_) => None, + }; + } + + if let Some(index) = index { + for pos in 0..index.index_count() { + if let Some(index_chunk_digest) = index.index_digest(pos) { + in_index.insert(proxmox::tools::digest_to_hex(index_chunk_digest)); + } + } + } + + if in_index.contains(digest_str) { + references.push(entry.path().to_string_lossy().into_owned()); + } + } + referenced_by = Some(references); + } + + if let Some(decode_output_path) = decode_output_path { + if to_stdout { + decode_blob(None, key_file_path, Some(&digest_raw), &blob)?; + } else { + decode_blob( + Some(decode_output_path), + key_file_path, + Some(&digest_raw), + &blob, + )?; + } + } + + let crc_status = format!( + "{}({})", + blob.compute_crc(), + blob.verify_crc().map_or("NOK", |_x| "OK") + ); + + let val = match referenced_by { + Some(references) => json!({ + "digest": digest_str, + "crc": crc_status, + "encryption": blob.crypt_mode()?, + "referenced-by": references + }), + None => json!({ + "digest": digest_str, + "crc": crc_status, + "encryption": blob.crypt_mode()?, + }), + }; + + format_and_print_result(&val, &output_format); + Ok(Value::Null) +} + +pub fn inspect_commands() -> CommandLineInterface { + let cmd_def = CliCommandMap::new().insert("chunk", CliCommand::new(&API_METHOD_INSPECT_CHUNK)); + + cmd_def.into() +} diff --git a/src/bin/proxmox_backup_recovery/mod.rs b/src/bin/proxmox_backup_recovery/mod.rs new file mode 100644 index 00000000..644583db --- /dev/null +++ b/src/bin/proxmox_backup_recovery/mod.rs @@ -0,0 +1,2 @@ +mod inspect; +pub use inspect::*; -- 2.20.1 ^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [pbs-devel] [PATCH proxmox-backup 1/3] add inspection of chunks 2020-12-15 8:16 ` [pbs-devel] [PATCH proxmox-backup 1/3] add inspection of chunks Hannes Laimer @ 2020-12-15 10:14 ` Thomas Lamprecht 0 siblings, 0 replies; 5+ messages in thread From: Thomas Lamprecht @ 2020-12-15 10:14 UTC (permalink / raw) To: Proxmox Backup Server development discussion, Hannes Laimer Still no patch version, that must be used! Use the `-vX` option to your git format-patch or git send-email command, please really read, IIRC I'm linking that the third time now! https://pve.proxmox.com/wiki/Developer_Documentation#Versioned_Patches On 15.12.20 09:16, Hannes Laimer wrote: > Signed-off-by: Hannes Laimer <h.laimer@proxmox.com> > --- below this triple dash is for meta information, it is not the commit message but should describe changes since the last version of the patch, it won't get included in git. > Adds possibility to inspect chunks and find indexes that reference the > chunk. Options: > - chunk: path to the chunk file > - [opt] decode: path to a file or to stdout(-), if specified, the chunk will > be decoded into the specified location > - [opt] keyfile: path to a keyfile, needed if decode is specified and the > data was encrypted > - [opt] reference-filter: path in which indexes that reference the chunk > should be searched, can be a group, snapshot or the whole datastore, > if not specified no references will be searched > Makefile | 3 +- > src/bin/proxmox-backup-recovery.rs | 32 ++++ what are they now, recovery or inspect tools? Maybe a more generic name like: # proxmox-backup-debug inspect chunk ... inspect file ... recover index ... I mean what is the plan for the whole CLI tool, what additional commands could be added, I'd like to not just tape this together somehow, but have somewhat of a design plan.. Documentation patches would be also good to have, e.g., what is the reference-filter, full path to datastore, index, ...? > src/bin/proxmox_backup_recovery/inspect.rs | 195 +++++++++++++++++++++ > src/bin/proxmox_backup_recovery/mod.rs | 2 + > 4 files changed, 231 insertions(+), 1 deletion(-) > create mode 100644 src/bin/proxmox-backup-recovery.rs > create mode 100644 src/bin/proxmox_backup_recovery/inspect.rs > create mode 100644 src/bin/proxmox_backup_recovery/mod.rs > > diff --git a/Makefile b/Makefile > index a1af9d51..a9c2a711 100644 > --- a/Makefile > +++ b/Makefile > @@ -13,7 +13,8 @@ USR_BIN := \ > > # Binaries usable by admins > USR_SBIN := \ > - proxmox-backup-manager > + proxmox-backup-manager \ > + proxmox-backup-recovery \ > > # Binaries for services: > SERVICE_BIN := \ > diff --git a/src/bin/proxmox-backup-recovery.rs b/src/bin/proxmox-backup-recovery.rs > new file mode 100644 > index 00000000..ae8c18d5 > --- /dev/null > +++ b/src/bin/proxmox-backup-recovery.rs > @@ -0,0 +1,32 @@ > +use anyhow::Error; > +use proxmox::api::schema::{Schema, StringSchema}; > +use proxmox::api::cli::*; > + > +use proxmox::sys::linux::tty; > +use proxmox_backup_recovery::*; > +mod proxmox_backup_recovery; > + > +pub fn get_encryption_key_password() -> Result<Vec<u8>, Error> { > + tty::read_password("Encryption Key Password: ") > +} > + > +pub const PATH_SCHEMA: Schema = StringSchema::new("Path to a file or a directory").schema(); > + > +pub const KEYFILE_SCHEMA: Schema = StringSchema::new( > + "Path to encryption key. If the data was encrypted, this key will be used for decryption.", > +) > +.schema(); > + > +fn main() { > + proxmox_backup::tools::setup_safe_path_env(); > + > + let cmd_def = CliCommandMap::new() > + .insert("inspect", inspect_commands()); > + > + let rpcenv = CliEnvironment::new(); > + run_cli_command( > + cmd_def, > + rpcenv, > + Some(|future| proxmox_backup::tools::runtime::main(future)), > + ); > +} > diff --git a/src/bin/proxmox_backup_recovery/inspect.rs b/src/bin/proxmox_backup_recovery/inspect.rs > new file mode 100644 > index 00000000..6bf4a0a3 > --- /dev/null > +++ b/src/bin/proxmox_backup_recovery/inspect.rs > @@ -0,0 +1,195 @@ > +use std::collections::HashSet; > +use std::fs::File; > +use std::io::Write; > +use std::path::Path; > + > +use anyhow::Error; > +use proxmox::api::cli::{ > + format_and_print_result, get_output_format, CliCommand, CliCommandMap, CommandLineInterface, > +}; > +use proxmox::api::{api, cli::*, RpcEnvironment}; > +use serde_json::{json, Value}; > +use walkdir::WalkDir; > + > +use proxmox_backup::api2::types::SHA256_HEX_REGEX; > +use proxmox_backup::backup::{ > + load_and_decrypt_key, CryptConfig, DataBlob, DynamicIndexReader, FixedIndexReader, IndexFile, > +}; > +use proxmox_backup::tools; > + > +use crate::{get_encryption_key_password, KEYFILE_SCHEMA, PATH_SCHEMA}; > + > +/// Decodes a blob and writes its content either to stdout or into a file > +fn decode_blob( > + output_path: Option<&Path>, > + key_file: Option<&Path>, > + digest: Option<&[u8; 32]>, > + blob: &DataBlob, > +) -> Result<(), Error> { > + let mut crypt_conf_opt = None; > + let crypt_conf; > + > + if blob.is_encrypted() && key_file.is_some() { > + let (key, _created, _fingerprint) = > + load_and_decrypt_key(&key_file.unwrap(), &get_encryption_key_password)?; > + crypt_conf = CryptConfig::new(key)?; > + crypt_conf_opt = Some(&crypt_conf); > + } > + > + if output_path.is_some() { > + let mut file = File::create(output_path.unwrap())?; > + Ok(file.write_all(blob.decode(crypt_conf_opt, digest)?.as_slice())?) > + } else { > + Ok(println!( > + "{}", > + String::from_utf8_lossy(blob.decode(crypt_conf_opt, digest)?.as_slice()) > + )) > + } > +} > + > +#[api( > + input: { > + properties: { > + chunk: { > + schema: PATH_SCHEMA, > + }, > + "reference-filter": { > + schema: PATH_SCHEMA, > + optional: true, > + }, > + "decode": { > + schema: PATH_SCHEMA, > + optional: true, > + }, > + "keyfile": { > + schema: KEYFILE_SCHEMA, > + optional: true, > + }, > + "output-format": { > + schema: OUTPUT_FORMAT, > + optional: true, > + }, > + } > + } > +)] > +/// Inspect a chunk > +fn inspect_chunk(param: Value, _rpcenv: &mut dyn RpcEnvironment) -> Result<Value, Error> { > + let chunk_path = Path::new(tools::required_string_param(¶m, "chunk")?); > + let output_format = get_output_format(¶m); > + let digest_str = chunk_path.file_name().unwrap().to_str().unwrap(); > + > + if !SHA256_HEX_REGEX.is_match(digest_str) { > + println!("chunk filename is not valid"); at least to stderr, using eprintln! maybe also refer why is it not valid. > + return Ok(Value::Null); maybe even use bail!(...), I mean, feels weird that we exit with 0 in such a case > + } > + > + let digest_raw = proxmox::tools::hex_to_digest(digest_str)?; or, you may even drop above REGEX check + if and just do: let digest_raw = proxmox::tools::hex_to_digest(digest_str) .map_err(|e| format_err!("could not parse chunk - {}", e))?; > + > + let reference_filter_param = param["reference-filter"].as_str(); > + let decode_output_param = param["decode"].as_str(); > + let key_file_param = param["keyfile"].as_str(); > + > + let mut search_path = None; > + let mut decode_output_path = None; > + let mut key_file_path = None; > + let mut to_stdout = false; > + > + if let Some(path) = reference_filter_param { > + search_path = Some(Path::new(path)) > + }; Rather avoid mutable plus three extra lines (noise) and do let search_path = reference_filter_param.map(|path| Path::new(path)): > + > + if let Some(path) = decode_output_param { > + to_stdout = path.eq("-"); > + decode_output_path = Some(Path::new(path)) maybe a match could fit above better and do away with mutables let (decode_output_path, to_stdout) = match decode_output_param { Some(path) if path.eq("-") => (None, true), Some(path) => (Some(path), false), None => (None, false), }; just a line less total, but more explicit, no hard feelings here though > + }; > + > + if let Some(path) = key_file_param { > + key_file_path = Some(Path::new(path)) > + }; use map here let key_file_path = key_file_param.map(|path| Path::new(path)); > + > + let mut file = std::fs::File::open(&chunk_path)?; may map_err + format! can make this a better UX if the open fails > + let blob = DataBlob::load_from_reader(&mut file)?; > + > + let mut referenced_by = None; > + if let Some(search_path) = search_path { > + let mut references = Vec::new(); > + for entry in WalkDir::new(search_path) > + .follow_links(false) > + .into_iter() > + .filter_map(|e| e.ok()) > + { > + let file_name = entry.file_name().to_string_lossy(); > + let mut in_index = HashSet::new(); why is this defined here, but only used below, actually it should not be required anyway. > + let mut index: Option<Box<dyn IndexFile>> = None; > + > + if file_name.ends_with(".fidx") { > + index = match FixedIndexReader::open(entry.path()) { > + Ok(index) => Some(Box::new(index)), > + Err(_) => None, > + }; > + } > + > + if file_name.ends_with(".didx") { > + index = match DynamicIndexReader::open(entry.path()) { > + Ok(index) => Some(Box::new(index)), > + Err(_) => None, > + }; > + } > + > + if let Some(index) = index { > + for pos in 0..index.index_count() { > + if let Some(index_chunk_digest) = index.index_digest(pos) { > + in_index.insert(proxmox::tools::digest_to_hex(index_chunk_digest)); > + } > + } > + } > + you could move below if hunk in the for loop's if above, avoiding the need for any hashset > + if in_index.contains(digest_str) { > + references.push(entry.path().to_string_lossy().into_owned()); > + } > + } > + referenced_by = Some(references); > + } > + > + if let Some(decode_output_path) = decode_output_path { > + if to_stdout { > + decode_blob(None, key_file_path, Some(&digest_raw), &blob)?; > + } else { > + decode_blob( > + Some(decode_output_path), > + key_file_path, > + Some(&digest_raw), > + &blob, > + )?; > + } > + } Above 12 lines could be reduced with something like (untested): if decode_output_path.is_some() { let decode_output_path = if to_stdout { None } else { decode_output_path }; decode_blob(decode_output_path, key_file_path, Some(&digest_raw), &blob)?; } > + > + let crc_status = format!( > + "{}({})", > + blob.compute_crc(), > + blob.verify_crc().map_or("NOK", |_x| "OK") rather use just _ if unused param > + ); > + > + let val = match referenced_by { > + Some(references) => json!({ > + "digest": digest_str, > + "crc": crc_status, > + "encryption": blob.crypt_mode()?, > + "referenced-by": references > + }), > + None => json!({ > + "digest": digest_str, > + "crc": crc_status, > + "encryption": blob.crypt_mode()?, > + }), > + }; > + > + format_and_print_result(&val, &output_format); > + Ok(Value::Null) > +} > + > +pub fn inspect_commands() -> CommandLineInterface { > + let cmd_def = CliCommandMap::new().insert("chunk", CliCommand::new(&API_METHOD_INSPECT_CHUNK)); > + > + cmd_def.into() > +} > diff --git a/src/bin/proxmox_backup_recovery/mod.rs b/src/bin/proxmox_backup_recovery/mod.rs > new file mode 100644 > index 00000000..644583db > --- /dev/null > +++ b/src/bin/proxmox_backup_recovery/mod.rs > @@ -0,0 +1,2 @@ > +mod inspect; > +pub use inspect::*; > ^ permalink raw reply [flat|nested] 5+ messages in thread
* [pbs-devel] [PATCH proxmox-backup 2/3] add inspection of .blob, .fidx and .didx files 2020-12-15 8:16 [pbs-devel] [PATCH proxmox-backup 0/3] add proxmox-backup-recovery binary Hannes Laimer 2020-12-15 8:16 ` [pbs-devel] [PATCH proxmox-backup 1/3] add inspection of chunks Hannes Laimer @ 2020-12-15 8:16 ` Hannes Laimer 2020-12-15 8:16 ` [pbs-devel] [PATCH proxmox-backup 3/3] add restore of index files Hannes Laimer 2 siblings, 0 replies; 5+ messages in thread From: Hannes Laimer @ 2020-12-15 8:16 UTC (permalink / raw) To: pbs-devel Signed-off-by: Hannes Laimer <h.laimer@proxmox.com> --- Adds possibility to inspect .blob, .fidx and .didx files. For index files a list of the chunks referenced will be printed in addition to some other inforation. .blob files can be decoded into file or directly into stdout. Options: - file: path to the file - [opt] decode: path to a file or stdout(-), if specidied, the file will be decoded into the specified location [only for blob files, no effect with index files] - [opt] keyfile: path to a keyfile, needed if decode is specified and the data was encrypted src/bin/proxmox_backup_recovery/inspect.rs | 115 ++++++++++++++++++++- 1 file changed, 114 insertions(+), 1 deletion(-) diff --git a/src/bin/proxmox_backup_recovery/inspect.rs b/src/bin/proxmox_backup_recovery/inspect.rs index 6bf4a0a3..0b8d8e48 100644 --- a/src/bin/proxmox_backup_recovery/inspect.rs +++ b/src/bin/proxmox_backup_recovery/inspect.rs @@ -188,8 +188,121 @@ fn inspect_chunk(param: Value, _rpcenv: &mut dyn RpcEnvironment) -> Result<Value Ok(Value::Null) } +#[api( + input: { + properties: { + file: { + schema: PATH_SCHEMA, + }, + "decode": { + schema: PATH_SCHEMA, + optional: true, + }, + "keyfile": { + schema: KEYFILE_SCHEMA, + optional: true, + }, + "output-format": { + schema: OUTPUT_FORMAT, + optional: true, + }, + } + } +)] +/// Inspect a file +fn inspect_file(param: Value, _rpcenv: &mut dyn RpcEnvironment) -> Result<Value, Error> { + let path = tools::required_string_param(¶m, "file")?; + let output_format = get_output_format(¶m); + + let val; + if path.ends_with(".blob") { + let decode_output_param = param["decode"].as_str(); + let key_file_param = param["keyfile"].as_str(); + + let mut file = std::fs::File::open(&path)?; + let data_blob = DataBlob::load_from_reader(&mut file)?; + + let mut decode_output_path = None; + let mut key_file_path = None; + + if let Some(path) = decode_output_param { + decode_output_path = Some(Path::new(path)) + }; + + if let Some(path) = key_file_param { + key_file_path = Some(Path::new(path)) + }; + + let crypt_mode = data_blob.crypt_mode()?; + val = json!({ + "encryption": crypt_mode, + "raw_size": data_blob.raw_size(), + }); + + if decode_output_path.is_some() { + if decode_output_param.unwrap().eq("-") { + decode_blob(None, key_file_path, None, &data_blob)?; + } else { + decode_blob(decode_output_path, key_file_path, None, &data_blob)?; + } + } + } else if path.ends_with(".fidx") { + let index = FixedIndexReader::open(Path::new(path))?; + + let mut ctime_str = index.ctime.to_string(); + if let Ok(s) = proxmox::tools::time::strftime_local("%c", index.ctime) { + ctime_str = s; + } + + let mut chunk_digests = HashSet::new(); + + for pos in 0..index.index_count() { + let digest = index.index_digest(pos).unwrap(); + chunk_digests.insert(proxmox::tools::digest_to_hex(digest)); + } + + val = json!({ + "size": index.size, + "ctime": ctime_str, + "chunk-digests": chunk_digests + + }) + } else if path.ends_with("didx") { + let index = DynamicIndexReader::open(Path::new(path))?; + + let mut ctime_str = index.ctime.to_string(); + if let Ok(s) = proxmox::tools::time::strftime_local("%c", index.ctime) { + ctime_str = s; + } + + let mut chunk_digests = HashSet::new(); + + for pos in 0..index.index_count() { + let digest = index.index_digest(pos).unwrap(); + chunk_digests.insert(proxmox::tools::digest_to_hex(digest)); + } + + val = json!({ + "size": index.size, + "ctime": ctime_str, + "chunk-digests": chunk_digests + }) + } else { + val = Value::Null; + println!("Only .blob, .fidx and .didx files may be inspected"); + } + + if !val.is_null() { + format_and_print_result(&val, &output_format); + } + + Ok(Value::Null) +} + pub fn inspect_commands() -> CommandLineInterface { - let cmd_def = CliCommandMap::new().insert("chunk", CliCommand::new(&API_METHOD_INSPECT_CHUNK)); + let cmd_def = CliCommandMap::new() + .insert("file", CliCommand::new(&API_METHOD_INSPECT_FILE)) + .insert("chunk", CliCommand::new(&API_METHOD_INSPECT_CHUNK)); cmd_def.into() } -- 2.20.1 ^ permalink raw reply [flat|nested] 5+ messages in thread
* [pbs-devel] [PATCH proxmox-backup 3/3] add restore of index files 2020-12-15 8:16 [pbs-devel] [PATCH proxmox-backup 0/3] add proxmox-backup-recovery binary Hannes Laimer 2020-12-15 8:16 ` [pbs-devel] [PATCH proxmox-backup 1/3] add inspection of chunks Hannes Laimer 2020-12-15 8:16 ` [pbs-devel] [PATCH proxmox-backup 2/3] add inspection of .blob, .fidx and .didx files Hannes Laimer @ 2020-12-15 8:16 ` Hannes Laimer 2 siblings, 0 replies; 5+ messages in thread From: Hannes Laimer @ 2020-12-15 8:16 UTC (permalink / raw) To: pbs-devel Signed-off-by: Hannes Laimer <h.laimer@proxmox.com> --- Adds possibility to restore data from an index file. Options: - chunks: path to the directory where the chunks are saved - file: the index file that should be restored(must be either .fidx or didx) - [opt] keyfile: path to a keyfile, if the data was encrypted, a keyfile is needed - [opt] skip-crc: boolean, if true, read chunks wont be verified with their crc-sum, increases the restore speed by a lot src/bin/proxmox-backup-recovery.rs | 109 ++++++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 2 deletions(-) diff --git a/src/bin/proxmox-backup-recovery.rs b/src/bin/proxmox-backup-recovery.rs index ae8c18d5..c2b9fdfa 100644 --- a/src/bin/proxmox-backup-recovery.rs +++ b/src/bin/proxmox-backup-recovery.rs @@ -1,9 +1,21 @@ +use std::fs::File; +use std::io::{Read, Write}; +use std::path::Path; + use anyhow::Error; use proxmox::api::schema::{Schema, StringSchema}; -use proxmox::api::cli::*; +use proxmox::api::{api, cli::*}; +use proxmox::tools::digest_to_hex; +use serde_json::Value; use proxmox::sys::linux::tty; +use proxmox_backup::backup::{ + load_and_decrypt_key, CryptConfig, DataBlob, DynamicIndexReader, FixedIndexReader, IndexFile, +}; +use proxmox_backup::tools; use proxmox_backup_recovery::*; +use std::time::Instant; + mod proxmox_backup_recovery; pub fn get_encryption_key_password() -> Result<Vec<u8>, Error> { @@ -17,11 +29,104 @@ pub const KEYFILE_SCHEMA: Schema = StringSchema::new( ) .schema(); +#[api( + input: { + properties: { + file: { + schema: PATH_SCHEMA, + }, + chunks: { + schema: PATH_SCHEMA, + }, + "keyfile": { + schema: KEYFILE_SCHEMA, + optional: true, + }, + "skip-crc": { + type: Boolean, + optional: true, + default: false, + description: "Skip the crc verification, increases the restore speed immensely", + } + } + } +)] +/// Restore a index file +fn restore(skip_crc: bool, param: Value) -> Result<Value, Error> { + let start = Instant::now(); + let file_path = Path::new(tools::required_string_param(¶m, "file")?); + let chunks_path = Path::new(tools::required_string_param(¶m, "chunks")?); + + let key_file_param = param["keyfile"].as_str(); + let mut key_file_path = None; + + if let Some(path) = key_file_param { + key_file_path = Some(Path::new(path)) + }; + + let file_name = file_path.file_name().unwrap().to_str().unwrap(); + let mut index: Option<Box<dyn IndexFile>> = None; + + if file_name.ends_with(".fidx") { + index = match FixedIndexReader::open(file_path) { + Ok(index) => Some(Box::new(index)), + Err(_) => None, + }; + } + + if file_name.ends_with(".didx") { + index = match DynamicIndexReader::open(file_path) { + Ok(index) => Some(Box::new(index)), + Err(_) => None, + }; + } + + let mut crypt_conf_opt = None; + let mut crypt_conf; + + let output_filename = &file_name[0..file_name.len() - 5]; + let output_path = Path::new(output_filename); + let mut output_file = File::create(output_path)?; + if let Some(index) = index { + for pos in 0..index.index_count() { + let chunk_digest = index.index_digest(pos).unwrap(); + let digest_str = digest_to_hex(chunk_digest); + let digest_prefix = &digest_str[0..4]; + let chunk_path = chunks_path.join(digest_prefix).join(digest_str); + let mut chunk_file = std::fs::File::open(&chunk_path)?; + + let mut data = Vec::with_capacity(1024 * 1024); + chunk_file.read_to_end(&mut data)?; + let chunk_blob = DataBlob::from_raw(data)?; + + if !skip_crc { + chunk_blob.verify_crc()?; + } + + if key_file_path.is_some() && chunk_blob.is_encrypted() && crypt_conf_opt.is_none() { + let (key, _created, _fingerprint) = + load_and_decrypt_key(&key_file_path.unwrap(), &get_encryption_key_password)?; + crypt_conf = CryptConfig::new(key)?; + crypt_conf_opt = Some(&crypt_conf); + } + + output_file.write_all( + chunk_blob + .decode(crypt_conf_opt, Some(chunk_digest))? + .as_slice(), + )?; + } + } + println!("{} sec.", start.elapsed().as_secs_f32()); + Ok(Value::Null) +} + fn main() { proxmox_backup::tools::setup_safe_path_env(); let cmd_def = CliCommandMap::new() - .insert("inspect", inspect_commands()); + .insert("inspect", inspect_commands()) + .insert("restore", CliCommand::new(&API_METHOD_RESTORE)); let rpcenv = CliEnvironment::new(); run_cli_command( -- 2.20.1 ^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2020-12-15 10:14 UTC | newest] Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2020-12-15 8:16 [pbs-devel] [PATCH proxmox-backup 0/3] add proxmox-backup-recovery binary Hannes Laimer 2020-12-15 8:16 ` [pbs-devel] [PATCH proxmox-backup 1/3] add inspection of chunks Hannes Laimer 2020-12-15 10:14 ` Thomas Lamprecht 2020-12-15 8:16 ` [pbs-devel] [PATCH proxmox-backup 2/3] add inspection of .blob, .fidx and .didx files Hannes Laimer 2020-12-15 8:16 ` [pbs-devel] [PATCH proxmox-backup 3/3] add restore of index files Hannes Laimer
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox