]> git.proxmox.com Git - pxar.git/blame - src/lib.rs
derive PartialEq trait for Metadata and related structs
[pxar.git] / src / lib.rs
CommitLineData
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
7use std::ffi::OsStr;
8use std::mem;
6cd4f635
WB
9use std::path::{Path, PathBuf};
10
11#[macro_use]
12mod macros;
13
14pub mod format;
15
16pub(crate) mod util;
17
18mod poll_fn;
19
20pub mod accessor;
fbddffdc 21pub mod binary_tree_array;
6cd4f635 22pub mod decoder;
70acf637
WB
23pub mod encoder;
24
a13a2f74 25#[doc(inline)]
2ea8aff2 26pub 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
34pub 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
51impl From<Stat> for Metadata {
52 fn from(stat: Stat) -> Self {
53 Self {
54 stat,
55 ..Default::default()
56 }
57 }
58}
59
96a067c5
WB
60impl 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
67impl 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.
74impl 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
151impl 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
158pub struct MetadataBuilder {
159 inner: Metadata,
160}
161
162impl 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
310pub 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 330impl 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)]
346pub 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)]
382pub struct Entry {
383 path: PathBuf,
384 metadata: Metadata,
385 kind: EntryKind,
6cd4f635
WB
386}
387
388/// General accessors.
389impl 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.
465impl 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}