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