]>
Commit | Line | Data |
---|---|---|
c60d34bd DM |
1 | extern crate proxmox_backup; |
2 | ||
3 | use failure::*; | |
4 | ||
ce7ba139 | 5 | use proxmox_backup::tools; |
4de0e142 | 6 | use proxmox_backup::cli::*; |
ef2f2efb | 7 | use proxmox_backup::api_schema::*; |
dc9a007b | 8 | use proxmox_backup::api_schema::router::*; |
c60d34bd DM |
9 | |
10 | use serde_json::{Value}; | |
11 | ||
37940aa1 | 12 | use std::io::Write; |
1ef46b81 | 13 | use std::path::{Path, PathBuf}; |
af309d4d | 14 | use std::fs::OpenOptions; |
f71e8cc9 | 15 | use std::ffi::OsStr; |
a0ec687c | 16 | use std::sync::Arc; |
af309d4d | 17 | use std::os::unix::fs::OpenOptionsExt; |
129dda47 | 18 | use std::os::unix::io::AsRawFd; |
2eeaacb9 | 19 | use std::collections::HashSet; |
c60d34bd | 20 | |
3dbfe5b1 | 21 | use proxmox_backup::pxar; |
e86c4924 | 22 | |
6b9a0710 DM |
23 | fn 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 |
39 | fn 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(¶m, "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 |
64 | fn 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 |
86 | fn 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(¶m, "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 |
161 | fn 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(¶m, "archive")?; |
168 | let source = tools::required_string_param(¶m, "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. |
221 | fn mount_archive( | |
222 | param: Value, | |
223 | _info: &ApiMethod, | |
224 | _rpcenv: &mut dyn RpcEnvironment, | |
225 | ) -> Result<Value, Error> { | |
226 | let archive = tools::required_string_param(¶m, "archive")?; | |
227 | let mountpoint = tools::required_string_param(¶m, "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 |
241 | fn 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 | } |