]> git.proxmox.com Git - pxar.git/blame - src/format/mod.rs
header: implement Display
[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.
7
8use std::cmp::Ordering;
2c442007 9use std::ffi::{CStr, OsStr};
4a13b8a3
FG
10use std::fmt;
11use std::fmt::Display;
6cd4f635
WB
12use std::io;
13use std::mem::size_of;
54912ae6 14use std::os::unix::ffi::OsStrExt;
6cd4f635
WB
15use std::path::Path;
16
17use endian_trait::Endian;
18use siphasher::sip::SipHasher24;
19
20pub mod acl;
21
70acf637
WB
22/// While these constants correspond to `libc::S_` constants, we need these to be fixed for the
23/// format itself, so we redefine them here.
24///
25/// Additionally this gets rid of a bunch of casts between u32 and u64.
26///
27/// You can usually find the values for these in `/usr/include/linux/stat.h`.
28#[rustfmt::skip]
29pub mod mode {
30 pub const IFMT : u64 = 0o0170000;
31
32 pub const IFSOCK : u64 = 0o0140000;
33 pub const IFLNK : u64 = 0o0120000;
34 pub const IFREG : u64 = 0o0100000;
35 pub const IFBLK : u64 = 0o0060000;
36 pub const IFDIR : u64 = 0o0040000;
37 pub const IFCHR : u64 = 0o0020000;
38 pub const IFIFO : u64 = 0o0010000;
a13a2f74 39
70acf637
WB
40 pub const ISUID : u64 = 0o0004000;
41 pub const ISGID : u64 = 0o0002000;
42 pub const ISVTX : u64 = 0o0001000;
43}
44
6cd4f635
WB
45pub const PXAR_ENTRY: u64 = 0x1396fabcea5bbb51;
46pub const PXAR_FILENAME: u64 = 0x6dbb6ebcb3161f0b;
47pub const PXAR_SYMLINK: u64 = 0x664a6fb6830e0d6c;
48pub const PXAR_DEVICE: u64 = 0xac3dace369dfe643;
49pub const PXAR_XATTR: u64 = 0xb8157091f80bc486;
50pub const PXAR_ACL_USER: u64 = 0x297dc88b2ef12faf;
51pub const PXAR_ACL_GROUP: u64 = 0x36f2acb56cb3dd0b;
52pub const PXAR_ACL_GROUP_OBJ: u64 = 0x23047110441f38f3;
53pub const PXAR_ACL_DEFAULT: u64 = 0xfe3eeda6823c8cd0;
54pub const PXAR_ACL_DEFAULT_USER: u64 = 0xbdf03df9bd010a91;
55pub const PXAR_ACL_DEFAULT_GROUP: u64 = 0xa0cb1168782d1f51;
56pub const PXAR_FCAPS: u64 = 0xf7267db0afed0629;
57pub const PXAR_QUOTA_PROJID: u64 = 0x161baf2d8772a72b;
58
59/// Marks item as hardlink
60/// compute_goodbye_hash(b"__PROXMOX_FORMAT_HARDLINK__");
61pub const PXAR_HARDLINK: u64 = 0x2c5e06f634f65b86;
62/// Marks the beginnig of the payload (actual content) of regular files
63pub const PXAR_PAYLOAD: u64 = 0x8b9e1d93d6dcffc9;
64/// Marks item as entry of goodbye table
65pub const PXAR_GOODBYE: u64 = 0xdfd35c5e8327c403;
66/// The end marker used in the GOODBYE object
67pub const PXAR_GOODBYE_TAIL_MARKER: u64 = 0x57446fa533702943;
68
69#[derive(Debug, Endian)]
70#[repr(C)]
71pub struct Header {
72 /// The item type (see `PXAR_` constants).
73 pub htype: u64,
74 /// The size of the item, including the size of `Header`.
75 full_size: u64,
76}
77
78impl Header {
70acf637
WB
79 #[inline]
80 pub fn with_full_size(htype: u64, full_size: u64) -> Self {
81 Self { htype, full_size }
82 }
83
84 #[inline]
85 pub fn with_content_size(htype: u64, content_size: u64) -> Self {
86 Self::with_full_size(htype, content_size + size_of::<Header>() as u64)
87 }
88
6cd4f635
WB
89 #[inline]
90 pub fn full_size(&self) -> u64 {
91 self.full_size
92 }
93
94 #[inline]
95 pub fn content_size(&self) -> u64 {
96 self.full_size() - (size_of::<Self>() as u64)
97 }
ec0761f9
FG
98
99 #[inline]
100 pub fn max_content_size(&self) -> u64 {
101 match self.htype {
102 // + null-termination
103 PXAR_FILENAME => crate::util::MAX_FILENAME_LEN + 1,
104 // + null-termination
105 PXAR_SYMLINK => crate::util::MAX_PATH_LEN + 1,
106 // + null-termination + offset
107 PXAR_HARDLINK => crate::util::MAX_PATH_LEN + 1 + (size_of::<u64>() as u64),
108 PXAR_DEVICE => size_of::<Device>() as u64,
109 PXAR_XATTR | PXAR_FCAPS => crate::util::MAX_XATTR_LEN,
110 PXAR_ACL_USER | PXAR_ACL_DEFAULT_USER => size_of::<acl::User>() as u64,
111 PXAR_ACL_GROUP | PXAR_ACL_DEFAULT_GROUP => size_of::<acl::Group>() as u64,
112 PXAR_ACL_DEFAULT => size_of::<acl::Default>() as u64,
113 PXAR_ACL_GROUP_OBJ => size_of::<acl::GroupObject> as u64,
114 PXAR_QUOTA_PROJID => size_of::<QuotaProjectId>() as u64,
115 PXAR_ENTRY => size_of::<Entry>() as u64,
116 PXAR_PAYLOAD | PXAR_GOODBYE => u64::MAX - (size_of::<Self>() as u64),
117 _ => u64::MAX - (size_of::<Self>() as u64),
118 }
119 }
120
121 #[inline]
122 pub fn check_header_size(&self) -> io::Result<()> {
123 if self.full_size() < size_of::<Header>() as u64 {
124 io_bail!("invalid header {} - too small ({})", self, self.full_size());
125 }
126
127 if self.content_size() > self.max_content_size() {
128 io_bail!("invalid content size ({} > {}) of entry with {}", self.content_size(), self.max_content_size(), self);
129 }
130 Ok(())
131 }
132}
4a13b8a3
FG
133
134impl Display for Header {
135 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136 let readable = match self.htype {
137 PXAR_FILENAME => "FILENAME",
138 PXAR_SYMLINK => "SYMLINK",
139 PXAR_HARDLINK => "HARDLINK",
140 PXAR_DEVICE => "DEVICE",
141 PXAR_XATTR => "XATTR",
142 PXAR_FCAPS => "FCAPS",
143 PXAR_ACL_USER => "ACL_USER",
144 PXAR_ACL_DEFAULT_USER => "ACL_DEFAULT_USER",
145 PXAR_ACL_GROUP => "ACL_GROUP",
146 PXAR_ACL_DEFAULT_GROUP => "ACL_DEFAULT_GROUP",
147 PXAR_ACL_DEFAULT => "ACL_DEFAULT",
148 PXAR_ACL_GROUP_OBJ => "ACL_GROUP_OBJ",
149 PXAR_QUOTA_PROJID => "QUOTA_PROJID",
150 PXAR_ENTRY => "ENTRY",
151 PXAR_PAYLOAD => "PAYLOAD",
152 PXAR_GOODBYE => "GOODBYE",
153 _ => "UNKNOWN",
154 };
155 write!(f, "{} header ({:x})", readable, self.htype)
156 }
6cd4f635
WB
157}
158
159#[derive(Clone, Debug, Default, Endian)]
160#[repr(C)]
161pub struct Entry {
162 pub mode: u64,
163 pub flags: u64,
164 pub uid: u32,
165 pub gid: u32,
166 pub mtime: u64,
167}
168
70acf637
WB
169/// Builder pattern methods.
170impl Entry {
171 pub const fn mode(self, mode: u64) -> Self {
172 Self { mode, ..self }
173 }
174
175 pub const fn flags(self, flags: u64) -> Self {
176 Self { flags, ..self }
177 }
178
179 pub const fn uid(self, uid: u32) -> Self {
180 Self { uid, ..self }
181 }
182
183 pub const fn gid(self, gid: u32) -> Self {
184 Self { gid, ..self }
185 }
186
187 pub const fn mtime(self, mtime: u64) -> Self {
188 Self { mtime, ..self }
189 }
190
191 pub const fn set_dir(self) -> Self {
192 let mode = self.mode;
193 self.mode((mode & !mode::IFMT) | mode::IFDIR)
194 }
195
196 pub const fn set_regular_file(self) -> Self {
197 let mode = self.mode;
198 self.mode((mode & !mode::IFMT) | mode::IFREG)
199 }
200
201 pub const fn set_symlink(self) -> Self {
202 let mode = self.mode;
203 self.mode((mode & !mode::IFMT) | mode::IFLNK)
204 }
205
206 pub const fn set_blockdev(self) -> Self {
207 let mode = self.mode;
208 self.mode((mode & !mode::IFMT) | mode::IFBLK)
209 }
210
211 pub const fn set_chardev(self) -> Self {
212 let mode = self.mode;
213 self.mode((mode & !mode::IFMT) | mode::IFCHR)
214 }
215
216 pub const fn set_fifo(self) -> Self {
217 let mode = self.mode;
218 self.mode((mode & !mode::IFMT) | mode::IFIFO)
219 }
220}
221
6da548dd
WB
222/// Convenience accessor methods.
223impl Entry {
224 /// Get the mtime as duration since the epoch.
225 pub fn mtime_as_duration(&self) -> std::time::Duration {
226 std::time::Duration::from_nanos(self.mtime)
227 }
8617cae3
WB
228
229 /// Get the file type portion of the mode bitfield.
230 pub fn get_file_bits(&self) -> u64 {
231 self.mode & mode::IFMT
232 }
233
234 /// Get the permission portion of the mode bitfield.
235 pub fn get_permission_bits(&self) -> u64 {
236 self.mode & !mode::IFMT
237 }
6da548dd
WB
238}
239
70acf637
WB
240/// Convenience methods.
241impl Entry {
21d66e10
WB
242 /// Get the file type (`mode & mode::IFMT`).
243 pub fn file_type(&self) -> u64 {
244 self.mode & mode::IFMT
245 }
246
247 /// Get the file mode bits (`mode & !mode::IFMT`).
248 pub fn file_mode(&self) -> u64 {
249 self.mode & !mode::IFMT
250 }
251
70acf637
WB
252 /// Check whether this is a directory.
253 pub fn is_dir(&self) -> bool {
254 (self.mode & mode::IFMT) == mode::IFDIR
255 }
256
257 /// Check whether this is a symbolic link.
258 pub fn is_symlink(&self) -> bool {
259 (self.mode & mode::IFMT) == mode::IFLNK
260 }
261
262 /// Check whether this is a device node.
263 pub fn is_device(&self) -> bool {
264 let fmt = self.mode & mode::IFMT;
265 fmt == mode::IFCHR || fmt == mode::IFBLK
266 }
267
da286c28
WB
268 /// Check whether this is a block device node.
269 pub fn is_blockdev(&self) -> bool {
270 let fmt = self.mode & mode::IFMT;
271 fmt == mode::IFBLK
272 }
273
274 /// Check whether this is a character device node.
275 pub fn is_chardev(&self) -> bool {
276 let fmt = self.mode & mode::IFMT;
277 fmt == mode::IFCHR
278 }
279
70acf637
WB
280 /// Check whether this is a regular file.
281 pub fn is_regular_file(&self) -> bool {
282 (self.mode & mode::IFMT) == mode::IFREG
283 }
2287d8b2
WB
284
285 /// Check whether this is a named pipe (FIFO).
286 pub fn is_fifo(&self) -> bool {
287 (self.mode & mode::IFMT) == mode::IFIFO
288 }
289
290 /// Check whether this is a named socket.
291 pub fn is_socket(&self) -> bool {
292 (self.mode & mode::IFMT) == mode::IFSOCK
293 }
70acf637
WB
294}
295
296impl From<&std::fs::Metadata> for Entry {
297 fn from(meta: &std::fs::Metadata) -> Entry {
298 #[cfg(unix)]
299 use std::os::unix::fs::MetadataExt;
300
301 let this = Entry::default();
302
303 #[cfg(unix)]
304 let this = this
305 .uid(meta.uid())
306 .gid(meta.gid())
0a6c7350
WB
307 .mode(meta.mode() as u64);
308
309 let this = match meta.modified() {
1250e3ea
WB
310 Ok(mtime) => this.mtime(
311 mtime
0a6c7350
WB
312 .duration_since(std::time::SystemTime::UNIX_EPOCH)
313 .map(|dur| dur.as_nanos() as u64)
1250e3ea
WB
314 .unwrap_or(0u64),
315 ),
0a6c7350
WB
316 Err(_) => this,
317 };
70acf637
WB
318
319 let file_type = meta.file_type();
320 let mode = this.mode;
321 let this = if file_type.is_dir() {
322 this.mode(mode | mode::IFDIR)
323 } else if file_type.is_symlink() {
324 this.mode(mode | mode::IFLNK)
325 } else {
326 this.mode(mode | mode::IFREG)
327 };
328
329 this
330 }
331}
332
6cd4f635
WB
333#[derive(Clone, Debug)]
334pub struct Filename {
335 pub name: Vec<u8>,
336}
337
338#[derive(Clone, Debug)]
339pub struct Symlink {
340 pub data: Vec<u8>,
341}
342
7b389f88
WB
343impl Symlink {
344 pub fn as_os_str(&self) -> &OsStr {
345 self.as_ref()
346 }
347}
348
353bbf73
WB
349impl AsRef<[u8]> for Symlink {
350 fn as_ref(&self) -> &[u8] {
351 &self.data
352 }
353}
354
54912ae6
WB
355impl AsRef<OsStr> for Symlink {
356 fn as_ref(&self) -> &OsStr {
353bbf73 357 OsStr::from_bytes(&self.data[..self.data.len().max(1) - 1])
54912ae6
WB
358 }
359}
360
6cd4f635
WB
361#[derive(Clone, Debug)]
362pub struct Hardlink {
b0752929 363 pub offset: u64,
6cd4f635
WB
364 pub data: Vec<u8>,
365}
366
7b389f88
WB
367impl Hardlink {
368 pub fn as_os_str(&self) -> &OsStr {
369 self.as_ref()
370 }
371}
372
353bbf73
WB
373impl AsRef<[u8]> for Hardlink {
374 fn as_ref(&self) -> &[u8] {
375 &self.data
376 }
377}
378
54912ae6
WB
379impl AsRef<OsStr> for Hardlink {
380 fn as_ref(&self) -> &OsStr {
353bbf73 381 OsStr::from_bytes(&self.data[..self.data.len().max(1) - 1])
54912ae6
WB
382 }
383}
384
6cd4f635
WB
385#[derive(Clone, Debug, Eq)]
386#[repr(C)]
387pub struct XAttr {
388 pub(crate) data: Vec<u8>,
389 pub(crate) name_len: usize,
390}
391
392impl XAttr {
393 pub fn new<N: AsRef<[u8]>, V: AsRef<[u8]>>(name: N, value: V) -> Self {
394 let name = name.as_ref();
395 let value = value.as_ref();
396 let mut data = Vec::with_capacity(name.len() + value.len() + 1);
397 data.extend(name);
398 data.push(0);
399 data.extend(value);
400 Self {
401 data,
402 name_len: name.len(),
403 }
404 }
405
2c442007 406 pub fn name(&self) -> &CStr {
2fd112ac 407 unsafe { CStr::from_bytes_with_nul_unchecked(&self.data[..self.name_len + 1]) }
6cd4f635
WB
408 }
409
410 pub fn value(&self) -> &[u8] {
411 &self.data[(self.name_len + 1)..]
412 }
413}
414
415impl Ord for XAttr {
416 fn cmp(&self, other: &XAttr) -> Ordering {
417 self.name().cmp(&other.name())
418 }
419}
420
421impl PartialOrd for XAttr {
422 fn partial_cmp(&self, other: &XAttr) -> Option<Ordering> {
423 Some(self.cmp(other))
424 }
425}
426
427impl PartialEq for XAttr {
428 fn eq(&self, other: &XAttr) -> bool {
429 self.name() == other.name()
430 }
431}
432
433#[derive(Clone, Debug, Endian)]
434#[repr(C)]
435pub struct Device {
436 pub major: u64,
437 pub minor: u64,
438}
439
9cda3431
WB
440#[cfg(target_os = "linux")]
441impl Device {
442 /// Get a `dev_t` value for this device.
443 #[rustfmt::skip]
444 pub fn to_dev_t(&self) -> u64 {
445 // see bits/sysmacros.h
446 ((self.major & 0x0000_0fff) << 8) |
447 ((self.major & 0xffff_f000) << 32) |
448 (self.minor & 0x0000_00ff) |
449 ((self.minor & 0xffff_ff00) << 12)
450 }
2fd112ac
WB
451
452 /// Get a `Device` from a `dev_t` value.
453 #[rustfmt::skip]
454 pub fn from_dev_t(dev: u64) -> Self {
455 // see to_dev_t
456 Self {
457 major: (dev >> 8) & 0x0000_0fff |
458 (dev >> 32) & 0xffff_f000,
459 minor: dev & 0x0000_00ff |
460 (dev >> 12) & 0xffff_ff00,
461 }
462 }
463}
464
465#[cfg(all(test, target_os = "linux"))]
466#[test]
467fn test_linux_devices() {
468 let c_dev = unsafe { ::libc::makedev(0xabcd_1234, 0xdcba_5678) };
469 let dev = Device::from_dev_t(c_dev);
470 assert_eq!(dev.to_dev_t(), c_dev);
9cda3431
WB
471}
472
6cd4f635
WB
473#[derive(Clone, Debug)]
474#[repr(C)]
475pub struct FCaps {
476 pub data: Vec<u8>,
477}
478
3680d0ee 479#[derive(Clone, Copy, Debug, Endian)]
6cd4f635
WB
480#[repr(C)]
481pub struct QuotaProjectId {
482 pub projid: u64,
483}
484
70acf637 485#[derive(Clone, Debug, Endian)]
6cd4f635
WB
486#[repr(C)]
487pub struct GoodbyeItem {
488 /// SipHash24 of the directory item name. The last GOODBYE item uses the special hash value
489 /// `PXAR_GOODBYE_TAIL_MARKER`.
490 pub hash: u64,
491
492 /// The offset from the start of the GOODBYE object to the start of the matching directory item
493 /// (point to a FILENAME). The last GOODBYE item points to the start of the matching ENTRY
494 /// object.
495 pub offset: u64,
496
497 /// The overall size of the directory item. This includes the FILENAME header. In other words,
498 /// `goodbye_start - offset + size` points to the end of the directory.
499 ///
500 /// The last GOODBYE item repeats the size of the GOODBYE item.
501 pub size: u64,
502}
503
504impl GoodbyeItem {
505 pub fn new(name: &[u8], offset: u64, size: u64) -> Self {
506 let hash = hash_filename(name);
507 Self { hash, offset, size }
508 }
509}
510
511pub fn hash_filename(name: &[u8]) -> u64 {
512 use std::hash::Hasher;
513 let mut hasher = SipHasher24::new_with_keys(0x8574442b0f1d84b3, 0x2736ed30d1c22ec1);
514 hasher.write(name);
515 hasher.finish()
516}
517
6cd4f635
WB
518pub fn path_is_legal_component(path: &Path) -> bool {
519 let mut components = path.components();
520 match components.next() {
521 Some(std::path::Component::Normal(_)) => (),
522 _ => return false,
523 }
524 components.next().is_none()
525}
526
527pub fn check_file_name(path: &Path) -> io::Result<()> {
528 if !path_is_legal_component(path) {
529 io_bail!("invalid file name in archive: {:?}", path);
530 } else {
531 Ok(())
532 }
533}