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