]> git.proxmox.com Git - pxar.git/blob - src/lib.rs
03f5df56f29d0da9c75c6f7c56a77c09886d3f21
[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 #![deny(unsafe_op_in_unsafe_fn)]
6
7 use std::ffi::OsStr;
8 use std::mem;
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;
21 pub mod binary_tree_array;
22 pub mod decoder;
23 pub mod encoder;
24
25 #[doc(inline)]
26 pub use format::{mode, Stat};
27
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.
32 #[derive(Clone, Debug, Default, PartialEq)]
33 #[cfg_attr(feature = "test-harness", derive(Eq))]
34 pub struct Metadata {
35 /// Data typically found in a `stat()` call.
36 pub stat: Stat,
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
51 impl From<Stat> for Metadata {
52 fn from(stat: Stat) -> Self {
53 Self {
54 stat,
55 ..Default::default()
56 }
57 }
58 }
59
60 impl From<&std::fs::Metadata> for Metadata {
61 fn from(meta: &std::fs::Metadata) -> Metadata {
62 // NOTE: fill the remaining metadata via feature flags?
63 Self::from(Stat::from(meta))
64 }
65 }
66
67 impl From<std::fs::Metadata> for Metadata {
68 fn from(meta: std::fs::Metadata) -> Metadata {
69 Self::from(&meta)
70 }
71 }
72
73 /// Convenience helpers.
74 impl Metadata {
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
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 }
110
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
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 {
126 self.stat.mtime_as_duration()
127 }
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.
140 pub const fn builder(mode: u64) -> MetadataBuilder {
141 MetadataBuilder::new(mode)
142 }
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 }
149 }
150
151 impl From<MetadataBuilder> for Metadata {
152 fn from(builder: MetadataBuilder) -> Self {
153 builder.build()
154 }
155 }
156
157 /// A builder for the file [`Metadata`] stored in pxar archives.
158 pub struct MetadataBuilder {
159 inner: Metadata,
160 }
161
162 impl MetadataBuilder {
163 /// Create a new [`MetadataBuilder`] given an initial type/mode bitset.
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,
172 mtime: format::StatxTimestamp::zero(),
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
189 /// Build the [`Metadata`].
190 pub fn build(self) -> Metadata {
191 self.inner
192 }
193
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
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
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
216 }
217
218 /// Set the modification time from a duration since the epoch (`SystemTime::UNIX_EPOCH`).
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())
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 }
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 }
303 }
304
305 /// ACL entries of a pxar archive.
306 ///
307 /// This contains all the various ACL entry types supported by the pxar archive format.
308 #[derive(Clone, Debug, Default, PartialEq)]
309 #[cfg_attr(feature = "test-harness", derive(Eq))]
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
330 impl Acl {
331 /// Shortcut to check if all fields of this [`Acl`] entry are empty.
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
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
356 /// Named unix socket.
357 Socket,
358
359 /// Named pipe.
360 Fifo,
361
362 /// Regular file.
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 },
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.
376 GoodbyeTable,
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,
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();
393 self.kind = EntryKind::GoodbyeTable;
394 }
395
396 fn internal_default() -> Self {
397 Self {
398 path: PathBuf::default(),
399 metadata: Metadata::default(),
400 kind: EntryKind::GoodbyeTable,
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
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 {
419 self.path.file_name().unwrap_or_else(|| OsStr::new(""))
420 }
421
422 /// Get the file metadata.
423 #[inline]
424 pub fn metadata(&self) -> &Metadata {
425 &self.metadata
426 }
427
428 /// Take out the metadata.
429 #[inline]
430 pub fn into_metadata(self) -> Metadata {
431 self.metadata
432 }
433
434 /// Get the entry-type specific information.
435 pub fn kind(&self) -> &EntryKind {
436 &self.kind
437 }
438
439 /// Get the value of the symbolic link if it is one.
440 pub fn get_symlink(&self) -> Option<&OsStr> {
441 match &self.kind {
442 EntryKind::Symlink(link) => Some(link.as_ref()),
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 {
450 EntryKind::Hardlink(link) => Some(link.as_ref()),
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 {
468 matches!(self.kind, EntryKind::Directory)
469 }
470
471 /// Check whether this is a symbolic link.
472 pub fn is_symlink(&self) -> bool {
473 matches!(self.kind, EntryKind::Symlink(_))
474 }
475
476 /// Check whether this is a hard link.
477 pub fn is_hardlink(&self) -> bool {
478 matches!(self.kind, EntryKind::Hardlink(_))
479 }
480
481 /// Check whether this is a device node.
482 pub fn is_device(&self) -> bool {
483 matches!(self.kind, EntryKind::Device(_))
484 }
485
486 /// Check whether this is a regular file.
487 pub fn is_regular_file(&self) -> bool {
488 matches!(self.kind, EntryKind::File { .. })
489 }
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 }
498 }