1 //! *pxar* binary format definition
3 //! Please note the all values are stored in little endian ordering.
5 //! The Archive contains a list of items. Each item starts with a `Header`, followed by the
8 use std
::cmp
::Ordering
;
9 use std
::ffi
::{CStr, OsStr}
;
11 use std
::fmt
::Display
;
13 use std
::mem
::size_of
;
14 use std
::os
::unix
::ffi
::OsStrExt
;
17 use endian_trait
::Endian
;
18 use siphasher
::sip
::SipHasher24
;
23 // $ echo -n 'PROXMOX ARCHIVE FORMAT' | sha1sum | sed -re 's/^(.{16})(.{16}).*$/0x\1, 0x\2/'
24 pub const PXAR_HASH_KEY_1
: u64 = 0x83ac3f1cfbb450db;
25 pub const PXAR_HASH_KEY_2
: u64 = 0xaa4f1b6879369fbd;
27 /// While these constants correspond to `libc::S_` constants, we need these to be fixed for the
28 /// format itself, so we redefine them here.
30 /// Additionally this gets rid of a bunch of casts between u32 and u64.
32 /// You can usually find the values for these in `/usr/include/linux/stat.h`.
35 pub const IFMT
: u64 = 0o0170000;
37 pub const IFSOCK
: u64 = 0o0140000;
38 pub const IFLNK
: u64 = 0o0120000;
39 pub const IFREG
: u64 = 0o0100000;
40 pub const IFBLK
: u64 = 0o0060000;
41 pub const IFDIR
: u64 = 0o0040000;
42 pub const IFCHR
: u64 = 0o0020000;
43 pub const IFIFO
: u64 = 0o0010000;
45 pub const ISUID
: u64 = 0o0004000;
46 pub const ISGID
: u64 = 0o0002000;
47 pub const ISVTX
: u64 = 0o0001000;
50 pub const PXAR_ENTRY
: u64 = 0x11da850a1c1cceff;
51 pub const PXAR_FILENAME
: u64 = 0x16701121063917b3;
52 pub const PXAR_SYMLINK
: u64 = 0x27f971e7dbf5dc5f;
53 pub const PXAR_DEVICE
: u64 = 0x9fc9e906586d5ce9;
54 pub const PXAR_XATTR
: u64 = 0x0dab0229b57dcd03;
55 pub const PXAR_ACL_USER
: u64 = 0x2ce8540a457d55b8;
56 pub const PXAR_ACL_GROUP
: u64 = 0x136e3eceb04c03ab;
57 pub const PXAR_ACL_GROUP_OBJ
: u64 = 0x10868031e9582876;
58 pub const PXAR_ACL_DEFAULT
: u64 = 0xbbbb13415a6896f5;
59 pub const PXAR_ACL_DEFAULT_USER
: u64 = 0xc89357b40532cd1f;
60 pub const PXAR_ACL_DEFAULT_GROUP
: u64 = 0xf90a8a5816038ffe;
61 pub const PXAR_FCAPS
: u64 = 0x2da9dd9db5f7fb67;
62 pub const PXAR_QUOTA_PROJID
: u64 = 0xe07540e82f7d1cbb;
63 /// Marks item as hardlink
64 pub const PXAR_HARDLINK
: u64 = 0x51269c8422bd7275;
65 /// Marks the beginnig of the payload (actual content) of regular files
66 pub const PXAR_PAYLOAD
: u64 = 0x28147a1b0b7c1a25;
67 /// Marks item as entry of goodbye table
68 pub const PXAR_GOODBYE
: u64 = 0x2fec4fa642d5731d;
69 /// The end marker used in the GOODBYE object
70 pub const PXAR_GOODBYE_TAIL_MARKER
: u64 = 0xef5eed5b753e1555;
72 #[derive(Debug, Endian)]
75 /// The item type (see `PXAR_` constants).
77 /// The size of the item, including the size of `Header`.
83 pub fn with_full_size(htype
: u64, full_size
: u64) -> Self {
84 Self { htype, full_size }
88 pub fn with_content_size(htype
: u64, content_size
: u64) -> Self {
89 Self::with_full_size(htype
, content_size
+ size_of
::<Header
>() as u64)
93 pub fn full_size(&self) -> u64 {
98 pub fn content_size(&self) -> u64 {
99 self.full_size() - (size_of
::<Self>() as u64)
103 pub fn max_content_size(&self) -> u64 {
105 // + null-termination
106 PXAR_FILENAME
=> crate::util
::MAX_FILENAME_LEN
+ 1,
107 // + null-termination
108 PXAR_SYMLINK
=> crate::util
::MAX_PATH_LEN
+ 1,
109 // + null-termination + offset
110 PXAR_HARDLINK
=> crate::util
::MAX_PATH_LEN
+ 1 + (size_of
::<u64>() as u64),
111 PXAR_DEVICE
=> size_of
::<Device
>() as u64,
112 PXAR_XATTR
| PXAR_FCAPS
=> crate::util
::MAX_XATTR_LEN
,
113 PXAR_ACL_USER
| PXAR_ACL_DEFAULT_USER
=> size_of
::<acl
::User
>() as u64,
114 PXAR_ACL_GROUP
| PXAR_ACL_DEFAULT_GROUP
=> size_of
::<acl
::Group
>() as u64,
115 PXAR_ACL_DEFAULT
=> size_of
::<acl
::Default
>() as u64,
116 PXAR_ACL_GROUP_OBJ
=> size_of
::<acl
::GroupObject
> as u64,
117 PXAR_QUOTA_PROJID
=> size_of
::<QuotaProjectId
>() as u64,
118 PXAR_ENTRY
=> size_of
::<Entry
>() as u64,
119 PXAR_PAYLOAD
| PXAR_GOODBYE
=> u64::MAX
- (size_of
::<Self>() as u64),
120 _
=> u64::MAX
- (size_of
::<Self>() as u64),
125 pub fn check_header_size(&self) -> io
::Result
<()> {
126 if self.full_size() < size_of
::<Header
>() as u64 {
127 io_bail
!("invalid header {} - too small ({})", self, self.full_size());
130 if self.content_size() > self.max_content_size() {
132 "invalid content size ({} > {}) of entry with {}",
134 self.max_content_size(),
142 impl Display
for Header
{
143 fn fmt(&self, f
: &mut fmt
::Formatter
<'_
>) -> fmt
::Result
{
144 let readable
= match self.htype
{
145 PXAR_FILENAME
=> "FILENAME",
146 PXAR_SYMLINK
=> "SYMLINK",
147 PXAR_HARDLINK
=> "HARDLINK",
148 PXAR_DEVICE
=> "DEVICE",
149 PXAR_XATTR
=> "XATTR",
150 PXAR_FCAPS
=> "FCAPS",
151 PXAR_ACL_USER
=> "ACL_USER",
152 PXAR_ACL_DEFAULT_USER
=> "ACL_DEFAULT_USER",
153 PXAR_ACL_GROUP
=> "ACL_GROUP",
154 PXAR_ACL_DEFAULT_GROUP
=> "ACL_DEFAULT_GROUP",
155 PXAR_ACL_DEFAULT
=> "ACL_DEFAULT",
156 PXAR_ACL_GROUP_OBJ
=> "ACL_GROUP_OBJ",
157 PXAR_QUOTA_PROJID
=> "QUOTA_PROJID",
158 PXAR_ENTRY
=> "ENTRY",
159 PXAR_PAYLOAD
=> "PAYLOAD",
160 PXAR_GOODBYE
=> "GOODBYE",
163 write
!(f
, "{} header ({:x})", readable
, self.htype
)
167 #[derive(Clone, Debug, Default, Endian)]
168 #[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))]
178 /// Builder pattern methods.
180 pub const fn mode(self, mode
: u64) -> Self {
181 Self { mode, ..self }
184 pub const fn flags(self, flags
: u64) -> Self {
185 Self { flags, ..self }
188 pub const fn uid(self, uid
: u32) -> Self {
192 pub const fn gid(self, gid
: u32) -> Self {
196 pub const fn mtime(self, mtime
: u64) -> Self {
197 Self { mtime, ..self }
200 pub const fn set_dir(self) -> Self {
201 let mode
= self.mode
;
202 self.mode((mode
& !mode
::IFMT
) | mode
::IFDIR
)
205 pub const fn set_regular_file(self) -> Self {
206 let mode
= self.mode
;
207 self.mode((mode
& !mode
::IFMT
) | mode
::IFREG
)
210 pub const fn set_symlink(self) -> Self {
211 let mode
= self.mode
;
212 self.mode((mode
& !mode
::IFMT
) | mode
::IFLNK
)
215 pub const fn set_blockdev(self) -> Self {
216 let mode
= self.mode
;
217 self.mode((mode
& !mode
::IFMT
) | mode
::IFBLK
)
220 pub const fn set_chardev(self) -> Self {
221 let mode
= self.mode
;
222 self.mode((mode
& !mode
::IFMT
) | mode
::IFCHR
)
225 pub const fn set_fifo(self) -> Self {
226 let mode
= self.mode
;
227 self.mode((mode
& !mode
::IFMT
) | mode
::IFIFO
)
231 /// Convenience accessor methods.
233 /// Get the mtime as duration since the epoch.
234 pub fn mtime_as_duration(&self) -> std
::time
::Duration
{
235 std
::time
::Duration
::from_nanos(self.mtime
)
238 /// Get the file type portion of the mode bitfield.
239 pub fn get_file_bits(&self) -> u64 {
240 self.mode
& mode
::IFMT
243 /// Get the permission portion of the mode bitfield.
244 pub fn get_permission_bits(&self) -> u64 {
245 self.mode
& !mode
::IFMT
249 /// Convenience methods.
251 /// Get the file type (`mode & mode::IFMT`).
252 pub fn file_type(&self) -> u64 {
253 self.mode
& mode
::IFMT
256 /// Get the file mode bits (`mode & !mode::IFMT`).
257 pub fn file_mode(&self) -> u64 {
258 self.mode
& !mode
::IFMT
261 /// Check whether this is a directory.
262 pub fn is_dir(&self) -> bool
{
263 (self.mode
& mode
::IFMT
) == mode
::IFDIR
266 /// Check whether this is a symbolic link.
267 pub fn is_symlink(&self) -> bool
{
268 (self.mode
& mode
::IFMT
) == mode
::IFLNK
271 /// Check whether this is a device node.
272 pub fn is_device(&self) -> bool
{
273 let fmt
= self.mode
& mode
::IFMT
;
274 fmt
== mode
::IFCHR
|| fmt
== mode
::IFBLK
277 /// Check whether this is a block device node.
278 pub fn is_blockdev(&self) -> bool
{
279 let fmt
= self.mode
& mode
::IFMT
;
283 /// Check whether this is a character device node.
284 pub fn is_chardev(&self) -> bool
{
285 let fmt
= self.mode
& mode
::IFMT
;
289 /// Check whether this is a regular file.
290 pub fn is_regular_file(&self) -> bool
{
291 (self.mode
& mode
::IFMT
) == mode
::IFREG
294 /// Check whether this is a named pipe (FIFO).
295 pub fn is_fifo(&self) -> bool
{
296 (self.mode
& mode
::IFMT
) == mode
::IFIFO
299 /// Check whether this is a named socket.
300 pub fn is_socket(&self) -> bool
{
301 (self.mode
& mode
::IFMT
) == mode
::IFSOCK
305 impl From
<&std
::fs
::Metadata
> for Entry
{
306 fn from(meta
: &std
::fs
::Metadata
) -> Entry
{
308 use std
::os
::unix
::fs
::MetadataExt
;
310 let this
= Entry
::default();
316 .mode(meta
.mode() as u64);
318 let this
= match meta
.modified() {
319 Ok(mtime
) => this
.mtime(
321 .duration_since(std
::time
::SystemTime
::UNIX_EPOCH
)
322 .map(|dur
| dur
.as_nanos() as u64)
328 let file_type
= meta
.file_type();
329 let mode
= this
.mode
;
330 let this
= if file_type
.is_dir() {
331 this
.mode(mode
| mode
::IFDIR
)
332 } else if file_type
.is_symlink() {
333 this
.mode(mode
| mode
::IFLNK
)
335 this
.mode(mode
| mode
::IFREG
)
342 #[derive(Clone, Debug)]
343 pub struct Filename
{
347 #[derive(Clone, Debug)]
353 pub fn as_os_str(&self) -> &OsStr
{
358 impl AsRef
<[u8]> for Symlink
{
359 fn as_ref(&self) -> &[u8] {
364 impl AsRef
<OsStr
> for Symlink
{
365 fn as_ref(&self) -> &OsStr
{
366 OsStr
::from_bytes(&self.data
[..self.data
.len().max(1) - 1])
370 #[derive(Clone, Debug)]
371 pub struct Hardlink
{
377 pub fn as_os_str(&self) -> &OsStr
{
382 impl AsRef
<[u8]> for Hardlink
{
383 fn as_ref(&self) -> &[u8] {
388 impl AsRef
<OsStr
> for Hardlink
{
389 fn as_ref(&self) -> &OsStr
{
390 OsStr
::from_bytes(&self.data
[..self.data
.len().max(1) - 1])
394 #[derive(Clone, Debug, Eq)]
397 pub(crate) data
: Vec
<u8>,
398 pub(crate) name_len
: usize,
402 pub fn new
<N
: AsRef
<[u8]>, V
: AsRef
<[u8]>>(name
: N
, value
: V
) -> Self {
403 let name
= name
.as_ref();
404 let value
= value
.as_ref();
405 let mut data
= Vec
::with_capacity(name
.len() + value
.len() + 1);
411 name_len
: name
.len(),
415 pub fn name(&self) -> &CStr
{
416 unsafe { CStr::from_bytes_with_nul_unchecked(&self.data[..self.name_len + 1]) }
419 pub fn value(&self) -> &[u8] {
420 &self.data
[(self.name_len
+ 1)..]
425 fn cmp(&self, other
: &XAttr
) -> Ordering
{
426 self.name().cmp(&other
.name())
430 impl PartialOrd
for XAttr
{
431 fn partial_cmp(&self, other
: &XAttr
) -> Option
<Ordering
> {
432 Some(self.cmp(other
))
436 impl PartialEq
for XAttr
{
437 fn eq(&self, other
: &XAttr
) -> bool
{
438 self.name() == other
.name()
442 #[derive(Clone, Debug, Endian, Eq, PartialEq)]
449 #[cfg(target_os = "linux")]
451 /// Get a `dev_t` value for this device.
453 pub fn to_dev_t(&self) -> u64 {
454 // see bits/sysmacros.h
455 ((self.major
& 0x0000_0fff) << 8) |
456 ((self.major
& 0xffff_f000) << 32) |
457 (self.minor
& 0x0000_00ff) |
458 ((self.minor
& 0xffff_ff00) << 12)
461 /// Get a `Device` from a `dev_t` value.
463 pub fn from_dev_t(dev
: u64) -> Self {
466 major
: (dev
>> 8) & 0x0000_0fff |
467 (dev
>> 32) & 0xffff_f000,
468 minor
: dev
& 0x0000_00ff |
469 (dev
>> 12) & 0xffff_ff00,
474 #[cfg(all(test, target_os = "linux"))]
476 fn test_linux_devices() {
477 let c_dev
= unsafe { ::libc::makedev(0xabcd_1234, 0xdcba_5678) }
;
478 let dev
= Device
::from_dev_t(c_dev
);
479 assert_eq
!(dev
.to_dev_t(), c_dev
);
482 #[derive(Clone, Debug)]
483 #[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))]
489 #[derive(Clone, Copy, Debug, Endian, Eq, PartialEq)]
491 pub struct QuotaProjectId
{
495 #[derive(Clone, Debug, Endian)]
497 pub struct GoodbyeItem
{
498 /// SipHash24 of the directory item name. The last GOODBYE item uses the special hash value
499 /// `PXAR_GOODBYE_TAIL_MARKER`.
502 /// The offset from the start of the GOODBYE object to the start of the matching directory item
503 /// (point to a FILENAME). The last GOODBYE item points to the start of the matching ENTRY
507 /// The overall size of the directory item. This includes the FILENAME header. In other words,
508 /// `goodbye_start - offset + size` points to the end of the directory.
510 /// The last GOODBYE item repeats the size of the GOODBYE item.
515 pub fn new(name
: &[u8], offset
: u64, size
: u64) -> Self {
516 let hash
= hash_filename(name
);
517 Self { hash, offset, size }
521 pub fn hash_filename(name
: &[u8]) -> u64 {
522 use std
::hash
::Hasher
;
524 let mut hasher
= SipHasher24
::new_with_keys(PXAR_HASH_KEY_1
, PXAR_HASH_KEY_2
);
529 pub fn path_is_legal_component(path
: &Path
) -> bool
{
530 let mut components
= path
.components();
531 match components
.next() {
532 Some(std
::path
::Component
::Normal(_
)) => (),
535 components
.next().is_none()
538 pub fn check_file_name(path
: &Path
) -> io
::Result
<()> {
539 if !path_is_legal_component(path
) {
540 io_bail
!("invalid file name in archive: {:?}", path
);