1 extern crate proxmox_backup
;
5 use proxmox
::{sortable, identity}
;
6 use proxmox
::api
::{ApiHandler, ApiMethod, RpcEnvironment}
;
7 use proxmox
::api
::schema
::*;
9 use proxmox_backup
::tools
;
10 use proxmox_backup
::cli
::*;
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
, |_
| Ok(()));
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, |path| {
75 println!("{:?}
", path);
79 decoder.set_allow_existing_dirs(allow_existing_dirs);
81 let pattern = pattern.unwrap_or_else(Vec::new);
82 decoder.restore(Path::new(target), &pattern)?;
90 _rpcenv: &mut dyn RpcEnvironment,
91 ) -> Result<Value, Error> {
93 let archive = tools::required_string_param(¶m, "archive
")?;
94 let target = param["target
"].as_str().unwrap_or(".");
95 let verbose = param["verbose
"].as_bool().unwrap_or(false);
96 let no_xattrs = param["no
-xattrs
"].as_bool().unwrap_or(false);
97 let no_fcaps = param["no
-fcaps
"].as_bool().unwrap_or(false);
98 let no_acls = param["no
-acls
"].as_bool().unwrap_or(false);
99 let no_device_nodes = param["no
-device
-nodes
"].as_bool().unwrap_or(false);
100 let no_fifos = param["no
-fifos
"].as_bool().unwrap_or(false);
101 let no_sockets = param["no
-sockets
"].as_bool().unwrap_or(false);
102 let allow_existing_dirs = param["allow
-existing
-dirs
"].as_bool().unwrap_or(false);
103 let files_from = param["files
-from
"].as_str();
104 let empty = Vec::new();
105 let arg_pattern = param["pattern
"].as_array().unwrap_or(&empty);
107 let mut feature_flags = pxar::flags::DEFAULT;
109 feature_flags ^= pxar::flags::WITH_XATTRS;
112 feature_flags ^= pxar::flags::WITH_FCAPS;
115 feature_flags ^= pxar::flags::WITH_ACL;
118 feature_flags ^= pxar::flags::WITH_DEVICE_NODES;
121 feature_flags ^= pxar::flags::WITH_FIFOS;
124 feature_flags ^= pxar::flags::WITH_SOCKETS;
127 let mut pattern_list = Vec::new();
128 if let Some(filename) = files_from {
129 let dir = nix::dir::Dir::open("./", nix::fcntl::OFlag::O_RDONLY, nix::sys::stat::Mode::empty())?;
130 if let Some((mut pattern, _, _)) = pxar::MatchPattern::from_file(dir.as_raw_fd(), filename)? {
131 pattern_list.append(&mut pattern);
135 for s in arg_pattern {
136 let l = s.as_str().ok_or_else(|| format_err!("Invalid pattern string slice
"))?;
137 let p = pxar::MatchPattern::from_line(l.as_bytes())?
138 .ok_or_else(|| format_err!("Invalid
match pattern
in arguments
"))?;
139 pattern_list.push(p);
142 let pattern = if pattern_list.is_empty() {
149 let stdin = std::io::stdin();
150 let mut reader = stdin.lock();
151 extract_archive_from_reader(&mut reader, target, feature_flags, allow_existing_dirs, verbose, pattern)?;
153 if verbose { println!("PXAR extract: {}", archive
); }
154 let file
= std
::fs
::File
::open(archive
)?
;
155 let mut reader
= std
::io
::BufReader
::new(file
);
156 extract_archive_from_reader(&mut reader
, target
, feature_flags
, allow_existing_dirs
, verbose
, pattern
)?
;
165 _rpcenv
: &mut dyn RpcEnvironment
,
166 ) -> Result
<Value
, Error
> {
168 let archive
= tools
::required_string_param(¶m
, "archive")?
;
169 let source
= tools
::required_string_param(¶m
, "source")?
;
170 let verbose
= param
["verbose"].as_bool().unwrap_or(false);
171 let all_file_systems
= param
["all-file-systems"].as_bool().unwrap_or(false);
172 let no_xattrs
= param
["no-xattrs"].as_bool().unwrap_or(false);
173 let no_fcaps
= param
["no-fcaps"].as_bool().unwrap_or(false);
174 let no_acls
= param
["no-acls"].as_bool().unwrap_or(false);
175 let no_device_nodes
= param
["no-device-nodes"].as_bool().unwrap_or(false);
176 let no_fifos
= param
["no-fifos"].as_bool().unwrap_or(false);
177 let no_sockets
= param
["no-sockets"].as_bool().unwrap_or(false);
178 let empty
= Vec
::new();
179 let exclude_pattern
= param
["exclude"].as_array().unwrap_or(&empty
);
181 let devices
= if all_file_systems { None }
else { Some(HashSet::new()) }
;
183 let source
= PathBuf
::from(source
);
185 let mut dir
= nix
::dir
::Dir
::open(
186 &source
, nix
::fcntl
::OFlag
::O_NOFOLLOW
, nix
::sys
::stat
::Mode
::empty())?
;
188 let file
= OpenOptions
::new()
194 let mut writer
= std
::io
::BufWriter
::with_capacity(1024*1024, file
);
195 let mut feature_flags
= pxar
::flags
::DEFAULT
;
197 feature_flags ^
= pxar
::flags
::WITH_XATTRS
;
200 feature_flags ^
= pxar
::flags
::WITH_FCAPS
;
203 feature_flags ^
= pxar
::flags
::WITH_ACL
;
206 feature_flags ^
= pxar
::flags
::WITH_DEVICE_NODES
;
209 feature_flags ^
= pxar
::flags
::WITH_FIFOS
;
212 feature_flags ^
= pxar
::flags
::WITH_SOCKETS
;
215 let mut pattern_list
= Vec
::new();
216 for s
in exclude_pattern
{
217 let l
= s
.as_str().ok_or_else(|| format_err
!("Invalid pattern string slice"))?
;
218 let p
= pxar
::MatchPattern
::from_line(l
.as_bytes())?
219 .ok_or_else(|| format_err
!("Invalid match pattern in arguments"))?
;
220 pattern_list
.push(p
);
223 let catalog
= None
::<&mut pxar
::catalog
::DummyCatalogWriter
>;
224 pxar
::Encoder
::encode(
241 /// Mount the archive to the provided mountpoint via FUSE.
245 _rpcenv
: &mut dyn RpcEnvironment
,
246 ) -> Result
<Value
, Error
> {
247 let archive
= tools
::required_string_param(¶m
, "archive")?
;
248 let mountpoint
= tools
::required_string_param(¶m
, "mountpoint")?
;
249 let verbose
= param
["verbose"].as_bool().unwrap_or(false);
250 let no_mt
= param
["no-mt"].as_bool().unwrap_or(false);
252 let archive
= Path
::new(archive
);
253 let mountpoint
= Path
::new(mountpoint
);
254 let options
= OsStr
::new("ro,default_permissions");
255 let mut session
= pxar
::fuse
::Session
::new(&archive
, &options
, verbose
)
256 .map_err(|err
| format_err
!("pxar mount failed: {}", err
))?
;
257 // Mount the session and deamonize if verbose is not set
258 session
.mount(&mountpoint
, !verbose
)?
;
259 session
.run_loop(!no_mt
)?
;
265 const API_METHOD_CREATE_ARCHIVE
: ApiMethod
= ApiMethod
::new(
266 &ApiHandler
::Sync(&create_archive
),
268 "Create new .pxar archive.",
273 &StringSchema
::new("Archive name").schema()
278 &StringSchema
::new("Source directory.").schema()
283 &BooleanSchema
::new("Verbose output.")
290 &BooleanSchema
::new("Ignore extended file attributes.")
297 &BooleanSchema
::new("Ignore file capabilities.")
304 &BooleanSchema
::new("Ignore access control list entries.")
311 &BooleanSchema
::new("Include mounted sudirs.")
318 &BooleanSchema
::new("Ignore device nodes.")
325 &BooleanSchema
::new("Ignore fifos.")
332 &BooleanSchema
::new("Ignore sockets.")
340 "List of paths or pattern matching files to exclude.",
341 &StringSchema
::new("Path or pattern matching files to restore.").schema()
349 const API_METHOD_EXTRACT_ARCHIVE
: ApiMethod
= ApiMethod
::new(
350 &ApiHandler
::Sync(&extract_archive
),
352 "Extract an archive.",
357 &StringSchema
::new("Archive name.").schema()
363 "List of paths or pattern matching files to restore",
364 &StringSchema
::new("Path or pattern matching files to restore.").schema()
370 &StringSchema
::new("Target directory.").schema()
375 &BooleanSchema
::new("Verbose output.")
382 &BooleanSchema
::new("Ignore extended file attributes.")
389 &BooleanSchema
::new("Ignore file capabilities.")
396 &BooleanSchema
::new("Ignore access control list entries.")
401 "allow-existing-dirs",
403 &BooleanSchema
::new("Allows directories to already exist on restore.")
410 &StringSchema
::new("Match pattern for files to restore.").schema()
415 &BooleanSchema
::new("Ignore device nodes.")
422 &BooleanSchema
::new("Ignore fifos.")
429 &BooleanSchema
::new("Ignore sockets.")
438 const API_METHOD_MOUNT_ARCHIVE
: ApiMethod
= ApiMethod
::new(
439 &ApiHandler
::Sync(&mount_archive
),
441 "Mount the archive as filesystem via FUSE.",
446 &StringSchema
::new("Archive name.").schema()
451 &StringSchema
::new("Mountpoint for the filesystem root.").schema()
456 &BooleanSchema
::new("Verbose output, keeps process running in foreground (for debugging).")
463 &BooleanSchema
::new("Run in single threaded mode (for debugging).")
472 const API_METHOD_DUMP_ARCHIVE
: ApiMethod
= ApiMethod
::new(
473 &ApiHandler
::Sync(&dump_archive
),
475 "List the contents of an archive.",
477 ( "archive", false, &StringSchema
::new("Archive name.").schema()),
478 ( "verbose", true, &BooleanSchema
::new("Verbose output.")
488 let cmd_def
= CliCommandMap
::new()
489 .insert("create", CliCommand
::new(&API_METHOD_CREATE_ARCHIVE
)
490 .arg_param(&["archive", "source", "exclude"])
491 .completion_cb("archive", tools
::complete_file_name
)
492 .completion_cb("source", tools
::complete_file_name
)
495 .insert("extract", CliCommand
::new(&API_METHOD_EXTRACT_ARCHIVE
)
496 .arg_param(&["archive", "pattern"])
497 .completion_cb("archive", tools
::complete_file_name
)
498 .completion_cb("target", tools
::complete_file_name
)
499 .completion_cb("files-from", tools
::complete_file_name
)
502 .insert("mount", CliCommand
::new(&API_METHOD_MOUNT_ARCHIVE
)
503 .arg_param(&["archive", "mountpoint"])
504 .completion_cb("archive", tools
::complete_file_name
)
505 .completion_cb("mountpoint", tools
::complete_file_name
)
508 .insert("list", CliCommand
::new(&API_METHOD_DUMP_ARCHIVE
)
509 .arg_param(&["archive"])
510 .completion_cb("archive", tools
::complete_file_name
)
514 run_cli_command(cmd_def
.into());