]> git.proxmox.com Git - pxar.git/blob - src/format/mod.rs
introduce StatxTimestamp helper type
[pxar.git] / src / format / mod.rs
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.
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
37
38 use std::cmp::Ordering;
39 use std::ffi::{CStr, OsStr};
40 use std::fmt;
41 use std::fmt::Display;
42 use std::io;
43 use std::mem::size_of;
44 use std::os::unix::ffi::OsStrExt;
45 use std::path::Path;
46 use std::time::{Duration, SystemTime};
47
48 use endian_trait::Endian;
49 use siphasher::sip::SipHasher24;
50
51 pub mod acl;
52
53 // generated with:
54 // $ echo -n 'PROXMOX ARCHIVE FORMAT' | sha1sum | sed -re 's/^(.{16})(.{16}).*$/0x\1, 0x\2/'
55 pub const PXAR_HASH_KEY_1: u64 = 0x83ac3f1cfbb450db;
56 pub const PXAR_HASH_KEY_2: u64 = 0xaa4f1b6879369fbd;
57
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]
65 pub 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;
75
76 pub const ISUID : u64 = 0o0004000;
77 pub const ISGID : u64 = 0o0002000;
78 pub const ISVTX : u64 = 0o0001000;
79 }
80
81 /// Beginning of an entry (current version).
82 pub const PXAR_ENTRY: u64 = 0xd5956474e588acef;
83 /// Previous version of the entry struct
84 pub const PXAR_ENTRY_V1: u64 = 0x11da850a1c1cceff;
85 pub const PXAR_FILENAME: u64 = 0x16701121063917b3;
86 pub const PXAR_SYMLINK: u64 = 0x27f971e7dbf5dc5f;
87 pub const PXAR_DEVICE: u64 = 0x9fc9e906586d5ce9;
88 pub const PXAR_XATTR: u64 = 0x0dab0229b57dcd03;
89 pub const PXAR_ACL_USER: u64 = 0x2ce8540a457d55b8;
90 pub const PXAR_ACL_GROUP: u64 = 0x136e3eceb04c03ab;
91 pub const PXAR_ACL_GROUP_OBJ: u64 = 0x10868031e9582876;
92 pub const PXAR_ACL_DEFAULT: u64 = 0xbbbb13415a6896f5;
93 pub const PXAR_ACL_DEFAULT_USER: u64 = 0xc89357b40532cd1f;
94 pub const PXAR_ACL_DEFAULT_GROUP: u64 = 0xf90a8a5816038ffe;
95 pub const PXAR_FCAPS: u64 = 0x2da9dd9db5f7fb67;
96 pub const PXAR_QUOTA_PROJID: u64 = 0xe07540e82f7d1cbb;
97 /// Marks item as hardlink
98 pub const PXAR_HARDLINK: u64 = 0x51269c8422bd7275;
99 /// Marks the beginnig of the payload (actual content) of regular files
100 pub const PXAR_PAYLOAD: u64 = 0x28147a1b0b7c1a25;
101 /// Marks item as entry of goodbye table
102 pub const PXAR_GOODBYE: u64 = 0x2fec4fa642d5731d;
103 /// The end marker used in the GOODBYE object
104 pub const PXAR_GOODBYE_TAIL_MARKER: u64 = 0xef5eed5b753e1555;
105
106 #[derive(Debug, Endian)]
107 #[repr(C)]
108 pub 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
115 impl Header {
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
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 }
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,
150 PXAR_ACL_GROUP_OBJ => size_of::<acl::GroupObject>() as u64,
151 PXAR_QUOTA_PROJID => size_of::<QuotaProjectId>() as u64,
152 PXAR_ENTRY => size_of::<Entry>() as u64,
153 PXAR_PAYLOAD | PXAR_GOODBYE => std::u64::MAX - (size_of::<Self>() as u64),
154 _ => std::u64::MAX - (size_of::<Self>() as u64),
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() {
165 io_bail!(
166 "invalid content size ({} > {}) of entry with {}",
167 self.content_size(),
168 self.max_content_size(),
169 self
170 );
171 }
172 Ok(())
173 }
174 }
175
176 impl 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 }
199 }
200
201 #[derive(Clone, Debug, Eq, PartialEq)]
202 pub enum SignedDuration {
203 Positive(Duration),
204 Negative(Duration),
205 }
206
207 #[derive(Clone, Debug, Default, Endian, Eq, PartialEq)]
208 #[repr(C)]
209 pub 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,
215 }
216
217 impl From<SystemTime> for StatxTimestamp {
218 fn from(time: SystemTime) -> Self {
219 match time.duration_since(SystemTime::UNIX_EPOCH) {
220 Ok(positive) => Self::from_duration_since_epoch(positive),
221 Err(negative) => Self::from_duration_before_epoch(negative.duration()),
222 }
223 }
224 }
225
226 impl StatxTimestamp {
227 /// `const` version of `Default`
228 pub const fn zero() -> Self {
229 Self { secs: 0, nanos: 0 }
230 }
231
232 /// Turn a positive duration relative to the unix epoch into a time stamp.
233 pub fn from_duration_since_epoch(duration: Duration) -> Self {
234 Self {
235 secs: duration.as_secs() as i64,
236 nanos: duration.subsec_nanos(),
237 }
238 }
239
240 /// Turn a *negative* duration from relative to the unix epoch into a time stamp.
241 pub fn from_duration_before_epoch(duration: Duration) -> Self {
242 match duration.subsec_nanos() {
243 0 => Self {
244 secs: -(duration.as_secs() as i64),
245 nanos: 0,
246 },
247 nanos => Self {
248 secs: -(duration.as_secs() as i64) - 1,
249 nanos: 1_000_000_000 - nanos,
250 },
251 }
252 }
253
254 /// Get the duration since the epoch. an `Ok` value is a positive duration, an `Err` value is a
255 /// negative duration.
256 pub fn to_duration(&self) -> SignedDuration {
257 if self.secs >= 0 {
258 SignedDuration::Positive(Duration::new(self.secs as u64, self.nanos))
259 } else {
260 // this handles the nanos=0 case as `Duration::new()` performs the carry-over.
261 SignedDuration::Negative(Duration::new(
262 -(self.secs + 1) as u64,
263 1_000_000_000 - self.nanos,
264 ))
265 }
266 }
267
268 /// Get a `std::time::SystemTime` from this time stamp.
269 pub fn system_time(&self) -> SystemTime {
270 match self.to_duration() {
271 SignedDuration::Positive(positive) => SystemTime::UNIX_EPOCH + positive,
272 SignedDuration::Negative(negative) => SystemTime::UNIX_EPOCH - negative,
273 }
274 }
275 }
276
277 #[test]
278 fn test_statx_timestamp() {
279 const MAY_1_2015_1530: i64 = 1430487000;
280 let system_time = SystemTime::UNIX_EPOCH + Duration::new(MAY_1_2015_1530 as u64, 1_000_000);
281 let tx = StatxTimestamp::from(system_time);
282 assert_eq!(
283 tx,
284 StatxTimestamp {
285 secs: MAY_1_2015_1530,
286 nanos: 1_000_000,
287 }
288 );
289 assert_eq!(tx.system_time(), system_time);
290
291 const MAY_1_1960_1530: i64 = -305112600;
292 let system_time = SystemTime::UNIX_EPOCH - Duration::new(-MAY_1_1960_1530 as u64, 1_000_000);
293 let tx = StatxTimestamp::from(system_time);
294 assert_eq!(
295 tx,
296 StatxTimestamp {
297 secs: MAY_1_1960_1530 - 1,
298 nanos: 999_000_000,
299 }
300 );
301 assert_eq!(tx.system_time(), system_time);
302
303 let system_time = SystemTime::UNIX_EPOCH - Duration::new(-MAY_1_1960_1530 as u64, 0);
304 let tx = StatxTimestamp::from(system_time);
305 assert_eq!(
306 tx,
307 StatxTimestamp {
308 secs: MAY_1_1960_1530,
309 nanos: 0,
310 }
311 );
312 assert_eq!(tx.system_time(), system_time);
313 }
314 #[derive(Clone, Debug, Default, Endian)]
315 #[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))]
316 #[repr(C)]
317 pub struct Entry_V1 {
318 pub mode: u64,
319 pub flags: u64,
320 pub uid: u32,
321 pub gid: u32,
322 pub mtime: u64,
323 }
324
325 impl Into<Entry> for Entry_V1 {
326 fn into(self) -> Entry {
327 Entry {
328 mode: self.mode,
329 flags: self.flags,
330 uid: self.uid,
331 gid: self.gid,
332 mtime: StatxTimestamp::from_duration_since_epoch(Duration::from_nanos(self.mtime)),
333 }
334 }
335 }
336
337 #[derive(Clone, Debug, Default, Endian)]
338 #[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))]
339 #[repr(C)]
340 pub struct Entry {
341 pub mode: u64,
342 pub flags: u64,
343 pub uid: u32,
344 pub gid: u32,
345 pub mtime: StatxTimestamp,
346 }
347
348 /// Builder pattern methods.
349 impl Entry {
350 pub const fn mode(self, mode: u64) -> Self {
351 Self { mode, ..self }
352 }
353
354 pub const fn flags(self, flags: u64) -> Self {
355 Self { flags, ..self }
356 }
357
358 pub const fn uid(self, uid: u32) -> Self {
359 Self { uid, ..self }
360 }
361
362 pub const fn gid(self, gid: u32) -> Self {
363 Self { gid, ..self }
364 }
365
366 pub const fn mtime(self, mtime: StatxTimestamp) -> Self {
367 Self { mtime, ..self }
368 }
369
370 pub const fn set_dir(self) -> Self {
371 let mode = self.mode;
372 self.mode((mode & !mode::IFMT) | mode::IFDIR)
373 }
374
375 pub const fn set_regular_file(self) -> Self {
376 let mode = self.mode;
377 self.mode((mode & !mode::IFMT) | mode::IFREG)
378 }
379
380 pub const fn set_symlink(self) -> Self {
381 let mode = self.mode;
382 self.mode((mode & !mode::IFMT) | mode::IFLNK)
383 }
384
385 pub const fn set_blockdev(self) -> Self {
386 let mode = self.mode;
387 self.mode((mode & !mode::IFMT) | mode::IFBLK)
388 }
389
390 pub const fn set_chardev(self) -> Self {
391 let mode = self.mode;
392 self.mode((mode & !mode::IFMT) | mode::IFCHR)
393 }
394
395 pub const fn set_fifo(self) -> Self {
396 let mode = self.mode;
397 self.mode((mode & !mode::IFMT) | mode::IFIFO)
398 }
399 }
400
401 /// Convenience accessor methods.
402 impl Entry {
403 /// Get the mtime as duration since the epoch. an `Ok` value is a positive duration, an `Err`
404 /// value is a negative duration.
405 pub fn mtime_as_duration(&self) -> SignedDuration {
406 self.mtime.to_duration()
407 }
408
409 /// Get the file type portion of the mode bitfield.
410 pub fn get_file_bits(&self) -> u64 {
411 self.mode & mode::IFMT
412 }
413
414 /// Get the permission portion of the mode bitfield.
415 pub fn get_permission_bits(&self) -> u64 {
416 self.mode & !mode::IFMT
417 }
418 }
419
420 /// Convenience methods.
421 impl Entry {
422 /// Get the file type (`mode & mode::IFMT`).
423 pub fn file_type(&self) -> u64 {
424 self.mode & mode::IFMT
425 }
426
427 /// Get the file mode bits (`mode & !mode::IFMT`).
428 pub fn file_mode(&self) -> u64 {
429 self.mode & !mode::IFMT
430 }
431
432 /// Check whether this is a directory.
433 pub fn is_dir(&self) -> bool {
434 (self.mode & mode::IFMT) == mode::IFDIR
435 }
436
437 /// Check whether this is a symbolic link.
438 pub fn is_symlink(&self) -> bool {
439 (self.mode & mode::IFMT) == mode::IFLNK
440 }
441
442 /// Check whether this is a device node.
443 pub fn is_device(&self) -> bool {
444 let fmt = self.mode & mode::IFMT;
445 fmt == mode::IFCHR || fmt == mode::IFBLK
446 }
447
448 /// Check whether this is a block device node.
449 pub fn is_blockdev(&self) -> bool {
450 let fmt = self.mode & mode::IFMT;
451 fmt == mode::IFBLK
452 }
453
454 /// Check whether this is a character device node.
455 pub fn is_chardev(&self) -> bool {
456 let fmt = self.mode & mode::IFMT;
457 fmt == mode::IFCHR
458 }
459
460 /// Check whether this is a regular file.
461 pub fn is_regular_file(&self) -> bool {
462 (self.mode & mode::IFMT) == mode::IFREG
463 }
464
465 /// Check whether this is a named pipe (FIFO).
466 pub fn is_fifo(&self) -> bool {
467 (self.mode & mode::IFMT) == mode::IFIFO
468 }
469
470 /// Check whether this is a named socket.
471 pub fn is_socket(&self) -> bool {
472 (self.mode & mode::IFMT) == mode::IFSOCK
473 }
474 }
475
476 impl From<&std::fs::Metadata> for Entry {
477 fn from(meta: &std::fs::Metadata) -> Entry {
478 #[cfg(unix)]
479 use std::os::unix::fs::MetadataExt;
480
481 let this = Entry::default();
482
483 #[cfg(unix)]
484 let this = this
485 .uid(meta.uid())
486 .gid(meta.gid())
487 .mode(meta.mode() as u64);
488
489 let this = match meta.modified() {
490 Ok(mtime) => this.mtime(mtime.into()),
491 Err(_) => this,
492 };
493
494 let file_type = meta.file_type();
495 let mode = this.mode;
496 if file_type.is_dir() {
497 this.mode(mode | mode::IFDIR)
498 } else if file_type.is_symlink() {
499 this.mode(mode | mode::IFLNK)
500 } else {
501 this.mode(mode | mode::IFREG)
502 }
503 }
504 }
505
506 #[derive(Clone, Debug)]
507 pub struct Filename {
508 pub name: Vec<u8>,
509 }
510
511 #[derive(Clone, Debug)]
512 pub struct Symlink {
513 pub data: Vec<u8>,
514 }
515
516 impl Symlink {
517 pub fn as_os_str(&self) -> &OsStr {
518 self.as_ref()
519 }
520 }
521
522 impl AsRef<[u8]> for Symlink {
523 fn as_ref(&self) -> &[u8] {
524 &self.data
525 }
526 }
527
528 impl AsRef<OsStr> for Symlink {
529 fn as_ref(&self) -> &OsStr {
530 OsStr::from_bytes(&self.data[..self.data.len().max(1) - 1])
531 }
532 }
533
534 #[derive(Clone, Debug)]
535 pub struct Hardlink {
536 pub offset: u64,
537 pub data: Vec<u8>,
538 }
539
540 impl Hardlink {
541 pub fn as_os_str(&self) -> &OsStr {
542 self.as_ref()
543 }
544 }
545
546 impl AsRef<[u8]> for Hardlink {
547 fn as_ref(&self) -> &[u8] {
548 &self.data
549 }
550 }
551
552 impl AsRef<OsStr> for Hardlink {
553 fn as_ref(&self) -> &OsStr {
554 OsStr::from_bytes(&self.data[..self.data.len().max(1) - 1])
555 }
556 }
557
558 #[derive(Clone, Debug, Eq)]
559 #[repr(C)]
560 pub struct XAttr {
561 pub(crate) data: Vec<u8>,
562 pub(crate) name_len: usize,
563 }
564
565 impl XAttr {
566 pub fn new<N: AsRef<[u8]>, V: AsRef<[u8]>>(name: N, value: V) -> Self {
567 let name = name.as_ref();
568 let value = value.as_ref();
569 let mut data = Vec::with_capacity(name.len() + value.len() + 1);
570 data.extend(name);
571 data.push(0);
572 data.extend(value);
573 Self {
574 data,
575 name_len: name.len(),
576 }
577 }
578
579 pub fn name(&self) -> &CStr {
580 unsafe { CStr::from_bytes_with_nul_unchecked(&self.data[..self.name_len + 1]) }
581 }
582
583 pub fn value(&self) -> &[u8] {
584 &self.data[(self.name_len + 1)..]
585 }
586 }
587
588 impl Ord for XAttr {
589 fn cmp(&self, other: &XAttr) -> Ordering {
590 self.name().cmp(&other.name())
591 }
592 }
593
594 impl PartialOrd for XAttr {
595 fn partial_cmp(&self, other: &XAttr) -> Option<Ordering> {
596 Some(self.cmp(other))
597 }
598 }
599
600 impl PartialEq for XAttr {
601 fn eq(&self, other: &XAttr) -> bool {
602 self.name() == other.name()
603 }
604 }
605
606 #[derive(Clone, Debug, Endian, Eq, PartialEq)]
607 #[repr(C)]
608 pub struct Device {
609 pub major: u64,
610 pub minor: u64,
611 }
612
613 #[cfg(target_os = "linux")]
614 impl Device {
615 /// Get a `dev_t` value for this device.
616 #[rustfmt::skip]
617 pub fn to_dev_t(&self) -> u64 {
618 // see bits/sysmacros.h
619 ((self.major & 0x0000_0fff) << 8) |
620 ((self.major & 0xffff_f000) << 32) |
621 (self.minor & 0x0000_00ff) |
622 ((self.minor & 0xffff_ff00) << 12)
623 }
624
625 /// Get a `Device` from a `dev_t` value.
626 #[rustfmt::skip]
627 pub fn from_dev_t(dev: u64) -> Self {
628 // see to_dev_t
629 Self {
630 major: (dev >> 8) & 0x0000_0fff |
631 (dev >> 32) & 0xffff_f000,
632 minor: dev & 0x0000_00ff |
633 (dev >> 12) & 0xffff_ff00,
634 }
635 }
636 }
637
638 #[cfg(all(test, target_os = "linux"))]
639 #[test]
640 fn test_linux_devices() {
641 let c_dev = unsafe { ::libc::makedev(0xabcd_1234, 0xdcba_5678) };
642 let dev = Device::from_dev_t(c_dev);
643 assert_eq!(dev.to_dev_t(), c_dev);
644 }
645
646 #[derive(Clone, Debug)]
647 #[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))]
648 #[repr(C)]
649 pub struct FCaps {
650 pub data: Vec<u8>,
651 }
652
653 #[derive(Clone, Copy, Debug, Endian, Eq, PartialEq)]
654 #[repr(C)]
655 pub struct QuotaProjectId {
656 pub projid: u64,
657 }
658
659 #[derive(Clone, Debug, Endian)]
660 #[repr(C)]
661 pub struct GoodbyeItem {
662 /// SipHash24 of the directory item name. The last GOODBYE item uses the special hash value
663 /// `PXAR_GOODBYE_TAIL_MARKER`.
664 pub hash: u64,
665
666 /// The offset from the start of the GOODBYE object to the start of the matching directory item
667 /// (point to a FILENAME). The last GOODBYE item points to the start of the matching ENTRY
668 /// object.
669 pub offset: u64,
670
671 /// The overall size of the directory item. This includes the FILENAME header. In other words,
672 /// `goodbye_start - offset + size` points to the end of the directory.
673 ///
674 /// The last GOODBYE item repeats the size of the GOODBYE item.
675 pub size: u64,
676 }
677
678 impl GoodbyeItem {
679 pub fn new(name: &[u8], offset: u64, size: u64) -> Self {
680 let hash = hash_filename(name);
681 Self { hash, offset, size }
682 }
683 }
684
685 pub fn hash_filename(name: &[u8]) -> u64 {
686 use std::hash::Hasher;
687
688 let mut hasher = SipHasher24::new_with_keys(PXAR_HASH_KEY_1, PXAR_HASH_KEY_2);
689 hasher.write(name);
690 hasher.finish()
691 }
692
693 pub fn path_is_legal_component(path: &Path) -> bool {
694 let mut components = path.components();
695 match components.next() {
696 Some(std::path::Component::Normal(_)) => (),
697 _ => return false,
698 }
699 components.next().is_none()
700 }
701
702 pub fn check_file_name(path: &Path) -> io::Result<()> {
703 if !path_is_legal_component(path) {
704 io_bail!("invalid file name in archive: {:?}", path);
705 } else {
706 Ok(())
707 }
708 }