1 use std
::path
::PathBuf
;
3 use std
::os
::unix
::io
::RawFd
;
7 use anyhow
::{bail, format_err, Error}
;
9 use tokio
::signal
::unix
::{signal, SignalKind}
;
10 use nix
::unistd
::{fork, ForkResult, pipe}
;
12 use futures
::future
::FutureExt
;
14 use proxmox
::{sortable, identity}
;
15 use proxmox
::api
::{ApiHandler, ApiMethod, RpcEnvironment, schema::*, cli::*}
;
18 use proxmox_backup
::tools
;
19 use proxmox_backup
::backup
::{
25 BufferedDynamicReader
,
28 use proxmox_backup
::client
::*;
32 extract_repository_from_value
,
33 complete_pxar_archive_name
,
34 complete_group_or_snapshot
,
38 api_datastore_latest_snapshot
,
39 BufferedDynamicReadAt
,
43 const API_METHOD_MOUNT
: ApiMethod
= ApiMethod
::new(
44 &ApiHandler
::Sync(&mount
),
46 "Mount pxar archive.",
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()),
58 pub fn mount_cmd_def() -> CliCommand
{
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
)
71 _rpcenv
: &mut dyn RpcEnvironment
,
72 ) -> Result
<Value
, Error
> {
74 let verbose
= param
["verbose"].as_bool().unwrap_or(false);
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
));
81 // Process should be deamonized.
82 // Make sure to fork before the async runtime is instantiated to avoid troubles.
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();
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)))
96 Err(_
) => bail
!("failed to daemonize process"),
100 async
fn mount_do(param
: Value
, pipe
: Option
<RawFd
>) -> Result
<Value
, Error
> {
101 let repo
= extract_repository_from_value(¶m
)?
;
102 let archive_name
= tools
::required_string_param(¶m
, "archive-name")?
;
103 let target
= tools
::required_string_param(¶m
, "target")?
;
104 let client
= connect(repo
.host(), repo
.port(), repo
.user())?
;
106 record_repository(&repo
);
108 let path
= tools
::required_string_param(¶m
, "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?
113 let snapshot
: BackupDir
= path
.parse()?
;
114 (snapshot
.group().backup_type().to_owned(), snapshot
.group().backup_id().to_owned(), snapshot
.backup_time())
117 let keyfile
= param
["keyfile"].as_str().map(PathBuf
::from
);
118 let crypt_config
= match keyfile
{
121 let (key
, _
) = load_and_decrypt_key(&path
, &crate::key
::get_encryption_key_password
)?
;
122 Some(Arc
::new(CryptConfig
::new(key
)?
))
126 let server_archive_name
= if archive_name
.ends_with(".pxar") {
127 format
!("{}.didx", archive_name
)
129 bail
!("Can only mount pxar archives.");
132 let client
= BackupReader
::start(
134 crypt_config
.clone(),
142 let (manifest
, _
) = client
.download_manifest().await?
;
144 let file_info
= manifest
.lookup_file_info(&server_archive_name
)?
;
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);
149 let chunk_reader
= RemoteChunkReader
::new(client
.clone(), crypt_config
, file_info
.chunk_crypt_mode(), most_used
);
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");
157 let session
= proxmox_backup
::pxar
::fuse
::Session
::mount(
163 .map_err(|err
| format_err
!("pxar mount failed: {}", err
))?
;
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(
170 nix
::fcntl
::OFlag
::O_RDWR
,
171 nix
::sys
::stat
::Mode
::empty(),
173 nix
::unistd
::dup2(nullfd
, 0).unwrap();
174 nix
::unistd
::dup2(nullfd
, 1).unwrap();
175 nix
::unistd
::dup2(nullfd
, 2).unwrap();
177 nix
::unistd
::close(nullfd
).unwrap();
179 // Signal the parent process that we are done with the setup and it can
181 nix
::unistd
::write(pipe
, &[0u8])?
;
182 nix
::unistd
::close(pipe
).unwrap();
185 let mut interrupt
= signal(SignalKind
::interrupt())?
;
187 res
= session
.fuse() => res?
,
188 _
= interrupt
.recv().fuse() => {
189 // exit on interrupted
193 bail
!("unknown archive file extension (expected .pxar)");