]> git.proxmox.com Git - pxar.git/blob - src/lib.rs
fixup build and test dependencies
[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 /// Reexport of `format::Entry`. Since this conveys mostly information found via the `stat` syscall
24 /// we mostly use this name for public interfaces.
25 #[doc(inline)]
26 pub use format::Entry as Stat;
27
28 #[doc(inline)]
29 pub use format::mode;
30
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)]
36 #[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))]
37 pub struct Metadata {
38 /// Data typically found in a `stat()` call.
39 pub stat: Stat,
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
54 impl From<Stat> for Metadata {
55 fn from(stat: Stat) -> Self {
56 Self {
57 stat,
58 ..Default::default()
59 }
60 }
61 }
62
63 impl From<&std::fs::Metadata> for Metadata {
64 fn from(meta: &std::fs::Metadata) -> Metadata {
65 // NOTE: fill the remaining metadata via feature flags?
66 Self::from(Stat::from(meta))
67 }
68 }
69
70 impl From<std::fs::Metadata> for Metadata {
71 fn from(meta: std::fs::Metadata) -> Metadata {
72 Self::from(&meta)
73 }
74 }
75
76 /// Convenience helpers.
77 impl Metadata {
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
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 }
113
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
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 {
129 self.stat.mtime_as_duration()
130 }
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,
167 mtime: format::StatxTimestamp::zero(),
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
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
204 }
205
206 /// Set the modification time from a duration since the epoch (`SystemTime::UNIX_EPOCH`).
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())
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 }
280 }
281
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)]
286 #[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))]
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
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
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
332 /// Named unix socket.
333 Socket,
334
335 /// Named pipe.
336 Fifo,
337
338 /// Regular file.
339 File { size: u64, offset: Option<u64> },
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.
346 GoodbyeTable,
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,
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();
363 self.kind = EntryKind::GoodbyeTable;
364 }
365
366 fn internal_default() -> Self {
367 Self {
368 path: PathBuf::default(),
369 metadata: Metadata::default(),
370 kind: EntryKind::GoodbyeTable,
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
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 {
389 self.path.file_name().unwrap_or_else(|| OsStr::new(""))
390 }
391
392 /// Get the file metadata.
393 #[inline]
394 pub fn metadata(&self) -> &Metadata {
395 &self.metadata
396 }
397
398 /// Take out the metadata.
399 #[inline]
400 pub fn into_metadata(self) -> Metadata {
401 self.metadata
402 }
403
404 /// Get the entry-type specific information.
405 pub fn kind(&self) -> &EntryKind {
406 &self.kind
407 }
408
409 /// Get the value of the symbolic link if it is one.
410 pub fn get_symlink(&self) -> Option<&OsStr> {
411 match &self.kind {
412 EntryKind::Symlink(link) => Some(link.as_ref()),
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 {
420 EntryKind::Hardlink(link) => Some(link.as_ref()),
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 }
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 }
483 }