]> git.proxmox.com Git - proxmox-backup.git/blame - src/bin/proxmox_backup_client/mount.rs
client/remote: add support to specify port number
[proxmox-backup.git] / src / bin / proxmox_backup_client / mount.rs
CommitLineData
43abba4b
DM
1use std::path::PathBuf;
2use std::sync::Arc;
3use std::os::unix::io::RawFd;
4use std::path::Path;
5use std::ffi::OsStr;
6
7use anyhow::{bail, format_err, Error};
8use serde_json::Value;
9use tokio::signal::unix::{signal, SignalKind};
10use nix::unistd::{fork, ForkResult, pipe};
11use futures::select;
12use futures::future::FutureExt;
13
14use proxmox::{sortable, identity};
cc7995ac 15use proxmox::api::{ApiHandler, ApiMethod, RpcEnvironment, schema::*, cli::*};
43abba4b
DM
16
17
18use proxmox_backup::tools;
19use proxmox_backup::backup::{
20 load_and_decrypt_key,
21 CryptConfig,
22 IndexFile,
23 BackupDir,
24 BackupGroup,
25 BufferedDynamicReader,
26};
27
28use proxmox_backup::client::*;
43abba4b
DM
29
30use crate::{
31 REPO_URL_SCHEMA,
32 extract_repository_from_value,
43abba4b
DM
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]
43const 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
58pub 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
68fn 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
100async 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")?;
ba20987a 104 let client = connect(repo.host(), repo.port(), repo.user())?;
43abba4b
DM
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) => {
9696f519 121 let (key, _) = load_and_decrypt_key(&path, &crate::key::get_encryption_key_password)?;
43abba4b
DM
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
2107a5ae 142 let (manifest, _) = client.download_manifest().await?;
43abba4b 143
871181d9 144 let file_info = manifest.lookup_file_info(&server_archive_name)?;
14f6c9cb 145
43abba4b
DM
146 if server_archive_name.ends_with(".didx") {
147 let index = client.download_dynamic_index(&manifest, &server_archive_name).await?;
148 let most_used = index.find_most_used_chunks(8);
14f6c9cb 149 let chunk_reader = RemoteChunkReader::new(client.clone(), crypt_config, file_info.chunk_crypt_mode(), most_used);
43abba4b
DM
150 let reader = BufferedDynamicReader::new(index, chunk_reader);
151 let archive_size = reader.archive_size();
152 let reader: proxmox_backup::pxar::fuse::Reader =
153 Arc::new(BufferedDynamicReadAt::new(reader));
154 let decoder = proxmox_backup::pxar::fuse::Accessor::new(reader, archive_size).await?;
155 let options = OsStr::new("ro,default_permissions");
156
157 let session = proxmox_backup::pxar::fuse::Session::mount(
158 decoder,
159 &options,
160 false,
161 Path::new(target),
162 )
163 .map_err(|err| format_err!("pxar mount failed: {}", err))?;
164
165 if let Some(pipe) = pipe {
166 nix::unistd::chdir(Path::new("/")).unwrap();
167 // Finish creation of daemon by redirecting filedescriptors.
168 let nullfd = nix::fcntl::open(
169 "/dev/null",
170 nix::fcntl::OFlag::O_RDWR,
171 nix::sys::stat::Mode::empty(),
172 ).unwrap();
173 nix::unistd::dup2(nullfd, 0).unwrap();
174 nix::unistd::dup2(nullfd, 1).unwrap();
175 nix::unistd::dup2(nullfd, 2).unwrap();
176 if nullfd > 2 {
177 nix::unistd::close(nullfd).unwrap();
178 }
179 // Signal the parent process that we are done with the setup and it can
180 // terminate.
181 nix::unistd::write(pipe, &[0u8])?;
182 nix::unistd::close(pipe).unwrap();
183 }
184
185 let mut interrupt = signal(SignalKind::interrupt())?;
186 select! {
187 res = session.fuse() => res?,
188 _ = interrupt.recv().fuse() => {
189 // exit on interrupted
190 }
191 }
192 } else {
193 bail!("unknown archive file extension (expected .pxar)");
194 }
195
196 Ok(Value::Null)
197}