1 extern crate proxmox_backup
;
5 use proxmox
::{sortable, identity}
;
6 use proxmox
::api
::{ApiHandler, ApiMethod, RpcEnvironment}
;
7 use proxmox
::api
::schema
::*;
8 use proxmox
::api
::cli
::*;
10 use proxmox_backup
::tools
;
12 use serde_json
::{Value}
;
15 use std
::path
::{Path, PathBuf}
;
16 use std
::fs
::OpenOptions
;
18 use std
::os
::unix
::fs
::OpenOptionsExt
;
19 use std
::os
::unix
::io
::AsRawFd
;
20 use std
::collections
::HashSet
;
22 use proxmox_backup
::pxar
;
24 fn dump_archive_from_reader
<R
: std
::io
::Read
>(
28 ) -> Result
<(), Error
> {
29 let mut decoder
= pxar
::SequentialDecoder
::new(reader
, feature_flags
);
31 let stdout
= std
::io
::stdout();
32 let mut out
= stdout
.lock();
34 let mut path
= PathBuf
::new();
35 decoder
.dump_entry(&mut path
, verbose
, &mut out
)?
;
43 _rpcenv
: &mut dyn RpcEnvironment
,
44 ) -> Result
<Value
, Error
> {
46 let archive
= tools
::required_string_param(¶m
, "archive")?
;
47 let verbose
= param
["verbose"].as_bool().unwrap_or(false);
49 let feature_flags
= pxar
::flags
::DEFAULT
;
52 let stdin
= std
::io
::stdin();
53 let mut reader
= stdin
.lock();
54 dump_archive_from_reader(&mut reader
, feature_flags
, verbose
)?
;
56 if verbose { println!("PXAR dump: {}
", archive); }
57 let file = std::fs::File::open(archive)?;
58 let mut reader = std::io::BufReader::new(file);
59 dump_archive_from_reader(&mut reader, feature_flags, verbose)?;
65 fn extract_archive_from_reader<R: std::io::Read>(
69 allow_existing_dirs: bool,
71 pattern: Option<Vec<pxar::MatchPattern>>
72 ) -> Result<(), Error> {
73 let mut decoder = pxar::SequentialDecoder::new(reader, feature_flags);
74 decoder.set_callback(move |path| {
76 println!("{:?}
", path);
80 decoder.set_allow_existing_dirs(allow_existing_dirs);
82 let pattern = pattern.unwrap_or_else(Vec::new);
83 decoder.restore(Path::new(target), &pattern)?;
91 _rpcenv: &mut dyn RpcEnvironment,
92 ) -> Result<Value, Error> {
94 let archive = tools::required_string_param(¶m, "archive
")?;
95 let target = param["target
"].as_str().unwrap_or(".");
96 let verbose = param["verbose
"].as_bool().unwrap_or(false);
97 let no_xattrs = param["no
-xattrs
"].as_bool().unwrap_or(false);
98 let no_fcaps = param["no
-fcaps
"].as_bool().unwrap_or(false);
99 let no_acls = param["no
-acls
"].as_bool().unwrap_or(false);
100 let no_device_nodes = param["no
-device
-nodes
"].as_bool().unwrap_or(false);
101 let no_fifos = param["no
-fifos
"].as_bool().unwrap_or(false);
102 let no_sockets = param["no
-sockets
"].as_bool().unwrap_or(false);
103 let allow_existing_dirs = param["allow
-existing
-dirs
"].as_bool().unwrap_or(false);
104 let files_from = param["files
-from
"].as_str();
105 let empty = Vec::new();
106 let arg_pattern = param["pattern
"].as_array().unwrap_or(&empty);
108 let mut feature_flags = pxar::flags::DEFAULT;
110 feature_flags ^= pxar::flags::WITH_XATTRS;
113 feature_flags ^= pxar::flags::WITH_FCAPS;
116 feature_flags ^= pxar::flags::WITH_ACL;
119 feature_flags ^= pxar::flags::WITH_DEVICE_NODES;
122 feature_flags ^= pxar::flags::WITH_FIFOS;
125 feature_flags ^= pxar::flags::WITH_SOCKETS;
128 let mut pattern_list = Vec::new();
129 if let Some(filename) = files_from {
130 let dir = nix::dir::Dir::open("./", nix::fcntl::OFlag::O_RDONLY, nix::sys::stat::Mode::empty())?;
131 if let Some((mut pattern, _, _)) = pxar::MatchPattern::from_file(dir.as_raw_fd(), filename)? {
132 pattern_list.append(&mut pattern);
136 for s in arg_pattern {
137 let l = s.as_str().ok_or_else(|| format_err!("Invalid pattern string slice
"))?;
138 let p = pxar::MatchPattern::from_line(l.as_bytes())?
139 .ok_or_else(|| format_err!("Invalid
match pattern
in arguments
"))?;
140 pattern_list.push(p);
143 let pattern = if pattern_list.is_empty() {
150 let stdin = std::io::stdin();
151 let mut reader = stdin.lock();
152 extract_archive_from_reader(&mut reader, target, feature_flags, allow_existing_dirs, verbose, pattern)?;
154 if verbose { println!("PXAR extract: {}", archive
); }
155 let file
= std
::fs
::File
::open(archive
)?
;
156 let mut reader
= std
::io
::BufReader
::new(file
);
157 extract_archive_from_reader(&mut reader
, target
, feature_flags
, allow_existing_dirs
, verbose
, pattern
)?
;
166 _rpcenv
: &mut dyn RpcEnvironment
,
167 ) -> Result
<Value
, Error
> {
169 let archive
= tools
::required_string_param(¶m
, "archive")?
;
170 let source
= tools
::required_string_param(¶m
, "source")?
;
171 let verbose
= param
["verbose"].as_bool().unwrap_or(false);
172 let all_file_systems
= param
["all-file-systems"].as_bool().unwrap_or(false);
173 let no_xattrs
= param
["no-xattrs"].as_bool().unwrap_or(false);
174 let no_fcaps
= param
["no-fcaps"].as_bool().unwrap_or(false);
175 let no_acls
= param
["no-acls"].as_bool().unwrap_or(false);
176 let no_device_nodes
= param
["no-device-nodes"].as_bool().unwrap_or(false);
177 let no_fifos
= param
["no-fifos"].as_bool().unwrap_or(false);
178 let no_sockets
= param
["no-sockets"].as_bool().unwrap_or(false);
179 let empty
= Vec
::new();
180 let exclude_pattern
= param
["exclude"].as_array().unwrap_or(&empty
);
181 let entries_max
= param
["entries-max"].as_u64().unwrap_or(pxar
::ENCODER_MAX_ENTRIES
as u64);
183 let devices
= if all_file_systems { None }
else { Some(HashSet::new()) }
;
185 let source
= PathBuf
::from(source
);
187 let mut dir
= nix
::dir
::Dir
::open(
188 &source
, nix
::fcntl
::OFlag
::O_NOFOLLOW
, nix
::sys
::stat
::Mode
::empty())?
;
190 let file
= OpenOptions
::new()
196 let mut writer
= std
::io
::BufWriter
::with_capacity(1024*1024, file
);
197 let mut feature_flags
= pxar
::flags
::DEFAULT
;
199 feature_flags ^
= pxar
::flags
::WITH_XATTRS
;
202 feature_flags ^
= pxar
::flags
::WITH_FCAPS
;
205 feature_flags ^
= pxar
::flags
::WITH_ACL
;
208 feature_flags ^
= pxar
::flags
::WITH_DEVICE_NODES
;
211 feature_flags ^
= pxar
::flags
::WITH_FIFOS
;
214 feature_flags ^
= pxar
::flags
::WITH_SOCKETS
;
217 let mut pattern_list
= Vec
::new();
218 for s
in exclude_pattern
{
219 let l
= s
.as_str().ok_or_else(|| format_err
!("Invalid pattern string slice"))?
;
220 let p
= pxar
::MatchPattern
::from_line(l
.as_bytes())?
221 .ok_or_else(|| format_err
!("Invalid match pattern in arguments"))?
;
222 pattern_list
.push(p
);
225 let catalog
= None
::<&mut pxar
::catalog
::DummyCatalogWriter
>;
226 pxar
::Encoder
::encode(
236 entries_max
as usize,
244 /// Mount the archive to the provided mountpoint via FUSE.
248 _rpcenv
: &mut dyn RpcEnvironment
,
249 ) -> Result
<Value
, Error
> {
250 let archive
= tools
::required_string_param(¶m
, "archive")?
;
251 let mountpoint
= tools
::required_string_param(¶m
, "mountpoint")?
;
252 let verbose
= param
["verbose"].as_bool().unwrap_or(false);
253 let no_mt
= param
["no-mt"].as_bool().unwrap_or(false);
255 let archive
= Path
::new(archive
);
256 let mountpoint
= Path
::new(mountpoint
);
257 let options
= OsStr
::new("ro,default_permissions");
258 let mut session
= pxar
::fuse
::Session
::from_path(&archive
, &options
, verbose
)
259 .map_err(|err
| format_err
!("pxar mount failed: {}", err
))?
;
260 // Mount the session and deamonize if verbose is not set
261 session
.mount(&mountpoint
, !verbose
)?
;
262 session
.run_loop(!no_mt
)?
;
268 const API_METHOD_CREATE_ARCHIVE
: ApiMethod
= ApiMethod
::new(
269 &ApiHandler
::Sync(&create_archive
),
271 "Create new .pxar archive.",
276 &StringSchema
::new("Archive name").schema()
281 &StringSchema
::new("Source directory.").schema()
286 &BooleanSchema
::new("Verbose output.")
293 &BooleanSchema
::new("Ignore extended file attributes.")
300 &BooleanSchema
::new("Ignore file capabilities.")
307 &BooleanSchema
::new("Ignore access control list entries.")
314 &BooleanSchema
::new("Include mounted sudirs.")
321 &BooleanSchema
::new("Ignore device nodes.")
328 &BooleanSchema
::new("Ignore fifos.")
335 &BooleanSchema
::new("Ignore sockets.")
343 "List of paths or pattern matching files to exclude.",
344 &StringSchema
::new("Path or pattern matching files to restore.").schema()
350 &IntegerSchema
::new("Max number of entries loaded at once into memory")
351 .default(pxar
::ENCODER_MAX_ENTRIES
as isize)
353 .maximum(std
::isize::MAX
)
361 const API_METHOD_EXTRACT_ARCHIVE
: ApiMethod
= ApiMethod
::new(
362 &ApiHandler
::Sync(&extract_archive
),
364 "Extract an archive.",
369 &StringSchema
::new("Archive name.").schema()
375 "List of paths or pattern matching files to restore",
376 &StringSchema
::new("Path or pattern matching files to restore.").schema()
382 &StringSchema
::new("Target directory.").schema()
387 &BooleanSchema
::new("Verbose output.")
394 &BooleanSchema
::new("Ignore extended file attributes.")
401 &BooleanSchema
::new("Ignore file capabilities.")
408 &BooleanSchema
::new("Ignore access control list entries.")
413 "allow-existing-dirs",
415 &BooleanSchema
::new("Allows directories to already exist on restore.")
422 &StringSchema
::new("Match pattern for files to restore.").schema()
427 &BooleanSchema
::new("Ignore device nodes.")
434 &BooleanSchema
::new("Ignore fifos.")
441 &BooleanSchema
::new("Ignore sockets.")
450 const API_METHOD_MOUNT_ARCHIVE
: ApiMethod
= ApiMethod
::new(
451 &ApiHandler
::Sync(&mount_archive
),
453 "Mount the archive as filesystem via FUSE.",
458 &StringSchema
::new("Archive name.").schema()
463 &StringSchema
::new("Mountpoint for the filesystem root.").schema()
468 &BooleanSchema
::new("Verbose output, keeps process running in foreground (for debugging).")
475 &BooleanSchema
::new("Run in single threaded mode (for debugging).")
484 const API_METHOD_DUMP_ARCHIVE
: ApiMethod
= ApiMethod
::new(
485 &ApiHandler
::Sync(&dump_archive
),
487 "List the contents of an archive.",
489 ( "archive", false, &StringSchema
::new("Archive name.").schema()),
490 ( "verbose", true, &BooleanSchema
::new("Verbose output.")
500 let cmd_def
= CliCommandMap
::new()
501 .insert("create", CliCommand
::new(&API_METHOD_CREATE_ARCHIVE
)
502 .arg_param(&["archive", "source"])
503 .completion_cb("archive", tools
::complete_file_name
)
504 .completion_cb("source", tools
::complete_file_name
)
506 .insert("extract", CliCommand
::new(&API_METHOD_EXTRACT_ARCHIVE
)
507 .arg_param(&["archive", "target"])
508 .completion_cb("archive", tools
::complete_file_name
)
509 .completion_cb("target", tools
::complete_file_name
)
510 .completion_cb("files-from", tools
::complete_file_name
)
512 .insert("mount", CliCommand
::new(&API_METHOD_MOUNT_ARCHIVE
)
513 .arg_param(&["archive", "mountpoint"])
514 .completion_cb("archive", tools
::complete_file_name
)
515 .completion_cb("mountpoint", tools
::complete_file_name
)
517 .insert("list", CliCommand
::new(&API_METHOD_DUMP_ARCHIVE
)
518 .arg_param(&["archive"])
519 .completion_cb("archive", tools
::complete_file_name
)
522 run_cli_command(cmd_def
, None
);