]> git.proxmox.com Git - proxmox-backup.git/blob - src/bin/proxmox_backup_client/mount.rs
src/backup/manifest.rs: include signature inside the manifest
[proxmox-backup.git] / src / bin / proxmox_backup_client / mount.rs
1 use std::path::PathBuf;
2 use std::sync::Arc;
3 use std::os::unix::io::RawFd;
4 use std::path::Path;
5 use std::ffi::OsStr;
6
7 use anyhow::{bail, format_err, Error};
8 use serde_json::Value;
9 use tokio::signal::unix::{signal, SignalKind};
10 use nix::unistd::{fork, ForkResult, pipe};
11 use futures::select;
12 use futures::future::FutureExt;
13
14 use proxmox::{sortable, identity};
15 use proxmox::api::{ApiHandler, ApiMethod, RpcEnvironment, schema::*, cli::*};
16
17
18 use proxmox_backup::tools;
19 use proxmox_backup::backup::{
20 load_and_decrypt_key,
21 CryptConfig,
22 IndexFile,
23 BackupDir,
24 BackupGroup,
25 BufferedDynamicReader,
26 };
27
28 use proxmox_backup::client::*;
29
30 use crate::{
31 REPO_URL_SCHEMA,
32 extract_repository_from_value,
33 complete_pxar_archive_name,
34 complete_group_or_snapshot,
35 complete_repository,
36 record_repository,
37 connect,
38 api_datastore_latest_snapshot,
39 BufferedDynamicReadAt,
40 };
41
42 #[sortable]
43 const API_METHOD_MOUNT: ApiMethod = ApiMethod::new(
44 &ApiHandler::Sync(&mount),
45 &ObjectSchema::new(
46 "Mount pxar archive.",
47 &sorted!([
48 ("snapshot", false, &StringSchema::new("Group/Snapshot path.").schema()),
49 ("archive-name", false, &StringSchema::new("Backup archive name.").schema()),
50 ("target", false, &StringSchema::new("Target directory path.").schema()),
51 ("repository", true, &REPO_URL_SCHEMA),
52 ("keyfile", true, &StringSchema::new("Path to encryption key.").schema()),
53 ("verbose", true, &BooleanSchema::new("Verbose output.").default(false).schema()),
54 ]),
55 )
56 );
57
58 pub fn mount_cmd_def() -> CliCommand {
59
60 CliCommand::new(&API_METHOD_MOUNT)
61 .arg_param(&["snapshot", "archive-name", "target"])
62 .completion_cb("repository", complete_repository)
63 .completion_cb("snapshot", complete_group_or_snapshot)
64 .completion_cb("archive-name", complete_pxar_archive_name)
65 .completion_cb("target", tools::complete_file_name)
66 }
67
68 fn mount(
69 param: Value,
70 _info: &ApiMethod,
71 _rpcenv: &mut dyn RpcEnvironment,
72 ) -> Result<Value, Error> {
73
74 let verbose = param["verbose"].as_bool().unwrap_or(false);
75 if verbose {
76 // This will stay in foreground with debug output enabled as None is
77 // passed for the RawFd.
78 return proxmox_backup::tools::runtime::main(mount_do(param, None));
79 }
80
81 // Process should be deamonized.
82 // Make sure to fork before the async runtime is instantiated to avoid troubles.
83 let pipe = pipe()?;
84 match fork() {
85 Ok(ForkResult::Parent { .. }) => {
86 nix::unistd::close(pipe.1).unwrap();
87 // Blocks the parent process until we are ready to go in the child
88 let _res = nix::unistd::read(pipe.0, &mut [0]).unwrap();
89 Ok(Value::Null)
90 }
91 Ok(ForkResult::Child) => {
92 nix::unistd::close(pipe.0).unwrap();
93 nix::unistd::setsid().unwrap();
94 proxmox_backup::tools::runtime::main(mount_do(param, Some(pipe.1)))
95 }
96 Err(_) => bail!("failed to daemonize process"),
97 }
98 }
99
100 async fn mount_do(param: Value, pipe: Option<RawFd>) -> Result<Value, Error> {
101 let repo = extract_repository_from_value(&param)?;
102 let archive_name = tools::required_string_param(&param, "archive-name")?;
103 let target = tools::required_string_param(&param, "target")?;
104 let client = connect(repo.host(), repo.user())?;
105
106 record_repository(&repo);
107
108 let path = tools::required_string_param(&param, "snapshot")?;
109 let (backup_type, backup_id, backup_time) = if path.matches('/').count() == 1 {
110 let group: BackupGroup = path.parse()?;
111 api_datastore_latest_snapshot(&client, repo.store(), group).await?
112 } else {
113 let snapshot: BackupDir = path.parse()?;
114 (snapshot.group().backup_type().to_owned(), snapshot.group().backup_id().to_owned(), snapshot.backup_time())
115 };
116
117 let keyfile = param["keyfile"].as_str().map(PathBuf::from);
118 let crypt_config = match keyfile {
119 None => None,
120 Some(path) => {
121 let (key, _) = load_and_decrypt_key(&path, &crate::key::get_encryption_key_password)?;
122 Some(Arc::new(CryptConfig::new(key)?))
123 }
124 };
125
126 let server_archive_name = if archive_name.ends_with(".pxar") {
127 format!("{}.didx", archive_name)
128 } else {
129 bail!("Can only mount pxar archives.");
130 };
131
132 let client = BackupReader::start(
133 client,
134 crypt_config.clone(),
135 repo.store(),
136 &backup_type,
137 &backup_id,
138 backup_time,
139 true,
140 ).await?;
141
142 let (manifest, _) = client.download_manifest().await?;
143
144 if server_archive_name.ends_with(".didx") {
145 let index = client.download_dynamic_index(&manifest, &server_archive_name).await?;
146 let most_used = index.find_most_used_chunks(8);
147 let chunk_reader = RemoteChunkReader::new(client.clone(), crypt_config, most_used);
148 let reader = BufferedDynamicReader::new(index, chunk_reader);
149 let archive_size = reader.archive_size();
150 let reader: proxmox_backup::pxar::fuse::Reader =
151 Arc::new(BufferedDynamicReadAt::new(reader));
152 let decoder = proxmox_backup::pxar::fuse::Accessor::new(reader, archive_size).await?;
153 let options = OsStr::new("ro,default_permissions");
154
155 let session = proxmox_backup::pxar::fuse::Session::mount(
156 decoder,
157 &options,
158 false,
159 Path::new(target),
160 )
161 .map_err(|err| format_err!("pxar mount failed: {}", err))?;
162
163 if let Some(pipe) = pipe {
164 nix::unistd::chdir(Path::new("/")).unwrap();
165 // Finish creation of daemon by redirecting filedescriptors.
166 let nullfd = nix::fcntl::open(
167 "/dev/null",
168 nix::fcntl::OFlag::O_RDWR,
169 nix::sys::stat::Mode::empty(),
170 ).unwrap();
171 nix::unistd::dup2(nullfd, 0).unwrap();
172 nix::unistd::dup2(nullfd, 1).unwrap();
173 nix::unistd::dup2(nullfd, 2).unwrap();
174 if nullfd > 2 {
175 nix::unistd::close(nullfd).unwrap();
176 }
177 // Signal the parent process that we are done with the setup and it can
178 // terminate.
179 nix::unistd::write(pipe, &[0u8])?;
180 nix::unistd::close(pipe).unwrap();
181 }
182
183 let mut interrupt = signal(SignalKind::interrupt())?;
184 select! {
185 res = session.fuse() => res?,
186 _ = interrupt.recv().fuse() => {
187 // exit on interrupted
188 }
189 }
190 } else {
191 bail!("unknown archive file extension (expected .pxar)");
192 }
193
194 Ok(Value::Null)
195 }