]> git.proxmox.com Git - pxar.git/blob - src/format/mod.rs
07470b4ac371c823e02a5fff003a346790a5c076
[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 use std::cmp::Ordering;
9 use std::ffi::{CStr, OsStr};
10 use std::fmt;
11 use std::fmt::Display;
12 use std::io;
13 use std::mem::size_of;
14 use std::os::unix::ffi::OsStrExt;
15 use std::path::Path;
16
17 use endian_trait::Endian;
18 use siphasher::sip::SipHasher24;
19
20 pub mod acl;
21
22 // generated with:
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;
26
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.
29 ///
30 /// Additionally this gets rid of a bunch of casts between u32 and u64.
31 ///
32 /// You can usually find the values for these in `/usr/include/linux/stat.h`.
33 #[rustfmt::skip]
34 pub mod mode {
35 pub const IFMT : u64 = 0o0170000;
36
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;
44
45 pub const ISUID : u64 = 0o0004000;
46 pub const ISGID : u64 = 0o0002000;
47 pub const ISVTX : u64 = 0o0001000;
48 }
49
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;
71
72 #[derive(Debug, Endian)]
73 #[repr(C)]
74 pub struct Header {
75 /// The item type (see `PXAR_` constants).
76 pub htype: u64,
77 /// The size of the item, including the size of `Header`.
78 full_size: u64,
79 }
80
81 impl Header {
82 #[inline]
83 pub fn with_full_size(htype: u64, full_size: u64) -> Self {
84 Self { htype, full_size }
85 }
86
87 #[inline]
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)
90 }
91
92 #[inline]
93 pub fn full_size(&self) -> u64 {
94 self.full_size
95 }
96
97 #[inline]
98 pub fn content_size(&self) -> u64 {
99 self.full_size() - (size_of::<Self>() as u64)
100 }
101
102 #[inline]
103 pub fn max_content_size(&self) -> u64 {
104 match self.htype {
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),
121 }
122 }
123
124 #[inline]
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());
128 }
129
130 if self.content_size() > self.max_content_size() {
131 io_bail!(
132 "invalid content size ({} > {}) of entry with {}",
133 self.content_size(),
134 self.max_content_size(),
135 self
136 );
137 }
138 Ok(())
139 }
140 }
141
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",
161 _ => "UNKNOWN",
162 };
163 write!(f, "{} header ({:x})", readable, self.htype)
164 }
165 }
166
167 #[derive(Clone, Debug, Default, Endian)]
168 #[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))]
169 #[repr(C)]
170 pub struct Entry {
171 pub mode: u64,
172 pub flags: u64,
173 pub uid: u32,
174 pub gid: u32,
175 pub mtime: u64,
176 }
177
178 /// Builder pattern methods.
179 impl Entry {
180 pub const fn mode(self, mode: u64) -> Self {
181 Self { mode, ..self }
182 }
183
184 pub const fn flags(self, flags: u64) -> Self {
185 Self { flags, ..self }
186 }
187
188 pub const fn uid(self, uid: u32) -> Self {
189 Self { uid, ..self }
190 }
191
192 pub const fn gid(self, gid: u32) -> Self {
193 Self { gid, ..self }
194 }
195
196 pub const fn mtime(self, mtime: u64) -> Self {
197 Self { mtime, ..self }
198 }
199
200 pub const fn set_dir(self) -> Self {
201 let mode = self.mode;
202 self.mode((mode & !mode::IFMT) | mode::IFDIR)
203 }
204
205 pub const fn set_regular_file(self) -> Self {
206 let mode = self.mode;
207 self.mode((mode & !mode::IFMT) | mode::IFREG)
208 }
209
210 pub const fn set_symlink(self) -> Self {
211 let mode = self.mode;
212 self.mode((mode & !mode::IFMT) | mode::IFLNK)
213 }
214
215 pub const fn set_blockdev(self) -> Self {
216 let mode = self.mode;
217 self.mode((mode & !mode::IFMT) | mode::IFBLK)
218 }
219
220 pub const fn set_chardev(self) -> Self {
221 let mode = self.mode;
222 self.mode((mode & !mode::IFMT) | mode::IFCHR)
223 }
224
225 pub const fn set_fifo(self) -> Self {
226 let mode = self.mode;
227 self.mode((mode & !mode::IFMT) | mode::IFIFO)
228 }
229 }
230
231 /// Convenience accessor methods.
232 impl Entry {
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)
236 }
237
238 /// Get the file type portion of the mode bitfield.
239 pub fn get_file_bits(&self) -> u64 {
240 self.mode & mode::IFMT
241 }
242
243 /// Get the permission portion of the mode bitfield.
244 pub fn get_permission_bits(&self) -> u64 {
245 self.mode & !mode::IFMT
246 }
247 }
248
249 /// Convenience methods.
250 impl Entry {
251 /// Get the file type (`mode & mode::IFMT`).
252 pub fn file_type(&self) -> u64 {
253 self.mode & mode::IFMT
254 }
255
256 /// Get the file mode bits (`mode & !mode::IFMT`).
257 pub fn file_mode(&self) -> u64 {
258 self.mode & !mode::IFMT
259 }
260
261 /// Check whether this is a directory.
262 pub fn is_dir(&self) -> bool {
263 (self.mode & mode::IFMT) == mode::IFDIR
264 }
265
266 /// Check whether this is a symbolic link.
267 pub fn is_symlink(&self) -> bool {
268 (self.mode & mode::IFMT) == mode::IFLNK
269 }
270
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
275 }
276
277 /// Check whether this is a block device node.
278 pub fn is_blockdev(&self) -> bool {
279 let fmt = self.mode & mode::IFMT;
280 fmt == mode::IFBLK
281 }
282
283 /// Check whether this is a character device node.
284 pub fn is_chardev(&self) -> bool {
285 let fmt = self.mode & mode::IFMT;
286 fmt == mode::IFCHR
287 }
288
289 /// Check whether this is a regular file.
290 pub fn is_regular_file(&self) -> bool {
291 (self.mode & mode::IFMT) == mode::IFREG
292 }
293
294 /// Check whether this is a named pipe (FIFO).
295 pub fn is_fifo(&self) -> bool {
296 (self.mode & mode::IFMT) == mode::IFIFO
297 }
298
299 /// Check whether this is a named socket.
300 pub fn is_socket(&self) -> bool {
301 (self.mode & mode::IFMT) == mode::IFSOCK
302 }
303 }
304
305 impl From<&std::fs::Metadata> for Entry {
306 fn from(meta: &std::fs::Metadata) -> Entry {
307 #[cfg(unix)]
308 use std::os::unix::fs::MetadataExt;
309
310 let this = Entry::default();
311
312 #[cfg(unix)]
313 let this = this
314 .uid(meta.uid())
315 .gid(meta.gid())
316 .mode(meta.mode() as u64);
317
318 let this = match meta.modified() {
319 Ok(mtime) => this.mtime(
320 mtime
321 .duration_since(std::time::SystemTime::UNIX_EPOCH)
322 .map(|dur| dur.as_nanos() as u64)
323 .unwrap_or(0u64),
324 ),
325 Err(_) => this,
326 };
327
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)
334 } else {
335 this.mode(mode | mode::IFREG)
336 };
337
338 this
339 }
340 }
341
342 #[derive(Clone, Debug)]
343 pub struct Filename {
344 pub name: Vec<u8>,
345 }
346
347 #[derive(Clone, Debug)]
348 pub struct Symlink {
349 pub data: Vec<u8>,
350 }
351
352 impl Symlink {
353 pub fn as_os_str(&self) -> &OsStr {
354 self.as_ref()
355 }
356 }
357
358 impl AsRef<[u8]> for Symlink {
359 fn as_ref(&self) -> &[u8] {
360 &self.data
361 }
362 }
363
364 impl AsRef<OsStr> for Symlink {
365 fn as_ref(&self) -> &OsStr {
366 OsStr::from_bytes(&self.data[..self.data.len().max(1) - 1])
367 }
368 }
369
370 #[derive(Clone, Debug)]
371 pub struct Hardlink {
372 pub offset: u64,
373 pub data: Vec<u8>,
374 }
375
376 impl Hardlink {
377 pub fn as_os_str(&self) -> &OsStr {
378 self.as_ref()
379 }
380 }
381
382 impl AsRef<[u8]> for Hardlink {
383 fn as_ref(&self) -> &[u8] {
384 &self.data
385 }
386 }
387
388 impl AsRef<OsStr> for Hardlink {
389 fn as_ref(&self) -> &OsStr {
390 OsStr::from_bytes(&self.data[..self.data.len().max(1) - 1])
391 }
392 }
393
394 #[derive(Clone, Debug, Eq)]
395 #[repr(C)]
396 pub struct XAttr {
397 pub(crate) data: Vec<u8>,
398 pub(crate) name_len: usize,
399 }
400
401 impl XAttr {
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);
406 data.extend(name);
407 data.push(0);
408 data.extend(value);
409 Self {
410 data,
411 name_len: name.len(),
412 }
413 }
414
415 pub fn name(&self) -> &CStr {
416 unsafe { CStr::from_bytes_with_nul_unchecked(&self.data[..self.name_len + 1]) }
417 }
418
419 pub fn value(&self) -> &[u8] {
420 &self.data[(self.name_len + 1)..]
421 }
422 }
423
424 impl Ord for XAttr {
425 fn cmp(&self, other: &XAttr) -> Ordering {
426 self.name().cmp(&other.name())
427 }
428 }
429
430 impl PartialOrd for XAttr {
431 fn partial_cmp(&self, other: &XAttr) -> Option<Ordering> {
432 Some(self.cmp(other))
433 }
434 }
435
436 impl PartialEq for XAttr {
437 fn eq(&self, other: &XAttr) -> bool {
438 self.name() == other.name()
439 }
440 }
441
442 #[derive(Clone, Debug, Endian, Eq, PartialEq)]
443 #[repr(C)]
444 pub struct Device {
445 pub major: u64,
446 pub minor: u64,
447 }
448
449 #[cfg(target_os = "linux")]
450 impl Device {
451 /// Get a `dev_t` value for this device.
452 #[rustfmt::skip]
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)
459 }
460
461 /// Get a `Device` from a `dev_t` value.
462 #[rustfmt::skip]
463 pub fn from_dev_t(dev: u64) -> Self {
464 // see to_dev_t
465 Self {
466 major: (dev >> 8) & 0x0000_0fff |
467 (dev >> 32) & 0xffff_f000,
468 minor: dev & 0x0000_00ff |
469 (dev >> 12) & 0xffff_ff00,
470 }
471 }
472 }
473
474 #[cfg(all(test, target_os = "linux"))]
475 #[test]
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);
480 }
481
482 #[derive(Clone, Debug)]
483 #[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))]
484 #[repr(C)]
485 pub struct FCaps {
486 pub data: Vec<u8>,
487 }
488
489 #[derive(Clone, Copy, Debug, Endian, Eq, PartialEq)]
490 #[repr(C)]
491 pub struct QuotaProjectId {
492 pub projid: u64,
493 }
494
495 #[derive(Clone, Debug, Endian)]
496 #[repr(C)]
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`.
500 pub hash: u64,
501
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
504 /// object.
505 pub offset: u64,
506
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.
509 ///
510 /// The last GOODBYE item repeats the size of the GOODBYE item.
511 pub size: u64,
512 }
513
514 impl GoodbyeItem {
515 pub fn new(name: &[u8], offset: u64, size: u64) -> Self {
516 let hash = hash_filename(name);
517 Self { hash, offset, size }
518 }
519 }
520
521 pub fn hash_filename(name: &[u8]) -> u64 {
522 use std::hash::Hasher;
523
524 let mut hasher = SipHasher24::new_with_keys(PXAR_HASH_KEY_1, PXAR_HASH_KEY_2);
525 hasher.write(name);
526 hasher.finish()
527 }
528
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(_)) => (),
533 _ => return false,
534 }
535 components.next().is_none()
536 }
537
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);
541 } else {
542 Ok(())
543 }
544 }