]> git.proxmox.com Git - proxmox-backup.git/blame - src/bin/pxar.rs
pxar: add command 'mount' to cli of pxar
[proxmox-backup.git] / src / bin / pxar.rs
CommitLineData
c60d34bd
DM
1extern crate proxmox_backup;
2
3use failure::*;
4
ce7ba139 5use proxmox_backup::tools;
4de0e142 6use proxmox_backup::cli::*;
ef2f2efb 7use proxmox_backup::api_schema::*;
dc9a007b 8use proxmox_backup::api_schema::router::*;
c60d34bd
DM
9
10use serde_json::{Value};
11
37940aa1 12use std::io::Write;
1ef46b81 13use std::path::{Path, PathBuf};
af309d4d 14use std::fs::OpenOptions;
f71e8cc9 15use std::ffi::OsStr;
a0ec687c 16use std::sync::Arc;
af309d4d 17use std::os::unix::fs::OpenOptionsExt;
129dda47 18use std::os::unix::io::AsRawFd;
2eeaacb9 19use std::collections::HashSet;
c60d34bd 20
3dbfe5b1 21use proxmox_backup::pxar;
e86c4924 22
6b9a0710
DM
23fn dump_archive_from_reader<R: std::io::Read>(
24 reader: &mut R,
25 feature_flags: u64,
26 verbose: bool,
27) -> Result<(), Error> {
c6c9e093
DM
28 let mut decoder = pxar::SequentialDecoder::new(reader, feature_flags, |_| Ok(()));
29
30 let stdout = std::io::stdout();
31 let mut out = stdout.lock();
32
33 let mut path = PathBuf::new();
6b9a0710 34 decoder.dump_entry(&mut path, verbose, &mut out)?;
c6c9e093
DM
35
36 Ok(())
37}
38
6049b71f
DM
39fn dump_archive(
40 param: Value,
41 _info: &ApiMethod,
dd5495d6 42 _rpcenv: &mut dyn RpcEnvironment,
6049b71f 43) -> Result<Value, Error> {
c60d34bd 44
6049b71f 45 let archive = tools::required_string_param(&param, "archive")?;
6b9a0710 46 let verbose = param["verbose"].as_bool().unwrap_or(false);
c60d34bd 47
47651f95 48 let feature_flags = pxar::flags::DEFAULT;
c60d34bd 49
c6c9e093
DM
50 if archive == "-" {
51 let stdin = std::io::stdin();
52 let mut reader = stdin.lock();
6b9a0710 53 dump_archive_from_reader(&mut reader, feature_flags, verbose)?;
c6c9e093 54 } else {
aad2ee49 55 if verbose { println!("PXAR dump: {}", archive); }
c6c9e093
DM
56 let file = std::fs::File::open(archive)?;
57 let mut reader = std::io::BufReader::new(file);
6b9a0710 58 dump_archive_from_reader(&mut reader, feature_flags, verbose)?;
9eae781a 59 }
c60d34bd
DM
60
61 Ok(Value::Null)
62}
63
129dda47
CE
64fn extract_archive_from_reader<R: std::io::Read>(
65 reader: &mut R,
66 target: &str,
67 feature_flags: u64,
6a879109 68 allow_existing_dirs: bool,
129dda47 69 verbose: bool,
4d142ea7 70 pattern: Option<Vec<pxar::MatchPattern>>
129dda47 71) -> Result<(), Error> {
9eae781a
DM
72 let mut decoder = pxar::SequentialDecoder::new(reader, feature_flags, |path| {
73 if verbose {
74 println!("{:?}", path);
75 }
76 Ok(())
77 });
6a879109 78 decoder.set_allow_existing_dirs(allow_existing_dirs);
9eae781a 79
129dda47
CE
80 let pattern = pattern.unwrap_or(Vec::new());
81 decoder.restore(Path::new(target), &pattern)?;
9eae781a
DM
82
83 Ok(())
84}
85
1ef46b81
DM
86fn extract_archive(
87 param: Value,
88 _info: &ApiMethod,
dd5495d6 89 _rpcenv: &mut dyn RpcEnvironment,
1ef46b81
DM
90) -> Result<Value, Error> {
91
92 let archive = tools::required_string_param(&param, "archive")?;
bdf0d82c 93 let target = param["target"].as_str().unwrap_or(".");
1ef46b81 94 let verbose = param["verbose"].as_bool().unwrap_or(false);
0d9bab05
CE
95 let no_xattrs = param["no-xattrs"].as_bool().unwrap_or(false);
96 let no_fcaps = param["no-fcaps"].as_bool().unwrap_or(false);
9b384433 97 let no_acls = param["no-acls"].as_bool().unwrap_or(false);
81a9905e
CE
98 let no_device_nodes = param["no-device-nodes"].as_bool().unwrap_or(false);
99 let no_fifos = param["no-fifos"].as_bool().unwrap_or(false);
100 let no_sockets = param["no-sockets"].as_bool().unwrap_or(false);
6a879109 101 let allow_existing_dirs = param["allow-existing-dirs"].as_bool().unwrap_or(false);
129dda47 102 let files_from = param["files-from"].as_str();
a0ec687c
CE
103 let empty = Vec::new();
104 let arg_pattern = param["pattern"].as_array().unwrap_or(&empty);
1ef46b81 105
47651f95 106 let mut feature_flags = pxar::flags::DEFAULT;
b344461b 107 if no_xattrs {
47651f95 108 feature_flags ^= pxar::flags::WITH_XATTRS;
b344461b
CE
109 }
110 if no_fcaps {
47651f95 111 feature_flags ^= pxar::flags::WITH_FCAPS;
b344461b 112 }
9b384433 113 if no_acls {
47651f95 114 feature_flags ^= pxar::flags::WITH_ACL;
9b384433 115 }
81a9905e 116 if no_device_nodes {
47651f95 117 feature_flags ^= pxar::flags::WITH_DEVICE_NODES;
81a9905e
CE
118 }
119 if no_fifos {
47651f95 120 feature_flags ^= pxar::flags::WITH_FIFOS;
81a9905e
CE
121 }
122 if no_sockets {
47651f95 123 feature_flags ^= pxar::flags::WITH_SOCKETS;
81a9905e 124 }
1ef46b81 125
a0ec687c
CE
126 let mut pattern_list = Vec::new();
127 if let Some(filename) = files_from {
128 let dir = nix::dir::Dir::open("./", nix::fcntl::OFlag::O_RDONLY, nix::sys::stat::Mode::empty())?;
4d142ea7 129 if let Some((mut pattern, _, _)) = pxar::MatchPattern::from_file(dir.as_raw_fd(), filename)? {
a0ec687c
CE
130 pattern_list.append(&mut pattern);
131 }
132 }
129dda47 133
a0ec687c
CE
134 for s in arg_pattern {
135 let l = s.as_str().ok_or_else(|| format_err!("Invalid pattern string slice"))?;
4d142ea7 136 let p = pxar::MatchPattern::from_line(l.as_bytes())?
a0ec687c
CE
137 .ok_or_else(|| format_err!("Invalid match pattern in arguments"))?;
138 pattern_list.push(p);
139 }
140
141 let pattern = if pattern_list.len() > 0 {
142 Some(pattern_list)
143 } else {
144 None
129dda47
CE
145 };
146
9eae781a
DM
147 if archive == "-" {
148 let stdin = std::io::stdin();
149 let mut reader = stdin.lock();
6a879109 150 extract_archive_from_reader(&mut reader, target, feature_flags, allow_existing_dirs, verbose, pattern)?;
9eae781a 151 } else {
40a13369 152 if verbose { println!("PXAR extract: {}", archive); }
9eae781a
DM
153 let file = std::fs::File::open(archive)?;
154 let mut reader = std::io::BufReader::new(file);
6a879109 155 extract_archive_from_reader(&mut reader, target, feature_flags, allow_existing_dirs, verbose, pattern)?;
9eae781a 156 }
1ef46b81
DM
157
158 Ok(Value::Null)
159}
160
6049b71f
DM
161fn create_archive(
162 param: Value,
163 _info: &ApiMethod,
dd5495d6 164 _rpcenv: &mut dyn RpcEnvironment,
6049b71f 165) -> Result<Value, Error> {
c60d34bd 166
6049b71f
DM
167 let archive = tools::required_string_param(&param, "archive")?;
168 let source = tools::required_string_param(&param, "source")?;
2689810c 169 let verbose = param["verbose"].as_bool().unwrap_or(false);
e3c30c50 170 let all_file_systems = param["all-file-systems"].as_bool().unwrap_or(false);
0d9bab05
CE
171 let no_xattrs = param["no-xattrs"].as_bool().unwrap_or(false);
172 let no_fcaps = param["no-fcaps"].as_bool().unwrap_or(false);
9b384433 173 let no_acls = param["no-acls"].as_bool().unwrap_or(false);
81a9905e
CE
174 let no_device_nodes = param["no-device-nodes"].as_bool().unwrap_or(false);
175 let no_fifos = param["no-fifos"].as_bool().unwrap_or(false);
176 let no_sockets = param["no-sockets"].as_bool().unwrap_or(false);
02c7d8e5 177
2eeaacb9
DM
178 let devices = if all_file_systems { None } else { Some(HashSet::new()) };
179
1ef46b81 180 let source = PathBuf::from(source);
02c7d8e5
DM
181
182 let mut dir = nix::dir::Dir::open(
183 &source, nix::fcntl::OFlag::O_NOFOLLOW, nix::sys::stat::Mode::empty())?;
184
af309d4d 185 let file = OpenOptions::new()
02c7d8e5
DM
186 .create_new(true)
187 .write(true)
af309d4d 188 .mode(0o640)
02c7d8e5
DM
189 .open(archive)?;
190
191 let mut writer = std::io::BufWriter::with_capacity(1024*1024, file);
47651f95 192 let mut feature_flags = pxar::flags::DEFAULT;
b344461b 193 if no_xattrs {
47651f95 194 feature_flags ^= pxar::flags::WITH_XATTRS;
b344461b
CE
195 }
196 if no_fcaps {
47651f95 197 feature_flags ^= pxar::flags::WITH_FCAPS;
b344461b 198 }
9b384433 199 if no_acls {
47651f95 200 feature_flags ^= pxar::flags::WITH_ACL;
9b384433 201 }
81a9905e 202 if no_device_nodes {
47651f95 203 feature_flags ^= pxar::flags::WITH_DEVICE_NODES;
81a9905e
CE
204 }
205 if no_fifos {
47651f95 206 feature_flags ^= pxar::flags::WITH_FIFOS;
81a9905e
CE
207 }
208 if no_sockets {
47651f95 209 feature_flags ^= pxar::flags::WITH_SOCKETS;
81a9905e 210 }
b344461b 211
2761d6a4
DM
212 let catalog = None::<&mut pxar::catalog::SimpleCatalog>;
213 pxar::Encoder::encode(source, &mut dir, &mut writer, catalog, devices, verbose, false, feature_flags)?;
c60d34bd 214
02c7d8e5 215 writer.flush()?;
c60d34bd
DM
216
217 Ok(Value::Null)
218}
219
f71e8cc9
CE
220/// Mount the archive to the provided mountpoint via FUSE.
221fn mount_archive(
222 param: Value,
223 _info: &ApiMethod,
224 _rpcenv: &mut dyn RpcEnvironment,
225) -> Result<Value, Error> {
226 let archive = tools::required_string_param(&param, "archive")?;
227 let mountpoint = tools::required_string_param(&param, "mountpoint")?;
228 let verbose = param["verbose"].as_bool().unwrap_or(false);
229
230 let archive = Path::new(archive);
231 let mountpoint = Path::new(mountpoint);
232 let options = OsStr::new("ro,default_permissions");
233 let mut session = pxar::fuse::Session::new(&archive, &options, verbose)
234 .map_err(|err| format_err!("pxar mount failed: {}", err))?;
235 session.mount(&mountpoint)?;
236 session.run_loop()?;
237
238 Ok(Value::Null)
239}
240
c60d34bd
DM
241fn main() {
242
243 let cmd_def = CliCommandMap::new()
244 .insert("create", CliCommand::new(
245 ApiMethod::new(
02c7d8e5 246 create_archive,
8968258b 247 ObjectSchema::new("Create new .pxar archive.")
c60d34bd
DM
248 .required("archive", StringSchema::new("Archive name"))
249 .required("source", StringSchema::new("Source directory."))
2689810c 250 .optional("verbose", BooleanSchema::new("Verbose output.").default(false))
0d9bab05
CE
251 .optional("no-xattrs", BooleanSchema::new("Ignore extended file attributes.").default(false))
252 .optional("no-fcaps", BooleanSchema::new("Ignore file capabilities.").default(false))
9b384433 253 .optional("no-acls", BooleanSchema::new("Ignore access control list entries.").default(false))
e3c30c50 254 .optional("all-file-systems", BooleanSchema::new("Include mounted sudirs.").default(false))
81a9905e
CE
255 .optional("no-device-nodes", BooleanSchema::new("Ignore device nodes.").default(false))
256 .optional("no-fifos", BooleanSchema::new("Ignore fifos.").default(false))
257 .optional("no-sockets", BooleanSchema::new("Ignore sockets.").default(false))
a0ec687c 258 ))
c60d34bd 259 .arg_param(vec!["archive", "source"])
ce7ba139
DM
260 .completion_cb("archive", tools::complete_file_name)
261 .completion_cb("source", tools::complete_file_name)
a0ec687c 262 .into()
c60d34bd 263 )
1ef46b81
DM
264 .insert("extract", CliCommand::new(
265 ApiMethod::new(
266 extract_archive,
267 ObjectSchema::new("Extract an archive.")
268 .required("archive", StringSchema::new("Archive name."))
a0ec687c
CE
269 .optional("pattern", Arc::new(
270 ArraySchema::new(
271 "List of paths or pattern matching files to restore",
272 Arc::new(StringSchema::new("Path or pattern matching files to restore.").into())
273 ).into()
274 ))
bdf0d82c 275 .optional("target", StringSchema::new("Target directory."))
1ef46b81 276 .optional("verbose", BooleanSchema::new("Verbose output.").default(false))
0d9bab05
CE
277 .optional("no-xattrs", BooleanSchema::new("Ignore extended file attributes.").default(false))
278 .optional("no-fcaps", BooleanSchema::new("Ignore file capabilities.").default(false))
9b384433 279 .optional("no-acls", BooleanSchema::new("Ignore access control list entries.").default(false))
6a879109 280 .optional("allow-existing-dirs", BooleanSchema::new("Allows directories to already exist on restore.").default(false))
129dda47 281 .optional("files-from", StringSchema::new("Match pattern for files to restore."))
81a9905e
CE
282 .optional("no-device-nodes", BooleanSchema::new("Ignore device nodes.").default(false))
283 .optional("no-fifos", BooleanSchema::new("Ignore fifos.").default(false))
284 .optional("no-sockets", BooleanSchema::new("Ignore sockets.").default(false))
a0ec687c
CE
285 ))
286 .arg_param(vec!["archive", "pattern"])
1ef46b81
DM
287 .completion_cb("archive", tools::complete_file_name)
288 .completion_cb("target", tools::complete_file_name)
129dda47 289 .completion_cb("files-from", tools::complete_file_name)
1ef46b81
DM
290 .into()
291 )
f71e8cc9
CE
292 .insert("mount", CliCommand::new(
293 ApiMethod::new(
294 mount_archive,
295 ObjectSchema::new("Mount the archive as filesystem via FUSE.")
296 .required("archive", StringSchema::new("Archive name."))
297 .required("mountpoint", StringSchema::new("Mountpoint for the filesystem root."))
298 .optional("verbose", BooleanSchema::new("Verbose output, keeps process running in foreground.").default(false))
299 ))
300 .arg_param(vec!["archive", "mountpoint"])
301 .completion_cb("archive", tools::complete_file_name)
302 .completion_cb("mountpoint", tools::complete_file_name)
303 .into()
304 )
e86c4924 305 .insert("list", CliCommand::new(
c60d34bd
DM
306 ApiMethod::new(
307 dump_archive,
6b9a0710 308 ObjectSchema::new("List the contents of an archive.")
c60d34bd 309 .required("archive", StringSchema::new("Archive name."))
6b9a0710 310 .optional("verbose", BooleanSchema::new("Verbose output.").default(false))
c60d34bd
DM
311 ))
312 .arg_param(vec!["archive"])
ce7ba139 313 .completion_cb("archive", tools::complete_file_name)
c60d34bd
DM
314 .into()
315 );
316
698d9d44 317 run_cli_command(cmd_def.into());
c60d34bd 318}