]>
Commit | Line | Data |
---|---|---|
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 | ||
8 | use std::cmp::Ordering; | |
2c442007 | 9 | use std::ffi::{CStr, OsStr}; |
4a13b8a3 FG |
10 | use std::fmt; |
11 | use std::fmt::Display; | |
6cd4f635 WB |
12 | use std::io; |
13 | use std::mem::size_of; | |
54912ae6 | 14 | use std::os::unix::ffi::OsStrExt; |
6cd4f635 WB |
15 | use std::path::Path; |
16 | ||
17 | use endian_trait::Endian; | |
18 | use siphasher::sip::SipHasher24; | |
19 | ||
20 | pub 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] | |
29 | pub 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 |
45 | pub const PXAR_ENTRY: u64 = 0x1396fabcea5bbb51; |
46 | pub const PXAR_FILENAME: u64 = 0x6dbb6ebcb3161f0b; | |
47 | pub const PXAR_SYMLINK: u64 = 0x664a6fb6830e0d6c; | |
48 | pub const PXAR_DEVICE: u64 = 0xac3dace369dfe643; | |
49 | pub const PXAR_XATTR: u64 = 0xb8157091f80bc486; | |
50 | pub const PXAR_ACL_USER: u64 = 0x297dc88b2ef12faf; | |
51 | pub const PXAR_ACL_GROUP: u64 = 0x36f2acb56cb3dd0b; | |
52 | pub const PXAR_ACL_GROUP_OBJ: u64 = 0x23047110441f38f3; | |
53 | pub const PXAR_ACL_DEFAULT: u64 = 0xfe3eeda6823c8cd0; | |
54 | pub const PXAR_ACL_DEFAULT_USER: u64 = 0xbdf03df9bd010a91; | |
55 | pub const PXAR_ACL_DEFAULT_GROUP: u64 = 0xa0cb1168782d1f51; | |
56 | pub const PXAR_FCAPS: u64 = 0xf7267db0afed0629; | |
57 | pub const PXAR_QUOTA_PROJID: u64 = 0x161baf2d8772a72b; | |
58 | ||
59 | /// Marks item as hardlink | |
60 | /// compute_goodbye_hash(b"__PROXMOX_FORMAT_HARDLINK__"); | |
61 | pub const PXAR_HARDLINK: u64 = 0x2c5e06f634f65b86; | |
62 | /// Marks the beginnig of the payload (actual content) of regular files | |
63 | pub const PXAR_PAYLOAD: u64 = 0x8b9e1d93d6dcffc9; | |
64 | /// Marks item as entry of goodbye table | |
65 | pub const PXAR_GOODBYE: u64 = 0xdfd35c5e8327c403; | |
66 | /// The end marker used in the GOODBYE object | |
67 | pub const PXAR_GOODBYE_TAIL_MARKER: u64 = 0x57446fa533702943; | |
68 | ||
69 | #[derive(Debug, Endian)] | |
70 | #[repr(C)] | |
71 | pub 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 | ||
78 | impl 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 | |
134 | impl 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)] | |
161 | pub 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. |
170 | impl 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. |
223 | impl 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. |
241 | impl 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 | ||
296 | impl 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)] |
334 | pub struct Filename { | |
335 | pub name: Vec<u8>, | |
336 | } | |
337 | ||
338 | #[derive(Clone, Debug)] | |
339 | pub struct Symlink { | |
340 | pub data: Vec<u8>, | |
341 | } | |
342 | ||
7b389f88 WB |
343 | impl Symlink { |
344 | pub fn as_os_str(&self) -> &OsStr { | |
345 | self.as_ref() | |
346 | } | |
347 | } | |
348 | ||
353bbf73 WB |
349 | impl AsRef<[u8]> for Symlink { |
350 | fn as_ref(&self) -> &[u8] { | |
351 | &self.data | |
352 | } | |
353 | } | |
354 | ||
54912ae6 WB |
355 | impl 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)] |
362 | pub struct Hardlink { | |
b0752929 | 363 | pub offset: u64, |
6cd4f635 WB |
364 | pub data: Vec<u8>, |
365 | } | |
366 | ||
7b389f88 WB |
367 | impl Hardlink { |
368 | pub fn as_os_str(&self) -> &OsStr { | |
369 | self.as_ref() | |
370 | } | |
371 | } | |
372 | ||
353bbf73 WB |
373 | impl AsRef<[u8]> for Hardlink { |
374 | fn as_ref(&self) -> &[u8] { | |
375 | &self.data | |
376 | } | |
377 | } | |
378 | ||
54912ae6 WB |
379 | impl 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)] | |
387 | pub struct XAttr { | |
388 | pub(crate) data: Vec<u8>, | |
389 | pub(crate) name_len: usize, | |
390 | } | |
391 | ||
392 | impl 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 | ||
415 | impl Ord for XAttr { | |
416 | fn cmp(&self, other: &XAttr) -> Ordering { | |
417 | self.name().cmp(&other.name()) | |
418 | } | |
419 | } | |
420 | ||
421 | impl PartialOrd for XAttr { | |
422 | fn partial_cmp(&self, other: &XAttr) -> Option<Ordering> { | |
423 | Some(self.cmp(other)) | |
424 | } | |
425 | } | |
426 | ||
427 | impl 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)] | |
435 | pub struct Device { | |
436 | pub major: u64, | |
437 | pub minor: u64, | |
438 | } | |
439 | ||
9cda3431 WB |
440 | #[cfg(target_os = "linux")] |
441 | impl 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] | |
467 | fn 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)] | |
475 | pub struct FCaps { | |
476 | pub data: Vec<u8>, | |
477 | } | |
478 | ||
3680d0ee | 479 | #[derive(Clone, Copy, Debug, Endian)] |
6cd4f635 WB |
480 | #[repr(C)] |
481 | pub struct QuotaProjectId { | |
482 | pub projid: u64, | |
483 | } | |
484 | ||
70acf637 | 485 | #[derive(Clone, Debug, Endian)] |
6cd4f635 WB |
486 | #[repr(C)] |
487 | pub 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 | ||
504 | impl 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 | ||
511 | pub 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 |
518 | pub 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 | ||
527 | pub 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 | } |