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, ErrorHandler, 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
<ErrorHandler
>,
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 patterns
= 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 options
= proxmox_backup
::pxar
::PxarCreateOptions
{
333 entries_max
: entries_max
as usize,
337 skip_lost_and_found
: false,
341 let source
= PathBuf
::from(source
);
343 let dir
= nix
::dir
::Dir
::open(
345 nix
::fcntl
::OFlag
::O_NOFOLLOW
,
346 nix
::sys
::stat
::Mode
::empty(),
349 let file
= OpenOptions
::new()
355 let writer
= std
::io
::BufWriter
::with_capacity(1024 * 1024, file
);
356 let mut feature_flags
= Flags
::DEFAULT
;
358 feature_flags ^
= Flags
::WITH_XATTRS
;
361 feature_flags ^
= Flags
::WITH_FCAPS
;
364 feature_flags ^
= Flags
::WITH_ACL
;
367 feature_flags ^
= Flags
::WITH_DEVICE_NODES
;
370 feature_flags ^
= Flags
::WITH_FIFOS
;
373 feature_flags ^
= Flags
::WITH_SOCKETS
;
376 let writer
= pxar
::encoder
::sync
::StandardWriter
::new(writer
);
377 proxmox_backup
::pxar
::create_archive(
383 println
!("{:?}", path
);
397 archive
: { description: "Archive name." }
,
398 mountpoint
: { description: "Mountpoint for the file system." }
,
400 description
: "Verbose output, running in the foreground (for debugging).",
407 /// Mount the archive to the provided mountpoint via FUSE.
408 async
fn mount_archive(
412 ) -> Result
<(), Error
> {
413 let archive
= Path
::new(&archive
);
414 let mountpoint
= Path
::new(&mountpoint
);
415 let options
= OsStr
::new("ro,default_permissions");
417 let session
= fuse
::Session
::mount_path(&archive
, &options
, verbose
, mountpoint
)
419 .map_err(|err
| format_err
!("pxar mount failed: {}", err
))?
;
421 let mut interrupt
= signal(SignalKind
::interrupt())?
;
424 res
= session
.fuse() => res?
,
425 _
= interrupt
.recv().fuse() => {
427 eprintln
!("interrupted");
439 description
: "Archive name.",
442 description
: "Verbose output.",
449 /// List the contents of an archive.
450 fn dump_archive(archive
: String
, verbose
: bool
) -> Result
<(), Error
> {
451 for entry
in pxar
::decoder
::Decoder
::open(archive
)?
{
455 println
!("{}", format_single_line_entry(&entry
));
457 println
!("{:?}", entry
.path());
464 let cmd_def
= CliCommandMap
::new()
467 CliCommand
::new(&API_METHOD_CREATE_ARCHIVE
)
468 .arg_param(&["archive", "source"])
469 .completion_cb("archive", tools
::complete_file_name
)
470 .completion_cb("source", tools
::complete_file_name
),
474 CliCommand
::new(&API_METHOD_EXTRACT_ARCHIVE
)
475 .arg_param(&["archive", "target"])
476 .completion_cb("archive", tools
::complete_file_name
)
477 .completion_cb("target", tools
::complete_file_name
)
478 .completion_cb("files-from", tools
::complete_file_name
),
482 CliCommand
::new(&API_METHOD_MOUNT_ARCHIVE
)
483 .arg_param(&["archive", "mountpoint"])
484 .completion_cb("archive", tools
::complete_file_name
)
485 .completion_cb("mountpoint", tools
::complete_file_name
),
489 CliCommand
::new(&API_METHOD_DUMP_ARCHIVE
)
490 .arg_param(&["archive"])
491 .completion_cb("archive", tools
::complete_file_name
),
494 let rpcenv
= CliEnvironment
::new();
495 run_cli_command(cmd_def
, rpcenv
, Some(|future
| {
496 proxmox_backup
::tools
::runtime
::main(future
)