]> git.proxmox.com Git - pxar.git/blob - src/lib.rs
add more code documentation
[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 /// A builder for the file [`Metadata`] stored in pxar archives.
156 pub struct MetadataBuilder {
157 inner: Metadata,
158 }
159
160 impl MetadataBuilder {
161 /// Create a new [`MetadataBuilder`] given an initial type/mode bitset.
162 pub const fn new(type_and_mode: u64) -> Self {
163 Self {
164 inner: Metadata {
165 stat: Stat {
166 mode: type_and_mode,
167 flags: 0,
168 uid: 0,
169 gid: 0,
170 mtime: format::StatxTimestamp::zero(),
171 },
172 xattrs: Vec::new(),
173 acl: Acl {
174 users: Vec::new(),
175 groups: Vec::new(),
176 group_obj: None,
177 default: None,
178 default_users: Vec::new(),
179 default_groups: Vec::new(),
180 },
181 fcaps: None,
182 quota_project_id: None,
183 },
184 }
185 }
186
187 /// Build the [`Metadata`].
188 pub fn build(self) -> Metadata {
189 self.inner
190 }
191
192 /// Set the file mode (complete `stat.st_mode` with file type and permission bits).
193 pub const fn st_mode(mut self, mode: u64) -> Self {
194 self.inner.stat.mode = mode;
195 self
196 }
197
198 /// Set the file type (`mode & mode::IFMT`).
199 pub const fn file_type(mut self, mode: u64) -> Self {
200 self.inner.stat.mode = (self.inner.stat.mode & !mode::IFMT) | (mode & mode::IFMT);
201 self
202 }
203
204 /// Set the file mode bits (`mode & !mode::IFMT`).
205 pub const fn file_mode(mut self, mode: u64) -> Self {
206 self.inner.stat.mode = (self.inner.stat.mode & mode::IFMT) | (mode & !mode::IFMT);
207 self
208 }
209
210 /// Set the modification time from a statx timespec value.
211 pub fn mtime_full(mut self, mtime: format::StatxTimestamp) -> Self {
212 self.inner.stat.mtime = mtime;
213 self
214 }
215
216 /// Set the modification time from a duration since the epoch (`SystemTime::UNIX_EPOCH`).
217 pub fn mtime_unix(self, mtime: std::time::Duration) -> Self {
218 self.mtime_full(format::StatxTimestamp::from_duration_since_epoch(mtime))
219 }
220
221 /// Set the modification time from a system time.
222 pub fn mtime(self, mtime: std::time::SystemTime) -> Self {
223 self.mtime_full(mtime.into())
224 }
225
226 /// Set the ownership information.
227 pub const fn owner(self, uid: u32, gid: u32) -> Self {
228 self.uid(uid).gid(gid)
229 }
230
231 /// Set the owning user id.
232 pub const fn uid(mut self, uid: u32) -> Self {
233 self.inner.stat.uid = uid;
234 self
235 }
236
237 /// Set the owning user id.
238 pub const fn gid(mut self, gid: u32) -> Self {
239 self.inner.stat.gid = gid;
240 self
241 }
242
243 /// Add an extended attribute.
244 pub fn xattr<N: AsRef<[u8]>, V: AsRef<[u8]>>(mut self, name: N, value: V) -> Self {
245 self.inner.xattrs.push(format::XAttr::new(name, value));
246 self
247 }
248
249 /// Add a user ACL entry.
250 pub fn acl_user(mut self, entry: format::acl::User) -> Self {
251 self.inner.acl.users.push(entry);
252 self
253 }
254
255 /// Add a group ACL entry.
256 pub fn acl_group(mut self, entry: format::acl::Group) -> Self {
257 self.inner.acl.groups.push(entry);
258 self
259 }
260
261 /// Add a user default-ACL entry.
262 pub fn default_acl_user(mut self, entry: format::acl::User) -> Self {
263 self.inner.acl.default_users.push(entry);
264 self
265 }
266
267 /// Add a group default-ACL entry.
268 pub fn default_acl_group(mut self, entry: format::acl::Group) -> Self {
269 self.inner.acl.default_groups.push(entry);
270 self
271 }
272
273 /// Set the default ACL entry for a directory.
274 pub const fn default_acl(mut self, entry: Option<format::acl::Default>) -> Self {
275 self.inner.acl.default = entry;
276 self
277 }
278
279 /// Set the quota project id.
280 pub fn quota_project_id(mut self, id: Option<u64>) -> Self {
281 self.inner.quota_project_id = id.map(|projid| format::QuotaProjectId { projid });
282 self
283 }
284
285 /// Set the raw file capability data.
286 pub fn fcaps(mut self, fcaps: Option<Vec<u8>>) -> Self {
287 self.inner.fcaps = fcaps.map(|data| format::FCaps { data });
288 self
289 }
290
291 #[cfg(all(test, target_os = "linux"))]
292 /// Fill the metadata with information from `struct stat`.
293 pub fn fill_from_stat(self, stat: &libc::stat) -> Self {
294 self.st_mode(u64::from(stat.st_mode))
295 .owner(stat.st_uid, stat.st_gid)
296 .mtime_full(format::StatxTimestamp::from_stat(
297 stat.st_mtime,
298 stat.st_mtime_nsec as u32,
299 ))
300 }
301 }
302
303 /// ACL entries of a pxar archive.
304 ///
305 /// This contains all the various ACL entry types supported by the pxar archive format.
306 #[derive(Clone, Debug, Default)]
307 #[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))]
308 pub struct Acl {
309 /// User ACL list.
310 pub users: Vec<format::acl::User>,
311
312 /// Group ACL list.
313 pub groups: Vec<format::acl::Group>,
314
315 /// Group object ACL entry.
316 pub group_obj: Option<format::acl::GroupObject>,
317
318 /// Default permissions.
319 pub default: Option<format::acl::Default>,
320
321 /// Default user permissions.
322 pub default_users: Vec<format::acl::User>,
323
324 /// Default group permissions.
325 pub default_groups: Vec<format::acl::Group>,
326 }
327
328 impl Acl {
329 /// Shortcut to check if all fields of this [`Acl`] entry are empty.
330 pub fn is_empty(&self) -> bool {
331 self.users.is_empty()
332 && self.groups.is_empty()
333 && self.group_obj.is_none()
334 && self.default.is_none()
335 && self.default_users.is_empty()
336 && self.default_groups.is_empty()
337 }
338 }
339
340 /// Pxar archive entry kind.
341 ///
342 /// Identifies whether the entry is a file, symlink, directory, etc.
343 #[derive(Clone, Debug)]
344 pub enum EntryKind {
345 /// Symbolic links.
346 Symlink(format::Symlink),
347
348 /// Hard links, relative to the root of the current archive.
349 Hardlink(format::Hardlink),
350
351 /// Device node.
352 Device(format::Device),
353
354 /// Named unix socket.
355 Socket,
356
357 /// Named pipe.
358 Fifo,
359
360 /// Regular file.
361 File {
362 /// The file size in bytes.
363 size: u64,
364
365 /// The file's byte offset inside the archive, if available.
366 offset: Option<u64>,
367 },
368
369 /// Directory entry. When iterating through an archive, the contents follow next.
370 Directory,
371
372 /// End of a directory. This is for internal use to remember the goodbye-table of a directory
373 /// entry. Will not occur during normal iteration.
374 GoodbyeTable,
375 }
376
377 /// A pxar archive entry. This contains the current path, file metadata and entry type specific
378 /// information.
379 #[derive(Clone, Debug)]
380 pub struct Entry {
381 path: PathBuf,
382 metadata: Metadata,
383 kind: EntryKind,
384 }
385
386 /// General accessors.
387 impl Entry {
388 /// Clear everything except for the path.
389 fn clear_data(&mut self) {
390 self.metadata = Metadata::default();
391 self.kind = EntryKind::GoodbyeTable;
392 }
393
394 fn internal_default() -> Self {
395 Self {
396 path: PathBuf::default(),
397 metadata: Metadata::default(),
398 kind: EntryKind::GoodbyeTable,
399 }
400 }
401
402 fn take(&mut self) -> Self {
403 let this = mem::replace(self, Self::internal_default());
404 self.path = this.path.clone();
405 this
406 }
407
408 /// Get the full path of this file within the current pxar directory structure.
409 #[inline]
410 pub fn path(&self) -> &Path {
411 &self.path
412 }
413
414 /// Convenience method to get just the file name portion of the current path.
415 #[inline]
416 pub fn file_name(&self) -> &OsStr {
417 self.path.file_name().unwrap_or_else(|| OsStr::new(""))
418 }
419
420 /// Get the file metadata.
421 #[inline]
422 pub fn metadata(&self) -> &Metadata {
423 &self.metadata
424 }
425
426 /// Take out the metadata.
427 #[inline]
428 pub fn into_metadata(self) -> Metadata {
429 self.metadata
430 }
431
432 /// Get the entry-type specific information.
433 pub fn kind(&self) -> &EntryKind {
434 &self.kind
435 }
436
437 /// Get the value of the symbolic link if it is one.
438 pub fn get_symlink(&self) -> Option<&OsStr> {
439 match &self.kind {
440 EntryKind::Symlink(link) => Some(link.as_ref()),
441 _ => None,
442 }
443 }
444
445 /// Get the value of the hard link if it is one.
446 pub fn get_hardlink(&self) -> Option<&OsStr> {
447 match &self.kind {
448 EntryKind::Hardlink(link) => Some(link.as_ref()),
449 _ => None,
450 }
451 }
452
453 /// Get the value of the device node if it is one.
454 pub fn get_device(&self) -> Option<format::Device> {
455 match &self.kind {
456 EntryKind::Device(dev) => Some(dev.clone()),
457 _ => None,
458 }
459 }
460 }
461
462 /// Convenience helpers.
463 impl Entry {
464 /// Check whether this is a directory.
465 pub fn is_dir(&self) -> bool {
466 matches!(self.kind, EntryKind::Directory)
467 }
468
469 /// Check whether this is a symbolic link.
470 pub fn is_symlink(&self) -> bool {
471 matches!(self.kind, EntryKind::Symlink(_))
472 }
473
474 /// Check whether this is a hard link.
475 pub fn is_hardlink(&self) -> bool {
476 matches!(self.kind, EntryKind::Hardlink(_))
477 }
478
479 /// Check whether this is a device node.
480 pub fn is_device(&self) -> bool {
481 matches!(self.kind, EntryKind::Device(_))
482 }
483
484 /// Check whether this is a regular file.
485 pub fn is_regular_file(&self) -> bool {
486 matches!(self.kind, EntryKind::File { .. })
487 }
488
489 /// Get the file size if this is a regular file, or `None`.
490 pub fn file_size(&self) -> Option<u64> {
491 match self.kind {
492 EntryKind::File { size, .. } => Some(size),
493 _ => None,
494 }
495 }
496 }