]> git.proxmox.com Git - pxar.git/blame - src/format/mod.rs
fix deprecated use of std::u64 modules
[pxar.git] / src / format / mod.rs
CommitLineData
6cd4f635
WB
1//! *pxar* binary format definition
2//!
3//! Please note the all values are stored in little endian ordering.
4//!
5//! The Archive contains a list of items. Each item starts with a `Header`, followed by the
6//! item data.
f0279c01
WB
7//!
8//! An archive contains items in the following order:
9//! * `ENTRY` -- containing general stat() data and related bits
10//! * `XATTR` -- one extended attribute
11//! * ... -- more of these when there are multiple defined
12//! * `ACL_USER` -- one `USER ACL` entry
13//! * ... -- more of these when there are multiple defined
14//! * `ACL_GROUP` -- one `GROUP ACL` entry
15//! * ... -- more of these when there are multiple defined
16//! * `ACL_GROUP_OBJ` -- The `ACL_GROUP_OBJ`
17//! * `ACL_DEFAULT` -- The various default ACL fields if there's one defined
18//! * `ACL_DEFAULT_USER` -- one USER ACL entry
19//! * ... -- more of these when multiple are defined
20//! * `ACL_DEFAULT_GROUP` -- one GROUP ACL entry
21//! * ... -- more of these when multiple are defined
22//! * `FCAPS` -- file capability in Linux disk format
23//! * `QUOTA_PROJECT_ID` -- the ext4/xfs quota project ID
24//! * `PAYLOAD` -- file contents, if it is one
25//! * `SYMLINK` -- symlink target, if it is one
26//! * `DEVICE` -- device major/minor, if it is a block/char device
27//!
28//! If we are serializing a directory, then this is followed by:
29//!
30//! * `FILENAME` -- name of the first directory entry (strictly ordered!)
31//! * `<archive>` -- serialization of the first directory entry's metadata and contents,
32//! following the exact same archive format
33//! * `FILENAME` -- name of the second directory entry (strictly ordered!)
34//! * `<archive>` -- serialization of the second directory entry
35//! * ...
36//! * `GOODBYE` -- lookup table at the end of a list of directory entries
6cd4f635
WB
37
38use std::cmp::Ordering;
2c442007 39use std::ffi::{CStr, OsStr};
4a13b8a3
FG
40use std::fmt;
41use std::fmt::Display;
6cd4f635
WB
42use std::io;
43use std::mem::size_of;
54912ae6 44use std::os::unix::ffi::OsStrExt;
6cd4f635 45use std::path::Path;
939f2468 46use std::time::{Duration, SystemTime};
6cd4f635
WB
47
48use endian_trait::Endian;
49use siphasher::sip::SipHasher24;
50
51pub mod acl;
52
1b1e52a4
WB
53// generated with:
54// $ echo -n 'PROXMOX ARCHIVE FORMAT' | sha1sum | sed -re 's/^(.{16})(.{16}).*$/0x\1, 0x\2/'
55pub const PXAR_HASH_KEY_1: u64 = 0x83ac3f1cfbb450db;
56pub const PXAR_HASH_KEY_2: u64 = 0xaa4f1b6879369fbd;
57
70acf637
WB
58/// While these constants correspond to `libc::S_` constants, we need these to be fixed for the
59/// format itself, so we redefine them here.
60///
61/// Additionally this gets rid of a bunch of casts between u32 and u64.
62///
63/// You can usually find the values for these in `/usr/include/linux/stat.h`.
64#[rustfmt::skip]
65pub mod mode {
66 pub const IFMT : u64 = 0o0170000;
67
68 pub const IFSOCK : u64 = 0o0140000;
69 pub const IFLNK : u64 = 0o0120000;
70 pub const IFREG : u64 = 0o0100000;
71 pub const IFBLK : u64 = 0o0060000;
72 pub const IFDIR : u64 = 0o0040000;
73 pub const IFCHR : u64 = 0o0020000;
74 pub const IFIFO : u64 = 0o0010000;
a13a2f74 75
70acf637
WB
76 pub const ISUID : u64 = 0o0004000;
77 pub const ISGID : u64 = 0o0002000;
78 pub const ISVTX : u64 = 0o0001000;
79}
80
644e844d
WB
81/// Beginning of an entry (current version).
82pub const PXAR_ENTRY: u64 = 0xd5956474e588acef;
83/// Previous version of the entry struct
84pub const PXAR_ENTRY_V1: u64 = 0x11da850a1c1cceff;
1b1e52a4
WB
85pub const PXAR_FILENAME: u64 = 0x16701121063917b3;
86pub const PXAR_SYMLINK: u64 = 0x27f971e7dbf5dc5f;
87pub const PXAR_DEVICE: u64 = 0x9fc9e906586d5ce9;
88pub const PXAR_XATTR: u64 = 0x0dab0229b57dcd03;
89pub const PXAR_ACL_USER: u64 = 0x2ce8540a457d55b8;
90pub const PXAR_ACL_GROUP: u64 = 0x136e3eceb04c03ab;
91pub const PXAR_ACL_GROUP_OBJ: u64 = 0x10868031e9582876;
92pub const PXAR_ACL_DEFAULT: u64 = 0xbbbb13415a6896f5;
93pub const PXAR_ACL_DEFAULT_USER: u64 = 0xc89357b40532cd1f;
94pub const PXAR_ACL_DEFAULT_GROUP: u64 = 0xf90a8a5816038ffe;
95pub const PXAR_FCAPS: u64 = 0x2da9dd9db5f7fb67;
96pub const PXAR_QUOTA_PROJID: u64 = 0xe07540e82f7d1cbb;
6cd4f635 97/// Marks item as hardlink
1b1e52a4 98pub const PXAR_HARDLINK: u64 = 0x51269c8422bd7275;
6cd4f635 99/// Marks the beginnig of the payload (actual content) of regular files
1b1e52a4 100pub const PXAR_PAYLOAD: u64 = 0x28147a1b0b7c1a25;
6cd4f635 101/// Marks item as entry of goodbye table
1b1e52a4 102pub const PXAR_GOODBYE: u64 = 0x2fec4fa642d5731d;
6cd4f635 103/// The end marker used in the GOODBYE object
1b1e52a4 104pub const PXAR_GOODBYE_TAIL_MARKER: u64 = 0xef5eed5b753e1555;
6cd4f635
WB
105
106#[derive(Debug, Endian)]
107#[repr(C)]
108pub struct Header {
109 /// The item type (see `PXAR_` constants).
110 pub htype: u64,
111 /// The size of the item, including the size of `Header`.
112 full_size: u64,
113}
114
115impl Header {
70acf637
WB
116 #[inline]
117 pub fn with_full_size(htype: u64, full_size: u64) -> Self {
118 Self { htype, full_size }
119 }
120
121 #[inline]
122 pub fn with_content_size(htype: u64, content_size: u64) -> Self {
123 Self::with_full_size(htype, content_size + size_of::<Header>() as u64)
124 }
125
6cd4f635
WB
126 #[inline]
127 pub fn full_size(&self) -> u64 {
128 self.full_size
129 }
130
131 #[inline]
132 pub fn content_size(&self) -> u64 {
133 self.full_size() - (size_of::<Self>() as u64)
134 }
ec0761f9
FG
135
136 #[inline]
137 pub fn max_content_size(&self) -> u64 {
138 match self.htype {
139 // + null-termination
140 PXAR_FILENAME => crate::util::MAX_FILENAME_LEN + 1,
141 // + null-termination
142 PXAR_SYMLINK => crate::util::MAX_PATH_LEN + 1,
143 // + null-termination + offset
144 PXAR_HARDLINK => crate::util::MAX_PATH_LEN + 1 + (size_of::<u64>() as u64),
145 PXAR_DEVICE => size_of::<Device>() as u64,
146 PXAR_XATTR | PXAR_FCAPS => crate::util::MAX_XATTR_LEN,
147 PXAR_ACL_USER | PXAR_ACL_DEFAULT_USER => size_of::<acl::User>() as u64,
148 PXAR_ACL_GROUP | PXAR_ACL_DEFAULT_GROUP => size_of::<acl::Group>() as u64,
149 PXAR_ACL_DEFAULT => size_of::<acl::Default>() as u64,
93fef2fd 150 PXAR_ACL_GROUP_OBJ => size_of::<acl::GroupObject>() as u64,
ec0761f9 151 PXAR_QUOTA_PROJID => size_of::<QuotaProjectId>() as u64,
2ea8aff2 152 PXAR_ENTRY => size_of::<Stat>() as u64,
1a2a5570
WB
153 PXAR_PAYLOAD | PXAR_GOODBYE => u64::MAX - (size_of::<Self>() as u64),
154 _ => u64::MAX - (size_of::<Self>() as u64),
ec0761f9
FG
155 }
156 }
157
158 #[inline]
159 pub fn check_header_size(&self) -> io::Result<()> {
160 if self.full_size() < size_of::<Header>() as u64 {
161 io_bail!("invalid header {} - too small ({})", self, self.full_size());
162 }
163
164 if self.content_size() > self.max_content_size() {
710e6c8b
WB
165 io_bail!(
166 "invalid content size ({} > {}) of entry with {}",
167 self.content_size(),
168 self.max_content_size(),
169 self
170 );
ec0761f9
FG
171 }
172 Ok(())
173 }
174}
4a13b8a3
FG
175
176impl Display for Header {
177 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178 let readable = match self.htype {
179 PXAR_FILENAME => "FILENAME",
180 PXAR_SYMLINK => "SYMLINK",
181 PXAR_HARDLINK => "HARDLINK",
182 PXAR_DEVICE => "DEVICE",
183 PXAR_XATTR => "XATTR",
184 PXAR_FCAPS => "FCAPS",
185 PXAR_ACL_USER => "ACL_USER",
186 PXAR_ACL_DEFAULT_USER => "ACL_DEFAULT_USER",
187 PXAR_ACL_GROUP => "ACL_GROUP",
188 PXAR_ACL_DEFAULT_GROUP => "ACL_DEFAULT_GROUP",
189 PXAR_ACL_DEFAULT => "ACL_DEFAULT",
190 PXAR_ACL_GROUP_OBJ => "ACL_GROUP_OBJ",
191 PXAR_QUOTA_PROJID => "QUOTA_PROJID",
192 PXAR_ENTRY => "ENTRY",
193 PXAR_PAYLOAD => "PAYLOAD",
194 PXAR_GOODBYE => "GOODBYE",
195 _ => "UNKNOWN",
196 };
197 write!(f, "{} header ({:x})", readable, self.htype)
198 }
6cd4f635
WB
199}
200
939f2468
WB
201#[derive(Clone, Debug, Eq, PartialEq)]
202pub enum SignedDuration {
203 Positive(Duration),
204 Negative(Duration),
205}
206
207#[derive(Clone, Debug, Default, Endian, Eq, PartialEq)]
208#[repr(C)]
209pub struct StatxTimestamp {
210 /// Seconds since the epoch (unix time).
211 pub secs: i64,
212
213 /// Nanoseconds since this struct's `secs`.
214 pub nanos: u32,
180186c5
WB
215
216 /// (Erroneously introduced padding...)
217 _zero: u32,
939f2468
WB
218}
219
220impl From<SystemTime> for StatxTimestamp {
221 fn from(time: SystemTime) -> Self {
222 match time.duration_since(SystemTime::UNIX_EPOCH) {
223 Ok(positive) => Self::from_duration_since_epoch(positive),
224 Err(negative) => Self::from_duration_before_epoch(negative.duration()),
225 }
226 }
227}
228
229impl StatxTimestamp {
180186c5
WB
230 /// Create a timestamp from seconds and nanoseconds.
231 pub const fn new(secs: i64, nanos: u32) -> Self {
232 Self {
737f75cf
WB
233 secs,
234 nanos,
235 _zero: 0,
180186c5
WB
236 }
237 }
238
939f2468
WB
239 /// `const` version of `Default`
240 pub const fn zero() -> Self {
737f75cf
WB
241 Self {
242 secs: 0,
243 nanos: 0,
244 _zero: 0,
245 }
939f2468
WB
246 }
247
2ea8aff2
WB
248 #[cfg(all(test, target_os = "linux"))]
249 /// From data found in `struct stat` (`libc::stat`).
250 pub fn from_stat(sec: i64, nsec: u32) -> Self {
251 if sec < 0 {
252 Self::from_duration_before_epoch(Duration::new((-sec) as u64, nsec))
253 } else {
254 Self::from_duration_since_epoch(Duration::new(sec as u64, nsec))
255 }
256 }
257
939f2468
WB
258 /// Turn a positive duration relative to the unix epoch into a time stamp.
259 pub fn from_duration_since_epoch(duration: Duration) -> Self {
260 Self {
261 secs: duration.as_secs() as i64,
262 nanos: duration.subsec_nanos(),
180186c5 263 _zero: 0,
939f2468
WB
264 }
265 }
266
267 /// Turn a *negative* duration from relative to the unix epoch into a time stamp.
268 pub fn from_duration_before_epoch(duration: Duration) -> Self {
269 match duration.subsec_nanos() {
270 0 => Self {
bb6779b5 271 secs: -(duration.as_secs() as i64),
939f2468 272 nanos: 0,
180186c5 273 _zero: 0,
939f2468
WB
274 },
275 nanos => Self {
276 secs: -(duration.as_secs() as i64) - 1,
277 nanos: 1_000_000_000 - nanos,
180186c5 278 _zero: 0,
939f2468
WB
279 },
280 }
281 }
282
283 /// Get the duration since the epoch. an `Ok` value is a positive duration, an `Err` value is a
284 /// negative duration.
285 pub fn to_duration(&self) -> SignedDuration {
286 if self.secs >= 0 {
287 SignedDuration::Positive(Duration::new(self.secs as u64, self.nanos))
288 } else {
bb6779b5 289 // this handles the nanos=0 case as `Duration::new()` performs the carry-over.
939f2468
WB
290 SignedDuration::Negative(Duration::new(
291 -(self.secs + 1) as u64,
292 1_000_000_000 - self.nanos,
293 ))
294 }
295 }
296
297 /// Get a `std::time::SystemTime` from this time stamp.
298 pub fn system_time(&self) -> SystemTime {
299 match self.to_duration() {
300 SignedDuration::Positive(positive) => SystemTime::UNIX_EPOCH + positive,
301 SignedDuration::Negative(negative) => SystemTime::UNIX_EPOCH - negative,
302 }
303 }
304}
305
306#[test]
307fn test_statx_timestamp() {
737f75cf
WB
308 assert_eq!(
309 size_of::<StatxTimestamp>(),
310 16,
311 "StatxTimestamp size needs to be 16 bytes"
312 );
939f2468
WB
313 const MAY_1_2015_1530: i64 = 1430487000;
314 let system_time = SystemTime::UNIX_EPOCH + Duration::new(MAY_1_2015_1530 as u64, 1_000_000);
315 let tx = StatxTimestamp::from(system_time);
316 assert_eq!(
317 tx,
318 StatxTimestamp {
319 secs: MAY_1_2015_1530,
320 nanos: 1_000_000,
180186c5 321 _zero: 0,
939f2468
WB
322 }
323 );
324 assert_eq!(tx.system_time(), system_time);
325
326 const MAY_1_1960_1530: i64 = -305112600;
327 let system_time = SystemTime::UNIX_EPOCH - Duration::new(-MAY_1_1960_1530 as u64, 1_000_000);
328 let tx = StatxTimestamp::from(system_time);
329 assert_eq!(
330 tx,
331 StatxTimestamp {
332 secs: MAY_1_1960_1530 - 1,
333 nanos: 999_000_000,
180186c5 334 _zero: 0,
939f2468
WB
335 }
336 );
337 assert_eq!(tx.system_time(), system_time);
939f2468 338
bb6779b5
WB
339 let system_time = SystemTime::UNIX_EPOCH - Duration::new(-MAY_1_1960_1530 as u64, 0);
340 let tx = StatxTimestamp::from(system_time);
341 assert_eq!(
342 tx,
343 StatxTimestamp {
344 secs: MAY_1_1960_1530,
345 nanos: 0,
180186c5 346 _zero: 0,
bb6779b5
WB
347 }
348 );
349 assert_eq!(tx.system_time(), system_time);
350}
6cd4f635 351#[derive(Clone, Debug, Default, Endian)]
0104cf77 352#[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))]
6cd4f635 353#[repr(C)]
2ea8aff2 354pub struct Stat_V1 {
6cd4f635
WB
355 pub mode: u64,
356 pub flags: u64,
357 pub uid: u32,
358 pub gid: u32,
359 pub mtime: u64,
360}
361
1646a72d
WB
362impl From<Stat_V1> for Stat {
363 fn from(v1: Stat_V1) -> Stat {
2ea8aff2 364 Stat {
1646a72d
WB
365 mode: v1.mode,
366 flags: v1.flags,
367 uid: v1.uid,
368 gid: v1.gid,
369 mtime: StatxTimestamp::from_duration_since_epoch(Duration::from_nanos(v1.mtime)),
644e844d
WB
370 }
371 }
372}
373
374#[derive(Clone, Debug, Default, Endian)]
375#[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))]
376#[repr(C)]
2ea8aff2 377pub struct Stat {
644e844d
WB
378 pub mode: u64,
379 pub flags: u64,
380 pub uid: u32,
381 pub gid: u32,
382 pub mtime: StatxTimestamp,
383}
384
70acf637 385/// Builder pattern methods.
2ea8aff2 386impl Stat {
70acf637
WB
387 pub const fn mode(self, mode: u64) -> Self {
388 Self { mode, ..self }
389 }
390
391 pub const fn flags(self, flags: u64) -> Self {
392 Self { flags, ..self }
393 }
394
395 pub const fn uid(self, uid: u32) -> Self {
396 Self { uid, ..self }
397 }
398
399 pub const fn gid(self, gid: u32) -> Self {
400 Self { gid, ..self }
401 }
402
644e844d 403 pub const fn mtime(self, mtime: StatxTimestamp) -> Self {
70acf637
WB
404 Self { mtime, ..self }
405 }
406
407 pub const fn set_dir(self) -> Self {
408 let mode = self.mode;
409 self.mode((mode & !mode::IFMT) | mode::IFDIR)
410 }
411
412 pub const fn set_regular_file(self) -> Self {
413 let mode = self.mode;
414 self.mode((mode & !mode::IFMT) | mode::IFREG)
415 }
416
417 pub const fn set_symlink(self) -> Self {
418 let mode = self.mode;
419 self.mode((mode & !mode::IFMT) | mode::IFLNK)
420 }
421
422 pub const fn set_blockdev(self) -> Self {
423 let mode = self.mode;
424 self.mode((mode & !mode::IFMT) | mode::IFBLK)
425 }
426
427 pub const fn set_chardev(self) -> Self {
428 let mode = self.mode;
429 self.mode((mode & !mode::IFMT) | mode::IFCHR)
430 }
431
432 pub const fn set_fifo(self) -> Self {
433 let mode = self.mode;
434 self.mode((mode & !mode::IFMT) | mode::IFIFO)
435 }
436}
437
6da548dd 438/// Convenience accessor methods.
2ea8aff2 439impl Stat {
fc2f1b9f 440 /// Get the mtime as duration since the epoch.
644e844d
WB
441 pub fn mtime_as_duration(&self) -> SignedDuration {
442 self.mtime.to_duration()
6da548dd 443 }
8617cae3
WB
444
445 /// Get the file type portion of the mode bitfield.
446 pub fn get_file_bits(&self) -> u64 {
447 self.mode & mode::IFMT
448 }
449
450 /// Get the permission portion of the mode bitfield.
451 pub fn get_permission_bits(&self) -> u64 {
452 self.mode & !mode::IFMT
453 }
6da548dd
WB
454}
455
70acf637 456/// Convenience methods.
2ea8aff2 457impl Stat {
21d66e10
WB
458 /// Get the file type (`mode & mode::IFMT`).
459 pub fn file_type(&self) -> u64 {
460 self.mode & mode::IFMT
461 }
462
463 /// Get the file mode bits (`mode & !mode::IFMT`).
464 pub fn file_mode(&self) -> u64 {
465 self.mode & !mode::IFMT
466 }
467
70acf637
WB
468 /// Check whether this is a directory.
469 pub fn is_dir(&self) -> bool {
470 (self.mode & mode::IFMT) == mode::IFDIR
471 }
472
473 /// Check whether this is a symbolic link.
474 pub fn is_symlink(&self) -> bool {
475 (self.mode & mode::IFMT) == mode::IFLNK
476 }
477
478 /// Check whether this is a device node.
479 pub fn is_device(&self) -> bool {
480 let fmt = self.mode & mode::IFMT;
481 fmt == mode::IFCHR || fmt == mode::IFBLK
482 }
483
da286c28
WB
484 /// Check whether this is a block device node.
485 pub fn is_blockdev(&self) -> bool {
486 let fmt = self.mode & mode::IFMT;
487 fmt == mode::IFBLK
488 }
489
490 /// Check whether this is a character device node.
491 pub fn is_chardev(&self) -> bool {
492 let fmt = self.mode & mode::IFMT;
493 fmt == mode::IFCHR
494 }
495
70acf637
WB
496 /// Check whether this is a regular file.
497 pub fn is_regular_file(&self) -> bool {
498 (self.mode & mode::IFMT) == mode::IFREG
499 }
2287d8b2
WB
500
501 /// Check whether this is a named pipe (FIFO).
502 pub fn is_fifo(&self) -> bool {
503 (self.mode & mode::IFMT) == mode::IFIFO
504 }
505
506 /// Check whether this is a named socket.
507 pub fn is_socket(&self) -> bool {
508 (self.mode & mode::IFMT) == mode::IFSOCK
509 }
70acf637
WB
510}
511
2ea8aff2
WB
512impl From<&std::fs::Metadata> for Stat {
513 fn from(meta: &std::fs::Metadata) -> Stat {
70acf637
WB
514 #[cfg(unix)]
515 use std::os::unix::fs::MetadataExt;
516
2ea8aff2 517 let this = Stat::default();
70acf637
WB
518
519 #[cfg(unix)]
520 let this = this
521 .uid(meta.uid())
522 .gid(meta.gid())
0a6c7350
WB
523 .mode(meta.mode() as u64);
524
525 let this = match meta.modified() {
644e844d 526 Ok(mtime) => this.mtime(mtime.into()),
0a6c7350
WB
527 Err(_) => this,
528 };
70acf637
WB
529
530 let file_type = meta.file_type();
531 let mode = this.mode;
1b25fc08 532 if file_type.is_dir() {
70acf637
WB
533 this.mode(mode | mode::IFDIR)
534 } else if file_type.is_symlink() {
535 this.mode(mode | mode::IFLNK)
536 } else {
537 this.mode(mode | mode::IFREG)
1b25fc08 538 }
70acf637
WB
539 }
540}
541
6cd4f635
WB
542#[derive(Clone, Debug)]
543pub struct Filename {
544 pub name: Vec<u8>,
545}
546
547#[derive(Clone, Debug)]
548pub struct Symlink {
549 pub data: Vec<u8>,
550}
551
7b389f88
WB
552impl Symlink {
553 pub fn as_os_str(&self) -> &OsStr {
554 self.as_ref()
555 }
556}
557
353bbf73
WB
558impl AsRef<[u8]> for Symlink {
559 fn as_ref(&self) -> &[u8] {
560 &self.data
561 }
562}
563
54912ae6
WB
564impl AsRef<OsStr> for Symlink {
565 fn as_ref(&self) -> &OsStr {
353bbf73 566 OsStr::from_bytes(&self.data[..self.data.len().max(1) - 1])
54912ae6
WB
567 }
568}
569
6cd4f635
WB
570#[derive(Clone, Debug)]
571pub struct Hardlink {
b0752929 572 pub offset: u64,
6cd4f635
WB
573 pub data: Vec<u8>,
574}
575
7b389f88
WB
576impl Hardlink {
577 pub fn as_os_str(&self) -> &OsStr {
578 self.as_ref()
579 }
580}
581
353bbf73
WB
582impl AsRef<[u8]> for Hardlink {
583 fn as_ref(&self) -> &[u8] {
584 &self.data
585 }
586}
587
54912ae6
WB
588impl AsRef<OsStr> for Hardlink {
589 fn as_ref(&self) -> &OsStr {
353bbf73 590 OsStr::from_bytes(&self.data[..self.data.len().max(1) - 1])
54912ae6
WB
591 }
592}
593
6cd4f635
WB
594#[derive(Clone, Debug, Eq)]
595#[repr(C)]
596pub struct XAttr {
597 pub(crate) data: Vec<u8>,
598 pub(crate) name_len: usize,
599}
600
601impl XAttr {
602 pub fn new<N: AsRef<[u8]>, V: AsRef<[u8]>>(name: N, value: V) -> Self {
603 let name = name.as_ref();
604 let value = value.as_ref();
605 let mut data = Vec::with_capacity(name.len() + value.len() + 1);
606 data.extend(name);
607 data.push(0);
608 data.extend(value);
609 Self {
610 data,
611 name_len: name.len(),
612 }
613 }
614
2c442007 615 pub fn name(&self) -> &CStr {
2fd112ac 616 unsafe { CStr::from_bytes_with_nul_unchecked(&self.data[..self.name_len + 1]) }
6cd4f635
WB
617 }
618
619 pub fn value(&self) -> &[u8] {
620 &self.data[(self.name_len + 1)..]
621 }
622}
623
624impl Ord for XAttr {
625 fn cmp(&self, other: &XAttr) -> Ordering {
626 self.name().cmp(&other.name())
627 }
628}
629
630impl PartialOrd for XAttr {
631 fn partial_cmp(&self, other: &XAttr) -> Option<Ordering> {
632 Some(self.cmp(other))
633 }
634}
635
636impl PartialEq for XAttr {
637 fn eq(&self, other: &XAttr) -> bool {
638 self.name() == other.name()
639 }
640}
641
0104cf77 642#[derive(Clone, Debug, Endian, Eq, PartialEq)]
6cd4f635
WB
643#[repr(C)]
644pub struct Device {
645 pub major: u64,
646 pub minor: u64,
647}
648
9cda3431
WB
649#[cfg(target_os = "linux")]
650impl Device {
651 /// Get a `dev_t` value for this device.
652 #[rustfmt::skip]
653 pub fn to_dev_t(&self) -> u64 {
654 // see bits/sysmacros.h
655 ((self.major & 0x0000_0fff) << 8) |
656 ((self.major & 0xffff_f000) << 32) |
657 (self.minor & 0x0000_00ff) |
658 ((self.minor & 0xffff_ff00) << 12)
659 }
2fd112ac
WB
660
661 /// Get a `Device` from a `dev_t` value.
662 #[rustfmt::skip]
663 pub fn from_dev_t(dev: u64) -> Self {
664 // see to_dev_t
665 Self {
666 major: (dev >> 8) & 0x0000_0fff |
667 (dev >> 32) & 0xffff_f000,
668 minor: dev & 0x0000_00ff |
669 (dev >> 12) & 0xffff_ff00,
670 }
671 }
672}
673
674#[cfg(all(test, target_os = "linux"))]
675#[test]
676fn test_linux_devices() {
677 let c_dev = unsafe { ::libc::makedev(0xabcd_1234, 0xdcba_5678) };
678 let dev = Device::from_dev_t(c_dev);
679 assert_eq!(dev.to_dev_t(), c_dev);
9cda3431
WB
680}
681
6cd4f635 682#[derive(Clone, Debug)]
0104cf77 683#[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))]
6cd4f635
WB
684#[repr(C)]
685pub struct FCaps {
686 pub data: Vec<u8>,
687}
688
0104cf77 689#[derive(Clone, Copy, Debug, Endian, Eq, PartialEq)]
6cd4f635
WB
690#[repr(C)]
691pub struct QuotaProjectId {
692 pub projid: u64,
693}
694
e5a2495e
WB
695/// An entry in the "goodbye table" in a pxar archive. This is required for random access inside
696/// pxar archives.
70acf637 697#[derive(Clone, Debug, Endian)]
6cd4f635
WB
698#[repr(C)]
699pub struct GoodbyeItem {
700 /// SipHash24 of the directory item name. The last GOODBYE item uses the special hash value
701 /// `PXAR_GOODBYE_TAIL_MARKER`.
702 pub hash: u64,
703
704 /// The offset from the start of the GOODBYE object to the start of the matching directory item
705 /// (point to a FILENAME). The last GOODBYE item points to the start of the matching ENTRY
706 /// object.
707 pub offset: u64,
708
709 /// The overall size of the directory item. This includes the FILENAME header. In other words,
710 /// `goodbye_start - offset + size` points to the end of the directory.
711 ///
712 /// The last GOODBYE item repeats the size of the GOODBYE item.
713 pub size: u64,
714}
715
716impl GoodbyeItem {
e5a2495e
WB
717 /// Create a new [`GoodbyeItem`] by hashing the name, and storing the hash along with the
718 /// offset and size information.
6cd4f635
WB
719 pub fn new(name: &[u8], offset: u64, size: u64) -> Self {
720 let hash = hash_filename(name);
721 Self { hash, offset, size }
722 }
723}
724
e5a2495e 725/// Hash a file name for use in the goodbye table.
6cd4f635
WB
726pub fn hash_filename(name: &[u8]) -> u64 {
727 use std::hash::Hasher;
1b1e52a4
WB
728
729 let mut hasher = SipHasher24::new_with_keys(PXAR_HASH_KEY_1, PXAR_HASH_KEY_2);
6cd4f635
WB
730 hasher.write(name);
731 hasher.finish()
732}
733
e5a2495e
WB
734/// Returns `true` if the path consists only of [`Normal`](std::path::Component::Normal)
735/// components.
6cd4f635
WB
736pub fn path_is_legal_component(path: &Path) -> bool {
737 let mut components = path.components();
738 match components.next() {
739 Some(std::path::Component::Normal(_)) => (),
740 _ => return false,
741 }
742 components.next().is_none()
743}
744
e5a2495e
WB
745/// Assert sure the path consists only of [`Normal`](std::path::Component::Normal) components.
746///
747/// Returns an [`io::Error`](std::io::Error) of type [`Other`](std::io::ErrorKind::Other) if that's
748/// not the case.
6cd4f635
WB
749pub fn check_file_name(path: &Path) -> io::Result<()> {
750 if !path_is_legal_component(path) {
751 io_bail!("invalid file name in archive: {:?}", path);
752 } else {
753 Ok(())
754 }
755}