1 use std
::collections
::HashSet
;
3 use std
::fs
::OpenOptions
;
4 use std
::os
::unix
::fs
::OpenOptionsExt
;
5 use std
::path
::{Path, PathBuf}
;
7 use std
::sync
::atomic
::{AtomicBool, Ordering}
;
9 use anyhow
::{bail, format_err, Error}
;
10 use futures
::future
::FutureExt
;
12 use tokio
::signal
::unix
::{signal, SignalKind}
;
14 use pathpatterns
::{MatchEntry, MatchType, PatternFlag}
;
16 use proxmox
::api
::cli
::*;
17 use proxmox
::api
::api
;
19 use proxmox_backup
::tools
;
20 use proxmox_backup
::pxar
::{fuse, format_single_line_entry, ENCODER_MAX_ENTRIES, Flags}
;
22 fn extract_archive_from_reader
<R
: std
::io
::Read
>(
26 allow_existing_dirs
: bool
,
28 match_list
: &[MatchEntry
],
29 extract_match_default
: bool
,
30 on_error
: Option
<Box
<dyn FnMut(Error
) -> Result
<(), Error
> + Send
>>,
31 ) -> Result
<(), Error
> {
32 proxmox_backup
::pxar
::extract_archive(
33 pxar
::decoder
::Decoder
::from_std(reader
)?
,
36 extract_match_default
,
41 println
!("{:?}", path
);
52 description
: "Archive name.",
55 description
: "List of paths or pattern matching files to restore",
59 description
: "Path or pattern matching files to restore.",
64 description
: "Target directory",
68 description
: "Verbose output.",
73 description
: "Ignore extended file attributes.",
78 description
: "Ignore file capabilities.",
83 description
: "Ignore access control list entries.",
87 "allow-existing-dirs": {
88 description
: "Allows directories to already exist on restore.",
93 description
: "File containing match pattern for files to restore.",
97 description
: "Ignore device nodes.",
102 description
: "Ignore fifos.",
107 description
: "Ignore sockets.",
112 description
: "Stop on errors. Otherwise most errors will simply warn.",
119 /// Extract an archive.
122 pattern
: Option
<Vec
<String
>>,
123 target
: Option
<String
>,
128 allow_existing_dirs
: bool
,
129 files_from
: Option
<String
>,
130 no_device_nodes
: bool
,
134 ) -> Result
<(), Error
> {
135 let mut feature_flags
= Flags
::DEFAULT
;
137 feature_flags ^
= Flags
::WITH_XATTRS
;
140 feature_flags ^
= Flags
::WITH_FCAPS
;
143 feature_flags ^
= Flags
::WITH_ACL
;
146 feature_flags ^
= Flags
::WITH_DEVICE_NODES
;
149 feature_flags ^
= Flags
::WITH_FIFOS
;
152 feature_flags ^
= Flags
::WITH_SOCKETS
;
155 let pattern
= pattern
.unwrap_or_else(Vec
::new
);
156 let target
= target
.as_ref().map_or_else(|| ".", String
::as_str
);
158 let mut match_list
= Vec
::new();
159 if let Some(filename
) = &files_from
{
160 for line
in proxmox_backup
::tools
::file_get_non_comment_lines(filename
)?
{
162 .map_err(|err
| format_err
!("error reading {}: {}", filename
, err
))?
;
164 MatchEntry
::parse_pattern(line
, PatternFlag
::PATH_NAME
, MatchType
::Include
)
165 .map_err(|err
| format_err
!("bad pattern in file '{}': {}", filename
, err
))?
,
170 for entry
in pattern
{
172 MatchEntry
::parse_pattern(entry
, PatternFlag
::PATH_NAME
, MatchType
::Include
)
173 .map_err(|err
| format_err
!("error in pattern: {}", err
))?
,
177 let extract_match_default
= match_list
.is_empty();
179 let was_ok
= Arc
::new(AtomicBool
::new(true));
180 let on_error
= if strict
{
181 // by default errors are propagated up
184 let was_ok
= Arc
::clone(&was_ok
);
185 // otherwise we want to log them but not act on them
186 Some(Box
::new(move |err
| {
187 was_ok
.store(false, Ordering
::Release
);
188 eprintln
!("error: {}", err
);
190 }) as Box
<dyn FnMut(Error
) -> Result
<(), Error
> + Send
>)
194 let stdin
= std
::io
::stdin();
195 let mut reader
= stdin
.lock();
196 extract_archive_from_reader(
203 extract_match_default
,
208 println
!("PXAR extract: {}", archive
);
210 let file
= std
::fs
::File
::open(archive
)?
;
211 let mut reader
= std
::io
::BufReader
::new(file
);
212 extract_archive_from_reader(
219 extract_match_default
,
224 if !was_ok
.load(Ordering
::Acquire
) {
225 bail
!("there were errors");
235 description
: "Archive name.",
238 description
: "Source directory.",
241 description
: "Verbose output.",
246 description
: "Ignore extended file attributes.",
251 description
: "Ignore file capabilities.",
256 description
: "Ignore access control list entries.",
260 "all-file-systems": {
261 description
: "Include mounted sudirs.",
266 description
: "Ignore device nodes.",
271 description
: "Ignore fifos.",
276 description
: "Ignore sockets.",
281 description
: "List of paths or pattern matching files to exclude.",
285 description
: "Path or pattern matching files to restore",
290 description
: "Max number of entries loaded at once into memory",
292 default: ENCODER_MAX_ENTRIES
as isize,
294 maximum
: std
::isize::MAX
,
299 /// Create a new .pxar archive.
307 all_file_systems
: bool
,
308 no_device_nodes
: bool
,
311 exclude
: Option
<Vec
<String
>>,
313 ) -> Result
<(), Error
> {
315 let input
= exclude
.unwrap_or_else(Vec
::new
);
316 let mut pattern_list
= Vec
::with_capacity(input
.len());
319 MatchEntry
::parse_pattern(entry
, PatternFlag
::PATH_NAME
, MatchType
::Exclude
)
320 .map_err(|err
| format_err
!("error in exclude pattern: {}", err
))?
,
326 let device_set
= if all_file_systems
{
332 let source
= PathBuf
::from(source
);
334 let dir
= nix
::dir
::Dir
::open(
336 nix
::fcntl
::OFlag
::O_NOFOLLOW
,
337 nix
::sys
::stat
::Mode
::empty(),
340 let file
= OpenOptions
::new()
346 let writer
= std
::io
::BufWriter
::with_capacity(1024 * 1024, file
);
347 let mut feature_flags
= Flags
::DEFAULT
;
349 feature_flags ^
= Flags
::WITH_XATTRS
;
352 feature_flags ^
= Flags
::WITH_FCAPS
;
355 feature_flags ^
= Flags
::WITH_ACL
;
358 feature_flags ^
= Flags
::WITH_DEVICE_NODES
;
361 feature_flags ^
= Flags
::WITH_FIFOS
;
364 feature_flags ^
= Flags
::WITH_SOCKETS
;
367 let writer
= pxar
::encoder
::sync
::StandardWriter
::new(writer
);
368 proxmox_backup
::pxar
::create_archive(
377 println
!("{:?}", path
);
381 entries_max
as usize,
391 archive
: { description: "Archive name." }
,
392 mountpoint
: { description: "Mountpoint for the file system." }
,
394 description
: "Verbose output, running in the foreground (for debugging).",
401 /// Mount the archive to the provided mountpoint via FUSE.
402 async
fn mount_archive(
406 ) -> Result
<(), Error
> {
407 let archive
= Path
::new(&archive
);
408 let mountpoint
= Path
::new(&mountpoint
);
409 let options
= OsStr
::new("ro,default_permissions");
411 let session
= fuse
::Session
::mount_path(&archive
, &options
, verbose
, mountpoint
)
413 .map_err(|err
| format_err
!("pxar mount failed: {}", err
))?
;
415 let mut interrupt
= signal(SignalKind
::interrupt())?
;
418 res
= session
.fuse() => res?
,
419 _
= interrupt
.recv().fuse() => {
421 eprintln
!("interrupted");
433 description
: "Archive name.",
436 description
: "Verbose output.",
443 /// List the contents of an archive.
444 fn dump_archive(archive
: String
, verbose
: bool
) -> Result
<(), Error
> {
445 for entry
in pxar
::decoder
::Decoder
::open(archive
)?
{
449 println
!("{}", format_single_line_entry(&entry
));
451 println
!("{:?}", entry
.path());
458 let cmd_def
= CliCommandMap
::new()
461 CliCommand
::new(&API_METHOD_CREATE_ARCHIVE
)
462 .arg_param(&["archive", "source"])
463 .completion_cb("archive", tools
::complete_file_name
)
464 .completion_cb("source", tools
::complete_file_name
),
468 CliCommand
::new(&API_METHOD_EXTRACT_ARCHIVE
)
469 .arg_param(&["archive", "target"])
470 .completion_cb("archive", tools
::complete_file_name
)
471 .completion_cb("target", tools
::complete_file_name
)
472 .completion_cb("files-from", tools
::complete_file_name
),
476 CliCommand
::new(&API_METHOD_MOUNT_ARCHIVE
)
477 .arg_param(&["archive", "mountpoint"])
478 .completion_cb("archive", tools
::complete_file_name
)
479 .completion_cb("mountpoint", tools
::complete_file_name
),
483 CliCommand
::new(&API_METHOD_DUMP_ARCHIVE
)
484 .arg_param(&["archive"])
485 .completion_cb("archive", tools
::complete_file_name
),
488 let rpcenv
= CliEnvironment
::new();
489 run_cli_command(cmd_def
, rpcenv
, Some(|future
| {
490 proxmox_backup
::tools
::runtime
::main(future
)