From 3cf73c4e5376699b05d2755a881088c697d868dc Mon Sep 17 00:00:00 2001 From: Christian Ebner Date: Thu, 21 Nov 2019 15:00:01 +0100 Subject: [PATCH] src/bin/proxmox-backup-client.rs: impl shell subcommand for client Signed-off-by: Christian Ebner --- src/bin/proxmox-backup-client.rs | 141 ++++++++++++++++++++++++++++++- 1 file changed, 140 insertions(+), 1 deletion(-) diff --git a/src/bin/proxmox-backup-client.rs b/src/bin/proxmox-backup-client.rs index 87dbc249..3b160349 100644 --- a/src/bin/proxmox-backup-client.rs +++ b/src/bin/proxmox-backup-client.rs @@ -1749,6 +1749,124 @@ async fn mount_do(param: Value, pipe: Option) -> Result { Ok(Value::Null) } +fn shell( + param: Value, + _info: &ApiMethod, + _rpcenv: &mut dyn RpcEnvironment, +) -> Result { + async_main(catalog_shell(param)) +} + +async fn catalog_shell(param: Value) -> Result { + let repo = extract_repository_from_value(¶m)?; + let client = HttpClient::new(repo.host(), repo.user(), None)?; + let path = tools::required_string_param(¶m, "snapshot")?; + let archive_name = tools::required_string_param(¶m, "archive-name")?; + + let (backup_type, backup_id, backup_time) = if path.matches('/').count() == 1 { + let group = BackupGroup::parse(path)?; + + let path = format!("api2/json/admin/datastore/{}/snapshots", repo.store()); + let result = client.get(&path, Some(json!({ + "backup-type": group.backup_type(), + "backup-id": group.backup_id(), + }))).await?; + + let list = result["data"].as_array().unwrap(); + if list.len() == 0 { + bail!("backup group '{}' does not contain any snapshots:", path); + } + + let epoch = list[0]["backup-time"].as_i64().unwrap(); + let backup_time = Utc.timestamp(epoch, 0); + (group.backup_type().to_owned(), group.backup_id().to_owned(), backup_time) + } else { + let snapshot = BackupDir::parse(path)?; + (snapshot.group().backup_type().to_owned(), snapshot.group().backup_id().to_owned(), snapshot.backup_time()) + }; + + let keyfile = param["keyfile"].as_str().map(|p| PathBuf::from(p)); + let crypt_config = match keyfile { + None => None, + Some(path) => { + let (key, _) = load_and_decrtypt_key(&path, &get_encryption_key_password)?; + Some(Arc::new(CryptConfig::new(key)?)) + } + }; + + let server_archive_name = if archive_name.ends_with(".pxar") { + format!("{}.didx", archive_name) + } else { + bail!("Can only mount pxar archives."); + }; + + let client = BackupReader::start( + client, + crypt_config.clone(), + repo.store(), + &backup_type, + &backup_id, + backup_time, + true, + ).await?; + + let tmpfile = std::fs::OpenOptions::new() + .write(true) + .read(true) + .custom_flags(libc::O_TMPFILE) + .open("/tmp")?; + + let manifest = client.download_manifest().await?; + + let index = client.download_dynamic_index(&manifest, &server_archive_name).await?; + let most_used = index.find_most_used_chunks(8); + let chunk_reader = RemoteChunkReader::new(client.clone(), crypt_config.clone(), most_used); + let reader = BufferedDynamicReader::new(index, chunk_reader); + let decoder = + pxar::Decoder::, fn(&Path) -> Result<(), Error>>::new( + reader, + |path| { + println!("{:?}", path); + Ok(()) + } + )?; + + let tmpfile = client.download(CATALOG_NAME, tmpfile).await?; + let index = DynamicIndexReader::new(tmpfile) + .map_err(|err| format_err!("unable to read catalog index - {}", err))?; + + // Note: do not use values stored in index (not trusted) - instead, computed them again + let (csum, size) = index.compute_csum(); + manifest.verify_file(CATALOG_NAME, &csum, size)?; + + let most_used = index.find_most_used_chunks(8); + let chunk_reader = RemoteChunkReader::new(client.clone(), crypt_config, most_used); + let mut reader = BufferedDynamicReader::new(index, chunk_reader); + let mut catalogfile = std::fs::OpenOptions::new() + .write(true) + .read(true) + .custom_flags(libc::O_TMPFILE) + .open("/tmp")?; + + std::io::copy(&mut reader, &mut catalogfile) + .map_err(|err| format_err!("unable to download catalog - {}", err))?; + + catalogfile.seek(SeekFrom::Start(0))?; + let catalog_reader = CatalogReader::new(catalogfile); + let state = Shell::new( + catalog_reader, + &server_archive_name, + decoder, + )?; + + println!("Starting interactive shell"); + state.shell()?; + + record_repository(&repo); + + Ok(Value::Null) +} + fn main() { const BACKUP_SOURCE_SCHEMA: Schema = StringSchema::new("Backup source specification ([