]> git.proxmox.com Git - pxar.git/blame - src/format/mod.rs
introduce StatxTimestamp helper type
[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
1b1e52a4
WB
81pub const PXAR_ENTRY: u64 = 0x11da850a1c1cceff;
82pub const PXAR_FILENAME: u64 = 0x16701121063917b3;
83pub const PXAR_SYMLINK: u64 = 0x27f971e7dbf5dc5f;
84pub const PXAR_DEVICE: u64 = 0x9fc9e906586d5ce9;
85pub const PXAR_XATTR: u64 = 0x0dab0229b57dcd03;
86pub const PXAR_ACL_USER: u64 = 0x2ce8540a457d55b8;
87pub const PXAR_ACL_GROUP: u64 = 0x136e3eceb04c03ab;
88pub const PXAR_ACL_GROUP_OBJ: u64 = 0x10868031e9582876;
89pub const PXAR_ACL_DEFAULT: u64 = 0xbbbb13415a6896f5;
90pub const PXAR_ACL_DEFAULT_USER: u64 = 0xc89357b40532cd1f;
91pub const PXAR_ACL_DEFAULT_GROUP: u64 = 0xf90a8a5816038ffe;
92pub const PXAR_FCAPS: u64 = 0x2da9dd9db5f7fb67;
93pub const PXAR_QUOTA_PROJID: u64 = 0xe07540e82f7d1cbb;
6cd4f635 94/// Marks item as hardlink
1b1e52a4 95pub const PXAR_HARDLINK: u64 = 0x51269c8422bd7275;
6cd4f635 96/// Marks the beginnig of the payload (actual content) of regular files
1b1e52a4 97pub const PXAR_PAYLOAD: u64 = 0x28147a1b0b7c1a25;
6cd4f635 98/// Marks item as entry of goodbye table
1b1e52a4 99pub const PXAR_GOODBYE: u64 = 0x2fec4fa642d5731d;
6cd4f635 100/// The end marker used in the GOODBYE object
1b1e52a4 101pub const PXAR_GOODBYE_TAIL_MARKER: u64 = 0xef5eed5b753e1555;
6cd4f635
WB
102
103#[derive(Debug, Endian)]
104#[repr(C)]
105pub struct Header {
106 /// The item type (see `PXAR_` constants).
107 pub htype: u64,
108 /// The size of the item, including the size of `Header`.
109 full_size: u64,
110}
111
112impl Header {
70acf637
WB
113 #[inline]
114 pub fn with_full_size(htype: u64, full_size: u64) -> Self {
115 Self { htype, full_size }
116 }
117
118 #[inline]
119 pub fn with_content_size(htype: u64, content_size: u64) -> Self {
120 Self::with_full_size(htype, content_size + size_of::<Header>() as u64)
121 }
122
6cd4f635
WB
123 #[inline]
124 pub fn full_size(&self) -> u64 {
125 self.full_size
126 }
127
128 #[inline]
129 pub fn content_size(&self) -> u64 {
130 self.full_size() - (size_of::<Self>() as u64)
131 }
ec0761f9
FG
132
133 #[inline]
134 pub fn max_content_size(&self) -> u64 {
135 match self.htype {
136 // + null-termination
137 PXAR_FILENAME => crate::util::MAX_FILENAME_LEN + 1,
138 // + null-termination
139 PXAR_SYMLINK => crate::util::MAX_PATH_LEN + 1,
140 // + null-termination + offset
141 PXAR_HARDLINK => crate::util::MAX_PATH_LEN + 1 + (size_of::<u64>() as u64),
142 PXAR_DEVICE => size_of::<Device>() as u64,
143 PXAR_XATTR | PXAR_FCAPS => crate::util::MAX_XATTR_LEN,
144 PXAR_ACL_USER | PXAR_ACL_DEFAULT_USER => size_of::<acl::User>() as u64,
145 PXAR_ACL_GROUP | PXAR_ACL_DEFAULT_GROUP => size_of::<acl::Group>() as u64,
146 PXAR_ACL_DEFAULT => size_of::<acl::Default>() as u64,
93fef2fd 147 PXAR_ACL_GROUP_OBJ => size_of::<acl::GroupObject>() as u64,
ec0761f9
FG
148 PXAR_QUOTA_PROJID => size_of::<QuotaProjectId>() as u64,
149 PXAR_ENTRY => size_of::<Entry>() as u64,
08bc9bbc
WB
150 PXAR_PAYLOAD | PXAR_GOODBYE => std::u64::MAX - (size_of::<Self>() as u64),
151 _ => std::u64::MAX - (size_of::<Self>() as u64),
ec0761f9
FG
152 }
153 }
154
155 #[inline]
156 pub fn check_header_size(&self) -> io::Result<()> {
157 if self.full_size() < size_of::<Header>() as u64 {
158 io_bail!("invalid header {} - too small ({})", self, self.full_size());
159 }
160
161 if self.content_size() > self.max_content_size() {
710e6c8b
WB
162 io_bail!(
163 "invalid content size ({} > {}) of entry with {}",
164 self.content_size(),
165 self.max_content_size(),
166 self
167 );
ec0761f9
FG
168 }
169 Ok(())
170 }
171}
4a13b8a3
FG
172
173impl Display for Header {
174 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175 let readable = match self.htype {
176 PXAR_FILENAME => "FILENAME",
177 PXAR_SYMLINK => "SYMLINK",
178 PXAR_HARDLINK => "HARDLINK",
179 PXAR_DEVICE => "DEVICE",
180 PXAR_XATTR => "XATTR",
181 PXAR_FCAPS => "FCAPS",
182 PXAR_ACL_USER => "ACL_USER",
183 PXAR_ACL_DEFAULT_USER => "ACL_DEFAULT_USER",
184 PXAR_ACL_GROUP => "ACL_GROUP",
185 PXAR_ACL_DEFAULT_GROUP => "ACL_DEFAULT_GROUP",
186 PXAR_ACL_DEFAULT => "ACL_DEFAULT",
187 PXAR_ACL_GROUP_OBJ => "ACL_GROUP_OBJ",
188 PXAR_QUOTA_PROJID => "QUOTA_PROJID",
189 PXAR_ENTRY => "ENTRY",
190 PXAR_PAYLOAD => "PAYLOAD",
191 PXAR_GOODBYE => "GOODBYE",
192 _ => "UNKNOWN",
193 };
194 write!(f, "{} header ({:x})", readable, self.htype)
195 }
6cd4f635
WB
196}
197
939f2468
WB
198#[derive(Clone, Debug, Eq, PartialEq)]
199pub enum SignedDuration {
200 Positive(Duration),
201 Negative(Duration),
202}
203
204#[derive(Clone, Debug, Default, Endian, Eq, PartialEq)]
205#[repr(C)]
206pub struct StatxTimestamp {
207 /// Seconds since the epoch (unix time).
208 pub secs: i64,
209
210 /// Nanoseconds since this struct's `secs`.
211 pub nanos: u32,
212}
213
214impl From<SystemTime> for StatxTimestamp {
215 fn from(time: SystemTime) -> Self {
216 match time.duration_since(SystemTime::UNIX_EPOCH) {
217 Ok(positive) => Self::from_duration_since_epoch(positive),
218 Err(negative) => Self::from_duration_before_epoch(negative.duration()),
219 }
220 }
221}
222
223impl StatxTimestamp {
224 /// `const` version of `Default`
225 pub const fn zero() -> Self {
226 Self { secs: 0, nanos: 0 }
227 }
228
229 /// Turn a positive duration relative to the unix epoch into a time stamp.
230 pub fn from_duration_since_epoch(duration: Duration) -> Self {
231 Self {
232 secs: duration.as_secs() as i64,
233 nanos: duration.subsec_nanos(),
234 }
235 }
236
237 /// Turn a *negative* duration from relative to the unix epoch into a time stamp.
238 pub fn from_duration_before_epoch(duration: Duration) -> Self {
239 match duration.subsec_nanos() {
240 0 => Self {
241 secs: duration.as_secs() as i64,
242 nanos: 0,
243 },
244 nanos => Self {
245 secs: -(duration.as_secs() as i64) - 1,
246 nanos: 1_000_000_000 - nanos,
247 },
248 }
249 }
250
251 /// Get the duration since the epoch. an `Ok` value is a positive duration, an `Err` value is a
252 /// negative duration.
253 pub fn to_duration(&self) -> SignedDuration {
254 if self.secs >= 0 {
255 SignedDuration::Positive(Duration::new(self.secs as u64, self.nanos))
256 } else {
257 SignedDuration::Negative(Duration::new(
258 -(self.secs + 1) as u64,
259 1_000_000_000 - self.nanos,
260 ))
261 }
262 }
263
264 /// Get a `std::time::SystemTime` from this time stamp.
265 pub fn system_time(&self) -> SystemTime {
266 match self.to_duration() {
267 SignedDuration::Positive(positive) => SystemTime::UNIX_EPOCH + positive,
268 SignedDuration::Negative(negative) => SystemTime::UNIX_EPOCH - negative,
269 }
270 }
271}
272
273#[test]
274fn test_statx_timestamp() {
275 const MAY_1_2015_1530: i64 = 1430487000;
276 let system_time = SystemTime::UNIX_EPOCH + Duration::new(MAY_1_2015_1530 as u64, 1_000_000);
277 let tx = StatxTimestamp::from(system_time);
278 assert_eq!(
279 tx,
280 StatxTimestamp {
281 secs: MAY_1_2015_1530,
282 nanos: 1_000_000,
283 }
284 );
285 assert_eq!(tx.system_time(), system_time);
286
287 const MAY_1_1960_1530: i64 = -305112600;
288 let system_time = SystemTime::UNIX_EPOCH - Duration::new(-MAY_1_1960_1530 as u64, 1_000_000);
289 let tx = StatxTimestamp::from(system_time);
290 assert_eq!(
291 tx,
292 StatxTimestamp {
293 secs: MAY_1_1960_1530 - 1,
294 nanos: 999_000_000,
295 }
296 );
297 assert_eq!(tx.system_time(), system_time);
298}
299
6cd4f635 300#[derive(Clone, Debug, Default, Endian)]
0104cf77 301#[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))]
6cd4f635
WB
302#[repr(C)]
303pub struct Entry {
304 pub mode: u64,
305 pub flags: u64,
306 pub uid: u32,
307 pub gid: u32,
308 pub mtime: u64,
309}
310
70acf637
WB
311/// Builder pattern methods.
312impl Entry {
313 pub const fn mode(self, mode: u64) -> Self {
314 Self { mode, ..self }
315 }
316
317 pub const fn flags(self, flags: u64) -> Self {
318 Self { flags, ..self }
319 }
320
321 pub const fn uid(self, uid: u32) -> Self {
322 Self { uid, ..self }
323 }
324
325 pub const fn gid(self, gid: u32) -> Self {
326 Self { gid, ..self }
327 }
328
329 pub const fn mtime(self, mtime: u64) -> Self {
330 Self { mtime, ..self }
331 }
332
333 pub const fn set_dir(self) -> Self {
334 let mode = self.mode;
335 self.mode((mode & !mode::IFMT) | mode::IFDIR)
336 }
337
338 pub const fn set_regular_file(self) -> Self {
339 let mode = self.mode;
340 self.mode((mode & !mode::IFMT) | mode::IFREG)
341 }
342
343 pub const fn set_symlink(self) -> Self {
344 let mode = self.mode;
345 self.mode((mode & !mode::IFMT) | mode::IFLNK)
346 }
347
348 pub const fn set_blockdev(self) -> Self {
349 let mode = self.mode;
350 self.mode((mode & !mode::IFMT) | mode::IFBLK)
351 }
352
353 pub const fn set_chardev(self) -> Self {
354 let mode = self.mode;
355 self.mode((mode & !mode::IFMT) | mode::IFCHR)
356 }
357
358 pub const fn set_fifo(self) -> Self {
359 let mode = self.mode;
360 self.mode((mode & !mode::IFMT) | mode::IFIFO)
361 }
362}
363
6da548dd
WB
364/// Convenience accessor methods.
365impl Entry {
366 /// Get the mtime as duration since the epoch.
367 pub fn mtime_as_duration(&self) -> std::time::Duration {
368 std::time::Duration::from_nanos(self.mtime)
369 }
8617cae3
WB
370
371 /// Get the file type portion of the mode bitfield.
372 pub fn get_file_bits(&self) -> u64 {
373 self.mode & mode::IFMT
374 }
375
376 /// Get the permission portion of the mode bitfield.
377 pub fn get_permission_bits(&self) -> u64 {
378 self.mode & !mode::IFMT
379 }
6da548dd
WB
380}
381
70acf637
WB
382/// Convenience methods.
383impl Entry {
21d66e10
WB
384 /// Get the file type (`mode & mode::IFMT`).
385 pub fn file_type(&self) -> u64 {
386 self.mode & mode::IFMT
387 }
388
389 /// Get the file mode bits (`mode & !mode::IFMT`).
390 pub fn file_mode(&self) -> u64 {
391 self.mode & !mode::IFMT
392 }
393
70acf637
WB
394 /// Check whether this is a directory.
395 pub fn is_dir(&self) -> bool {
396 (self.mode & mode::IFMT) == mode::IFDIR
397 }
398
399 /// Check whether this is a symbolic link.
400 pub fn is_symlink(&self) -> bool {
401 (self.mode & mode::IFMT) == mode::IFLNK
402 }
403
404 /// Check whether this is a device node.
405 pub fn is_device(&self) -> bool {
406 let fmt = self.mode & mode::IFMT;
407 fmt == mode::IFCHR || fmt == mode::IFBLK
408 }
409
da286c28
WB
410 /// Check whether this is a block device node.
411 pub fn is_blockdev(&self) -> bool {
412 let fmt = self.mode & mode::IFMT;
413 fmt == mode::IFBLK
414 }
415
416 /// Check whether this is a character device node.
417 pub fn is_chardev(&self) -> bool {
418 let fmt = self.mode & mode::IFMT;
419 fmt == mode::IFCHR
420 }
421
70acf637
WB
422 /// Check whether this is a regular file.
423 pub fn is_regular_file(&self) -> bool {
424 (self.mode & mode::IFMT) == mode::IFREG
425 }
2287d8b2
WB
426
427 /// Check whether this is a named pipe (FIFO).
428 pub fn is_fifo(&self) -> bool {
429 (self.mode & mode::IFMT) == mode::IFIFO
430 }
431
432 /// Check whether this is a named socket.
433 pub fn is_socket(&self) -> bool {
434 (self.mode & mode::IFMT) == mode::IFSOCK
435 }
70acf637
WB
436}
437
438impl From<&std::fs::Metadata> for Entry {
439 fn from(meta: &std::fs::Metadata) -> Entry {
440 #[cfg(unix)]
441 use std::os::unix::fs::MetadataExt;
442
443 let this = Entry::default();
444
445 #[cfg(unix)]
446 let this = this
447 .uid(meta.uid())
448 .gid(meta.gid())
0a6c7350
WB
449 .mode(meta.mode() as u64);
450
451 let this = match meta.modified() {
1250e3ea
WB
452 Ok(mtime) => this.mtime(
453 mtime
0a6c7350
WB
454 .duration_since(std::time::SystemTime::UNIX_EPOCH)
455 .map(|dur| dur.as_nanos() as u64)
1250e3ea
WB
456 .unwrap_or(0u64),
457 ),
0a6c7350
WB
458 Err(_) => this,
459 };
70acf637
WB
460
461 let file_type = meta.file_type();
462 let mode = this.mode;
1b25fc08 463 if file_type.is_dir() {
70acf637
WB
464 this.mode(mode | mode::IFDIR)
465 } else if file_type.is_symlink() {
466 this.mode(mode | mode::IFLNK)
467 } else {
468 this.mode(mode | mode::IFREG)
1b25fc08 469 }
70acf637
WB
470 }
471}
472
6cd4f635
WB
473#[derive(Clone, Debug)]
474pub struct Filename {
475 pub name: Vec<u8>,
476}
477
478#[derive(Clone, Debug)]
479pub struct Symlink {
480 pub data: Vec<u8>,
481}
482
7b389f88
WB
483impl Symlink {
484 pub fn as_os_str(&self) -> &OsStr {
485 self.as_ref()
486 }
487}
488
353bbf73
WB
489impl AsRef<[u8]> for Symlink {
490 fn as_ref(&self) -> &[u8] {
491 &self.data
492 }
493}
494
54912ae6
WB
495impl AsRef<OsStr> for Symlink {
496 fn as_ref(&self) -> &OsStr {
353bbf73 497 OsStr::from_bytes(&self.data[..self.data.len().max(1) - 1])
54912ae6
WB
498 }
499}
500
6cd4f635
WB
501#[derive(Clone, Debug)]
502pub struct Hardlink {
b0752929 503 pub offset: u64,
6cd4f635
WB
504 pub data: Vec<u8>,
505}
506
7b389f88
WB
507impl Hardlink {
508 pub fn as_os_str(&self) -> &OsStr {
509 self.as_ref()
510 }
511}
512
353bbf73
WB
513impl AsRef<[u8]> for Hardlink {
514 fn as_ref(&self) -> &[u8] {
515 &self.data
516 }
517}
518
54912ae6
WB
519impl AsRef<OsStr> for Hardlink {
520 fn as_ref(&self) -> &OsStr {
353bbf73 521 OsStr::from_bytes(&self.data[..self.data.len().max(1) - 1])
54912ae6
WB
522 }
523}
524
6cd4f635
WB
525#[derive(Clone, Debug, Eq)]
526#[repr(C)]
527pub struct XAttr {
528 pub(crate) data: Vec<u8>,
529 pub(crate) name_len: usize,
530}
531
532impl XAttr {
533 pub fn new<N: AsRef<[u8]>, V: AsRef<[u8]>>(name: N, value: V) -> Self {
534 let name = name.as_ref();
535 let value = value.as_ref();
536 let mut data = Vec::with_capacity(name.len() + value.len() + 1);
537 data.extend(name);
538 data.push(0);
539 data.extend(value);
540 Self {
541 data,
542 name_len: name.len(),
543 }
544 }
545
2c442007 546 pub fn name(&self) -> &CStr {
2fd112ac 547 unsafe { CStr::from_bytes_with_nul_unchecked(&self.data[..self.name_len + 1]) }
6cd4f635
WB
548 }
549
550 pub fn value(&self) -> &[u8] {
551 &self.data[(self.name_len + 1)..]
552 }
553}
554
555impl Ord for XAttr {
556 fn cmp(&self, other: &XAttr) -> Ordering {
557 self.name().cmp(&other.name())
558 }
559}
560
561impl PartialOrd for XAttr {
562 fn partial_cmp(&self, other: &XAttr) -> Option<Ordering> {
563 Some(self.cmp(other))
564 }
565}
566
567impl PartialEq for XAttr {
568 fn eq(&self, other: &XAttr) -> bool {
569 self.name() == other.name()
570 }
571}
572
0104cf77 573#[derive(Clone, Debug, Endian, Eq, PartialEq)]
6cd4f635
WB
574#[repr(C)]
575pub struct Device {
576 pub major: u64,
577 pub minor: u64,
578}
579
9cda3431
WB
580#[cfg(target_os = "linux")]
581impl Device {
582 /// Get a `dev_t` value for this device.
583 #[rustfmt::skip]
584 pub fn to_dev_t(&self) -> u64 {
585 // see bits/sysmacros.h
586 ((self.major & 0x0000_0fff) << 8) |
587 ((self.major & 0xffff_f000) << 32) |
588 (self.minor & 0x0000_00ff) |
589 ((self.minor & 0xffff_ff00) << 12)
590 }
2fd112ac
WB
591
592 /// Get a `Device` from a `dev_t` value.
593 #[rustfmt::skip]
594 pub fn from_dev_t(dev: u64) -> Self {
595 // see to_dev_t
596 Self {
597 major: (dev >> 8) & 0x0000_0fff |
598 (dev >> 32) & 0xffff_f000,
599 minor: dev & 0x0000_00ff |
600 (dev >> 12) & 0xffff_ff00,
601 }
602 }
603}
604
605#[cfg(all(test, target_os = "linux"))]
606#[test]
607fn test_linux_devices() {
608 let c_dev = unsafe { ::libc::makedev(0xabcd_1234, 0xdcba_5678) };
609 let dev = Device::from_dev_t(c_dev);
610 assert_eq!(dev.to_dev_t(), c_dev);
9cda3431
WB
611}
612
6cd4f635 613#[derive(Clone, Debug)]
0104cf77 614#[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))]
6cd4f635
WB
615#[repr(C)]
616pub struct FCaps {
617 pub data: Vec<u8>,
618}
619
0104cf77 620#[derive(Clone, Copy, Debug, Endian, Eq, PartialEq)]
6cd4f635
WB
621#[repr(C)]
622pub struct QuotaProjectId {
623 pub projid: u64,
624}
625
70acf637 626#[derive(Clone, Debug, Endian)]
6cd4f635
WB
627#[repr(C)]
628pub struct GoodbyeItem {
629 /// SipHash24 of the directory item name. The last GOODBYE item uses the special hash value
630 /// `PXAR_GOODBYE_TAIL_MARKER`.
631 pub hash: u64,
632
633 /// The offset from the start of the GOODBYE object to the start of the matching directory item
634 /// (point to a FILENAME). The last GOODBYE item points to the start of the matching ENTRY
635 /// object.
636 pub offset: u64,
637
638 /// The overall size of the directory item. This includes the FILENAME header. In other words,
639 /// `goodbye_start - offset + size` points to the end of the directory.
640 ///
641 /// The last GOODBYE item repeats the size of the GOODBYE item.
642 pub size: u64,
643}
644
645impl GoodbyeItem {
646 pub fn new(name: &[u8], offset: u64, size: u64) -> Self {
647 let hash = hash_filename(name);
648 Self { hash, offset, size }
649 }
650}
651
652pub fn hash_filename(name: &[u8]) -> u64 {
653 use std::hash::Hasher;
1b1e52a4
WB
654
655 let mut hasher = SipHasher24::new_with_keys(PXAR_HASH_KEY_1, PXAR_HASH_KEY_2);
6cd4f635
WB
656 hasher.write(name);
657 hasher.finish()
658}
659
6cd4f635
WB
660pub fn path_is_legal_component(path: &Path) -> bool {
661 let mut components = path.components();
662 match components.next() {
663 Some(std::path::Component::Normal(_)) => (),
664 _ => return false,
665 }
666 components.next().is_none()
667}
668
669pub fn check_file_name(path: &Path) -> io::Result<()> {
670 if !path_is_legal_component(path) {
671 io_bail!("invalid file name in archive: {:?}", path);
672 } else {
673 Ok(())
674 }
675}