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