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, PxarExtractOptions}
;
22 fn extract_archive_from_reader
<R
: std
::io
::Read
>(
27 options
: PxarExtractOptions
,
28 ) -> Result
<(), Error
> {
30 proxmox_backup
::pxar
::extract_archive(
31 pxar
::decoder
::Decoder
::from_std(reader
)?
,
36 println
!("{:?}", path
);
47 description
: "Archive name.",
50 description
: "List of paths or pattern matching files to restore",
54 description
: "Path or pattern matching files to restore.",
59 description
: "Target directory",
63 description
: "Verbose output.",
68 description
: "Ignore extended file attributes.",
73 description
: "Ignore file capabilities.",
78 description
: "Ignore access control list entries.",
82 "allow-existing-dirs": {
83 description
: "Allows directories to already exist on restore.",
88 description
: "File containing match pattern for files to restore.",
92 description
: "Ignore device nodes.",
97 description
: "Ignore fifos.",
102 description
: "Ignore sockets.",
107 description
: "Stop on errors. Otherwise most errors will simply warn.",
114 /// Extract an archive.
115 #[allow(clippy::too_many_arguments)]
118 pattern
: Option
<Vec
<String
>>,
119 target
: Option
<String
>,
124 allow_existing_dirs
: bool
,
125 files_from
: Option
<String
>,
126 no_device_nodes
: bool
,
130 ) -> Result
<(), Error
> {
131 let mut feature_flags
= Flags
::DEFAULT
;
133 feature_flags ^
= Flags
::WITH_XATTRS
;
136 feature_flags ^
= Flags
::WITH_FCAPS
;
139 feature_flags ^
= Flags
::WITH_ACL
;
142 feature_flags ^
= Flags
::WITH_DEVICE_NODES
;
145 feature_flags ^
= Flags
::WITH_FIFOS
;
148 feature_flags ^
= Flags
::WITH_SOCKETS
;
151 let pattern
= pattern
.unwrap_or_else(Vec
::new
);
152 let target
= target
.as_ref().map_or_else(|| ".", String
::as_str
);
154 let mut match_list
= Vec
::new();
155 if let Some(filename
) = &files_from
{
156 for line
in proxmox_backup
::tools
::file_get_non_comment_lines(filename
)?
{
158 .map_err(|err
| format_err
!("error reading {}: {}", filename
, err
))?
;
160 MatchEntry
::parse_pattern(line
, PatternFlag
::PATH_NAME
, MatchType
::Include
)
161 .map_err(|err
| format_err
!("bad pattern in file '{}': {}", filename
, err
))?
,
166 for entry
in pattern
{
168 MatchEntry
::parse_pattern(entry
, PatternFlag
::PATH_NAME
, MatchType
::Include
)
169 .map_err(|err
| format_err
!("error in pattern: {}", err
))?
,
173 let extract_match_default
= match_list
.is_empty();
175 let was_ok
= Arc
::new(AtomicBool
::new(true));
176 let on_error
= if strict
{
177 // by default errors are propagated up
180 let was_ok
= Arc
::clone(&was_ok
);
181 // otherwise we want to log them but not act on them
182 Some(Box
::new(move |err
| {
183 was_ok
.store(false, Ordering
::Release
);
184 eprintln
!("error: {}", err
);
186 }) as Box
<dyn FnMut(Error
) -> Result
<(), Error
> + Send
>)
189 let options
= PxarExtractOptions
{
190 match_list
: &match_list
,
192 extract_match_default
,
197 let stdin
= std
::io
::stdin();
198 let mut reader
= stdin
.lock();
199 extract_archive_from_reader(
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(
221 if !was_ok
.load(Ordering
::Acquire
) {
222 bail
!("there were errors");
232 description
: "Archive name.",
235 description
: "Source directory.",
238 description
: "Verbose output.",
243 description
: "Ignore extended file attributes.",
248 description
: "Ignore file capabilities.",
253 description
: "Ignore access control list entries.",
257 "all-file-systems": {
258 description
: "Include mounted sudirs.",
263 description
: "Ignore device nodes.",
268 description
: "Ignore fifos.",
273 description
: "Ignore sockets.",
278 description
: "List of paths or pattern matching files to exclude.",
282 description
: "Path or pattern matching files to restore",
287 description
: "Max number of entries loaded at once into memory",
289 default: ENCODER_MAX_ENTRIES
as isize,
291 maximum
: std
::isize::MAX
,
296 /// Create a new .pxar archive.
297 #[allow(clippy::too_many_arguments)]
298 async
fn create_archive(
305 all_file_systems
: bool
,
306 no_device_nodes
: bool
,
309 exclude
: Option
<Vec
<String
>>,
311 ) -> Result
<(), Error
> {
313 let input
= exclude
.unwrap_or_else(Vec
::new
);
314 let mut patterns
= Vec
::with_capacity(input
.len());
317 MatchEntry
::parse_pattern(entry
, PatternFlag
::PATH_NAME
, MatchType
::Exclude
)
318 .map_err(|err
| format_err
!("error in exclude pattern: {}", err
))?
,
324 let device_set
= if all_file_systems
{
330 let options
= proxmox_backup
::pxar
::PxarCreateOptions
{
331 entries_max
: entries_max
as usize,
335 skip_lost_and_found
: false,
339 let source
= PathBuf
::from(source
);
341 let dir
= nix
::dir
::Dir
::open(
343 nix
::fcntl
::OFlag
::O_NOFOLLOW
,
344 nix
::sys
::stat
::Mode
::empty(),
347 let file
= OpenOptions
::new()
353 let writer
= std
::io
::BufWriter
::with_capacity(1024 * 1024, file
);
354 let mut feature_flags
= Flags
::DEFAULT
;
356 feature_flags ^
= Flags
::WITH_XATTRS
;
359 feature_flags ^
= Flags
::WITH_FCAPS
;
362 feature_flags ^
= Flags
::WITH_ACL
;
365 feature_flags ^
= Flags
::WITH_DEVICE_NODES
;
368 feature_flags ^
= Flags
::WITH_FIFOS
;
371 feature_flags ^
= Flags
::WITH_SOCKETS
;
374 let writer
= pxar
::encoder
::sync
::StandardWriter
::new(writer
);
375 proxmox_backup
::pxar
::create_archive(
381 println
!("{:?}", path
);
395 archive
: { description: "Archive name." }
,
396 mountpoint
: { description: "Mountpoint for the file system." }
,
398 description
: "Verbose output, running in the foreground (for debugging).",
405 /// Mount the archive to the provided mountpoint via FUSE.
406 async
fn mount_archive(
410 ) -> Result
<(), Error
> {
411 let archive
= Path
::new(&archive
);
412 let mountpoint
= Path
::new(&mountpoint
);
413 let options
= OsStr
::new("ro,default_permissions");
415 let session
= fuse
::Session
::mount_path(&archive
, &options
, verbose
, mountpoint
)
417 .map_err(|err
| format_err
!("pxar mount failed: {}", err
))?
;
419 let mut interrupt
= signal(SignalKind
::interrupt())?
;
422 res
= session
.fuse() => res?
,
423 _
= interrupt
.recv().fuse() => {
425 eprintln
!("interrupted");
437 description
: "Archive name.",
440 description
: "Verbose output.",
447 /// List the contents of an archive.
448 fn dump_archive(archive
: String
, verbose
: bool
) -> Result
<(), Error
> {
449 for entry
in pxar
::decoder
::Decoder
::open(archive
)?
{
453 println
!("{}", format_single_line_entry(&entry
));
455 println
!("{:?}", entry
.path());
462 let cmd_def
= CliCommandMap
::new()
465 CliCommand
::new(&API_METHOD_CREATE_ARCHIVE
)
466 .arg_param(&["archive", "source"])
467 .completion_cb("archive", tools
::complete_file_name
)
468 .completion_cb("source", tools
::complete_file_name
),
472 CliCommand
::new(&API_METHOD_EXTRACT_ARCHIVE
)
473 .arg_param(&["archive", "target"])
474 .completion_cb("archive", tools
::complete_file_name
)
475 .completion_cb("target", tools
::complete_file_name
)
476 .completion_cb("files-from", tools
::complete_file_name
),
480 CliCommand
::new(&API_METHOD_MOUNT_ARCHIVE
)
481 .arg_param(&["archive", "mountpoint"])
482 .completion_cb("archive", tools
::complete_file_name
)
483 .completion_cb("mountpoint", tools
::complete_file_name
),
487 CliCommand
::new(&API_METHOD_DUMP_ARCHIVE
)
488 .arg_param(&["archive"])
489 .completion_cb("archive", tools
::complete_file_name
),
492 let rpcenv
= CliEnvironment
::new();
493 run_cli_command(cmd_def
, rpcenv
, Some(|future
| {
494 proxmox_backup
::tools
::runtime
::main(future
)