]>
Commit | Line | Data |
---|---|---|
6cd4f635 WB |
1 | //! Proxmox backup archive format handling. |
2 | //! | |
3 | //! This implements a reader and writer for the proxmox archive format (.pxar). | |
4 | ||
609e3a63 WB |
5 | #![deny(unsafe_op_in_unsafe_fn)] |
6 | ||
6cd4f635 WB |
7 | use std::ffi::OsStr; |
8 | use std::mem; | |
6cd4f635 WB |
9 | use std::path::{Path, PathBuf}; |
10 | ||
11 | #[macro_use] | |
12 | mod macros; | |
13 | ||
14 | pub mod format; | |
15 | ||
16 | pub(crate) mod util; | |
17 | ||
18 | mod poll_fn; | |
19 | ||
20 | pub mod accessor; | |
fbddffdc | 21 | pub mod binary_tree_array; |
6cd4f635 | 22 | pub mod decoder; |
70acf637 WB |
23 | pub mod encoder; |
24 | ||
a13a2f74 | 25 | #[doc(inline)] |
2ea8aff2 | 26 | pub use format::{mode, Stat}; |
a13a2f74 | 27 | |
6cd4f635 WB |
28 | /// File metadata found in pxar archives. |
29 | /// | |
30 | /// This includes the usual data you'd get from `stat()` as well as ACLs, extended attributes, file | |
31 | /// capabilities and more. | |
6ec9e999 LW |
32 | #[derive(Clone, Debug, Default, PartialEq)] |
33 | #[cfg_attr(feature = "test-harness", derive(Eq))] | |
6cd4f635 WB |
34 | pub struct Metadata { |
35 | /// Data typically found in a `stat()` call. | |
70acf637 | 36 | pub stat: Stat, |
6cd4f635 WB |
37 | |
38 | /// Extended attributes. | |
39 | pub xattrs: Vec<format::XAttr>, | |
40 | ||
41 | /// ACLs. | |
42 | pub acl: Acl, | |
43 | ||
44 | /// File capabilities. | |
45 | pub fcaps: Option<format::FCaps>, | |
46 | ||
47 | /// Quota project id. | |
48 | pub quota_project_id: Option<format::QuotaProjectId>, | |
49 | } | |
50 | ||
70acf637 WB |
51 | impl From<Stat> for Metadata { |
52 | fn from(stat: Stat) -> Self { | |
53 | Self { | |
54 | stat, | |
55 | ..Default::default() | |
56 | } | |
57 | } | |
58 | } | |
59 | ||
96a067c5 WB |
60 | impl From<&std::fs::Metadata> for Metadata { |
61 | fn from(meta: &std::fs::Metadata) -> Metadata { | |
1b25fc08 WB |
62 | // NOTE: fill the remaining metadata via feature flags? |
63 | Self::from(Stat::from(meta)) | |
70acf637 WB |
64 | } |
65 | } | |
66 | ||
96a067c5 WB |
67 | impl From<std::fs::Metadata> for Metadata { |
68 | fn from(meta: std::fs::Metadata) -> Metadata { | |
69 | Self::from(&meta) | |
70 | } | |
71 | } | |
72 | ||
70acf637 WB |
73 | /// Convenience helpers. |
74 | impl Metadata { | |
21d66e10 WB |
75 | /// Get the file type (`mode & mode::IFMT`). |
76 | #[inline] | |
77 | pub fn file_type(&self) -> u64 { | |
78 | self.stat.file_type() | |
79 | } | |
80 | ||
81 | /// Get the file mode bits (`mode & !mode::IFMT`). | |
82 | #[inline] | |
83 | pub fn file_mode(&self) -> u64 { | |
84 | self.stat.file_mode() | |
85 | } | |
86 | ||
70acf637 WB |
87 | /// Check whether this is a directory. |
88 | #[inline] | |
89 | pub fn is_dir(&self) -> bool { | |
90 | self.stat.is_dir() | |
91 | } | |
92 | ||
93 | /// Check whether this is a symbolic link. | |
94 | #[inline] | |
95 | pub fn is_symlink(&self) -> bool { | |
96 | self.stat.is_symlink() | |
97 | } | |
98 | ||
99 | /// Check whether this is a device node. | |
100 | #[inline] | |
101 | pub fn is_device(&self) -> bool { | |
102 | self.stat.is_device() | |
103 | } | |
104 | ||
105 | /// Check whether this is a regular file. | |
106 | #[inline] | |
107 | pub fn is_regular_file(&self) -> bool { | |
108 | self.stat.is_regular_file() | |
109 | } | |
ae2210fe | 110 | |
2287d8b2 WB |
111 | /// Check whether this is a named pipe (FIFO). |
112 | #[inline] | |
113 | pub fn is_fifo(&self) -> bool { | |
114 | self.stat.is_fifo() | |
115 | } | |
116 | ||
117 | /// Check whether this is a named socket. | |
118 | #[inline] | |
119 | pub fn is_socket(&self) -> bool { | |
120 | self.stat.is_socket() | |
121 | } | |
122 | ||
644e844d WB |
123 | /// Get the mtime as duration since the epoch. an `Ok` value is a positive duration, an `Err` |
124 | /// value is a negative duration. | |
125 | pub fn mtime_as_duration(&self) -> format::SignedDuration { | |
ae2210fe WB |
126 | self.stat.mtime_as_duration() |
127 | } | |
0f109bf7 WB |
128 | |
129 | /// A more convenient way to create metadata for a regular file. | |
130 | pub fn file_builder(mode: u64) -> MetadataBuilder { | |
131 | Self::builder(mode::IFREG | (mode & !mode::IFMT)) | |
132 | } | |
133 | ||
134 | /// A more convenient way to create metadata for a directory file. | |
135 | pub fn dir_builder(mode: u64) -> MetadataBuilder { | |
136 | Self::builder(mode::IFDIR | (mode & !mode::IFMT)) | |
137 | } | |
138 | ||
139 | /// A more convenient way to create generic metadata. | |
2ea8aff2 | 140 | pub const fn builder(mode: u64) -> MetadataBuilder { |
0f109bf7 WB |
141 | MetadataBuilder::new(mode) |
142 | } | |
2ea8aff2 WB |
143 | |
144 | #[cfg(all(test, target_os = "linux"))] | |
145 | /// A more convenient way to create metadata starting from `libc::stat`. | |
146 | pub fn builder_from_stat(stat: &libc::stat) -> MetadataBuilder { | |
147 | MetadataBuilder::new(0).fill_from_stat(stat) | |
148 | } | |
0f109bf7 WB |
149 | } |
150 | ||
151 | impl From<MetadataBuilder> for Metadata { | |
152 | fn from(builder: MetadataBuilder) -> Self { | |
153 | builder.build() | |
154 | } | |
155 | } | |
156 | ||
e5a2495e | 157 | /// A builder for the file [`Metadata`] stored in pxar archives. |
0f109bf7 WB |
158 | pub struct MetadataBuilder { |
159 | inner: Metadata, | |
160 | } | |
161 | ||
162 | impl MetadataBuilder { | |
e5a2495e | 163 | /// Create a new [`MetadataBuilder`] given an initial type/mode bitset. |
0f109bf7 WB |
164 | pub const fn new(type_and_mode: u64) -> Self { |
165 | Self { | |
166 | inner: Metadata { | |
167 | stat: Stat { | |
168 | mode: type_and_mode, | |
169 | flags: 0, | |
170 | uid: 0, | |
171 | gid: 0, | |
644e844d | 172 | mtime: format::StatxTimestamp::zero(), |
0f109bf7 WB |
173 | }, |
174 | xattrs: Vec::new(), | |
175 | acl: Acl { | |
176 | users: Vec::new(), | |
177 | groups: Vec::new(), | |
178 | group_obj: None, | |
179 | default: None, | |
180 | default_users: Vec::new(), | |
181 | default_groups: Vec::new(), | |
182 | }, | |
183 | fcaps: None, | |
184 | quota_project_id: None, | |
185 | }, | |
186 | } | |
187 | } | |
188 | ||
e5a2495e | 189 | /// Build the [`Metadata`]. |
0f109bf7 WB |
190 | pub fn build(self) -> Metadata { |
191 | self.inner | |
192 | } | |
193 | ||
2ea8aff2 WB |
194 | /// Set the file mode (complete `stat.st_mode` with file type and permission bits). |
195 | pub const fn st_mode(mut self, mode: u64) -> Self { | |
196 | self.inner.stat.mode = mode; | |
197 | self | |
198 | } | |
199 | ||
0f109bf7 WB |
200 | /// Set the file type (`mode & mode::IFMT`). |
201 | pub const fn file_type(mut self, mode: u64) -> Self { | |
202 | self.inner.stat.mode = (self.inner.stat.mode & !mode::IFMT) | (mode & mode::IFMT); | |
203 | self | |
204 | } | |
205 | ||
206 | /// Set the file mode bits (`mode & !mode::IFMT`). | |
207 | pub const fn file_mode(mut self, mode: u64) -> Self { | |
208 | self.inner.stat.mode = (self.inner.stat.mode & mode::IFMT) | (mode & !mode::IFMT); | |
209 | self | |
210 | } | |
211 | ||
644e844d WB |
212 | /// Set the modification time from a statx timespec value. |
213 | pub fn mtime_full(mut self, mtime: format::StatxTimestamp) -> Self { | |
214 | self.inner.stat.mtime = mtime; | |
215 | self | |
0f109bf7 WB |
216 | } |
217 | ||
218 | /// Set the modification time from a duration since the epoch (`SystemTime::UNIX_EPOCH`). | |
644e844d WB |
219 | pub fn mtime_unix(self, mtime: std::time::Duration) -> Self { |
220 | self.mtime_full(format::StatxTimestamp::from_duration_since_epoch(mtime)) | |
221 | } | |
222 | ||
223 | /// Set the modification time from a system time. | |
224 | pub fn mtime(self, mtime: std::time::SystemTime) -> Self { | |
225 | self.mtime_full(mtime.into()) | |
0f109bf7 WB |
226 | } |
227 | ||
228 | /// Set the ownership information. | |
229 | pub const fn owner(self, uid: u32, gid: u32) -> Self { | |
230 | self.uid(uid).gid(gid) | |
231 | } | |
232 | ||
233 | /// Set the owning user id. | |
234 | pub const fn uid(mut self, uid: u32) -> Self { | |
235 | self.inner.stat.uid = uid; | |
236 | self | |
237 | } | |
238 | ||
239 | /// Set the owning user id. | |
240 | pub const fn gid(mut self, gid: u32) -> Self { | |
241 | self.inner.stat.gid = gid; | |
242 | self | |
243 | } | |
244 | ||
245 | /// Add an extended attribute. | |
246 | pub fn xattr<N: AsRef<[u8]>, V: AsRef<[u8]>>(mut self, name: N, value: V) -> Self { | |
247 | self.inner.xattrs.push(format::XAttr::new(name, value)); | |
248 | self | |
249 | } | |
250 | ||
251 | /// Add a user ACL entry. | |
252 | pub fn acl_user(mut self, entry: format::acl::User) -> Self { | |
253 | self.inner.acl.users.push(entry); | |
254 | self | |
255 | } | |
256 | ||
257 | /// Add a group ACL entry. | |
258 | pub fn acl_group(mut self, entry: format::acl::Group) -> Self { | |
259 | self.inner.acl.groups.push(entry); | |
260 | self | |
261 | } | |
262 | ||
263 | /// Add a user default-ACL entry. | |
264 | pub fn default_acl_user(mut self, entry: format::acl::User) -> Self { | |
265 | self.inner.acl.default_users.push(entry); | |
266 | self | |
267 | } | |
268 | ||
269 | /// Add a group default-ACL entry. | |
270 | pub fn default_acl_group(mut self, entry: format::acl::Group) -> Self { | |
271 | self.inner.acl.default_groups.push(entry); | |
272 | self | |
273 | } | |
274 | ||
275 | /// Set the default ACL entry for a directory. | |
276 | pub const fn default_acl(mut self, entry: Option<format::acl::Default>) -> Self { | |
277 | self.inner.acl.default = entry; | |
278 | self | |
279 | } | |
280 | ||
281 | /// Set the quota project id. | |
282 | pub fn quota_project_id(mut self, id: Option<u64>) -> Self { | |
283 | self.inner.quota_project_id = id.map(|projid| format::QuotaProjectId { projid }); | |
284 | self | |
285 | } | |
286 | ||
287 | /// Set the raw file capability data. | |
288 | pub fn fcaps(mut self, fcaps: Option<Vec<u8>>) -> Self { | |
289 | self.inner.fcaps = fcaps.map(|data| format::FCaps { data }); | |
290 | self | |
291 | } | |
2ea8aff2 WB |
292 | |
293 | #[cfg(all(test, target_os = "linux"))] | |
294 | /// Fill the metadata with information from `struct stat`. | |
295 | pub fn fill_from_stat(self, stat: &libc::stat) -> Self { | |
296 | self.st_mode(u64::from(stat.st_mode)) | |
297 | .owner(stat.st_uid, stat.st_gid) | |
298 | .mtime_full(format::StatxTimestamp::from_stat( | |
299 | stat.st_mtime, | |
300 | stat.st_mtime_nsec as u32, | |
301 | )) | |
302 | } | |
70acf637 WB |
303 | } |
304 | ||
6cd4f635 WB |
305 | /// ACL entries of a pxar archive. |
306 | /// | |
307 | /// This contains all the various ACL entry types supported by the pxar archive format. | |
6ec9e999 LW |
308 | #[derive(Clone, Debug, Default, PartialEq)] |
309 | #[cfg_attr(feature = "test-harness", derive(Eq))] | |
6cd4f635 WB |
310 | pub struct Acl { |
311 | /// User ACL list. | |
312 | pub users: Vec<format::acl::User>, | |
313 | ||
314 | /// Group ACL list. | |
315 | pub groups: Vec<format::acl::Group>, | |
316 | ||
317 | /// Group object ACL entry. | |
318 | pub group_obj: Option<format::acl::GroupObject>, | |
319 | ||
320 | /// Default permissions. | |
321 | pub default: Option<format::acl::Default>, | |
322 | ||
323 | /// Default user permissions. | |
324 | pub default_users: Vec<format::acl::User>, | |
325 | ||
326 | /// Default group permissions. | |
327 | pub default_groups: Vec<format::acl::Group>, | |
328 | } | |
329 | ||
a6928cfc | 330 | impl Acl { |
e5a2495e | 331 | /// Shortcut to check if all fields of this [`Acl`] entry are empty. |
a6928cfc WB |
332 | pub fn is_empty(&self) -> bool { |
333 | self.users.is_empty() | |
334 | && self.groups.is_empty() | |
335 | && self.group_obj.is_none() | |
336 | && self.default.is_none() | |
337 | && self.default_users.is_empty() | |
338 | && self.default_groups.is_empty() | |
339 | } | |
340 | } | |
341 | ||
6cd4f635 WB |
342 | /// Pxar archive entry kind. |
343 | /// | |
344 | /// Identifies whether the entry is a file, symlink, directory, etc. | |
345 | #[derive(Clone, Debug)] | |
346 | pub enum EntryKind { | |
347 | /// Symbolic links. | |
348 | Symlink(format::Symlink), | |
349 | ||
350 | /// Hard links, relative to the root of the current archive. | |
351 | Hardlink(format::Hardlink), | |
352 | ||
353 | /// Device node. | |
354 | Device(format::Device), | |
355 | ||
2287d8b2 WB |
356 | /// Named unix socket. |
357 | Socket, | |
358 | ||
359 | /// Named pipe. | |
360 | Fifo, | |
361 | ||
6cd4f635 | 362 | /// Regular file. |
e5a2495e WB |
363 | File { |
364 | /// The file size in bytes. | |
365 | size: u64, | |
366 | ||
367 | /// The file's byte offset inside the archive, if available. | |
368 | offset: Option<u64>, | |
369 | }, | |
6cd4f635 WB |
370 | |
371 | /// Directory entry. When iterating through an archive, the contents follow next. | |
372 | Directory, | |
373 | ||
374 | /// End of a directory. This is for internal use to remember the goodbye-table of a directory | |
375 | /// entry. Will not occur during normal iteration. | |
27631c16 | 376 | GoodbyeTable, |
6cd4f635 WB |
377 | } |
378 | ||
379 | /// A pxar archive entry. This contains the current path, file metadata and entry type specific | |
380 | /// information. | |
381 | #[derive(Clone, Debug)] | |
382 | pub struct Entry { | |
383 | path: PathBuf, | |
384 | metadata: Metadata, | |
385 | kind: EntryKind, | |
6cd4f635 WB |
386 | } |
387 | ||
388 | /// General accessors. | |
389 | impl Entry { | |
390 | /// Clear everything except for the path. | |
391 | fn clear_data(&mut self) { | |
392 | self.metadata = Metadata::default(); | |
27631c16 | 393 | self.kind = EntryKind::GoodbyeTable; |
6cd4f635 WB |
394 | } |
395 | ||
396 | fn internal_default() -> Self { | |
397 | Self { | |
398 | path: PathBuf::default(), | |
399 | metadata: Metadata::default(), | |
27631c16 | 400 | kind: EntryKind::GoodbyeTable, |
6cd4f635 WB |
401 | } |
402 | } | |
403 | ||
404 | fn take(&mut self) -> Self { | |
405 | let this = mem::replace(self, Self::internal_default()); | |
406 | self.path = this.path.clone(); | |
407 | this | |
408 | } | |
409 | ||
6cd4f635 WB |
410 | /// Get the full path of this file within the current pxar directory structure. |
411 | #[inline] | |
412 | pub fn path(&self) -> &Path { | |
413 | &self.path | |
414 | } | |
415 | ||
416 | /// Convenience method to get just the file name portion of the current path. | |
417 | #[inline] | |
418 | pub fn file_name(&self) -> &OsStr { | |
1b25fc08 | 419 | self.path.file_name().unwrap_or_else(|| OsStr::new("")) |
6cd4f635 WB |
420 | } |
421 | ||
422 | /// Get the file metadata. | |
423 | #[inline] | |
424 | pub fn metadata(&self) -> &Metadata { | |
425 | &self.metadata | |
426 | } | |
427 | ||
27eaae48 WB |
428 | /// Take out the metadata. |
429 | #[inline] | |
430 | pub fn into_metadata(self) -> Metadata { | |
431 | self.metadata | |
432 | } | |
433 | ||
65a8a0d5 WB |
434 | /// Get the entry-type specific information. |
435 | pub fn kind(&self) -> &EntryKind { | |
436 | &self.kind | |
437 | } | |
438 | ||
6cd4f635 WB |
439 | /// Get the value of the symbolic link if it is one. |
440 | pub fn get_symlink(&self) -> Option<&OsStr> { | |
441 | match &self.kind { | |
54912ae6 | 442 | EntryKind::Symlink(link) => Some(link.as_ref()), |
6cd4f635 WB |
443 | _ => None, |
444 | } | |
445 | } | |
446 | ||
447 | /// Get the value of the hard link if it is one. | |
448 | pub fn get_hardlink(&self) -> Option<&OsStr> { | |
449 | match &self.kind { | |
54912ae6 | 450 | EntryKind::Hardlink(link) => Some(link.as_ref()), |
6cd4f635 WB |
451 | _ => None, |
452 | } | |
453 | } | |
454 | ||
455 | /// Get the value of the device node if it is one. | |
456 | pub fn get_device(&self) -> Option<format::Device> { | |
457 | match &self.kind { | |
458 | EntryKind::Device(dev) => Some(dev.clone()), | |
459 | _ => None, | |
460 | } | |
461 | } | |
462 | } | |
463 | ||
464 | /// Convenience helpers. | |
465 | impl Entry { | |
466 | /// Check whether this is a directory. | |
467 | pub fn is_dir(&self) -> bool { | |
8de202d4 | 468 | matches!(self.kind, EntryKind::Directory) |
6cd4f635 WB |
469 | } |
470 | ||
471 | /// Check whether this is a symbolic link. | |
472 | pub fn is_symlink(&self) -> bool { | |
8de202d4 | 473 | matches!(self.kind, EntryKind::Symlink(_)) |
6cd4f635 WB |
474 | } |
475 | ||
476 | /// Check whether this is a hard link. | |
477 | pub fn is_hardlink(&self) -> bool { | |
8de202d4 | 478 | matches!(self.kind, EntryKind::Hardlink(_)) |
6cd4f635 WB |
479 | } |
480 | ||
481 | /// Check whether this is a device node. | |
482 | pub fn is_device(&self) -> bool { | |
8de202d4 | 483 | matches!(self.kind, EntryKind::Device(_)) |
6cd4f635 WB |
484 | } |
485 | ||
486 | /// Check whether this is a regular file. | |
487 | pub fn is_regular_file(&self) -> bool { | |
8de202d4 | 488 | matches!(self.kind, EntryKind::File { .. }) |
6cd4f635 | 489 | } |
a95eabc0 WB |
490 | |
491 | /// Get the file size if this is a regular file, or `None`. | |
492 | pub fn file_size(&self) -> Option<u64> { | |
493 | match self.kind { | |
494 | EntryKind::File { size, .. } => Some(size), | |
495 | _ => None, | |
496 | } | |
497 | } | |
6cd4f635 | 498 | } |