]>
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}; |
6cd4f635 WB |
10 | use std::io; |
11 | use std::mem::size_of; | |
54912ae6 | 12 | use std::os::unix::ffi::OsStrExt; |
6cd4f635 WB |
13 | use std::path::Path; |
14 | ||
15 | use endian_trait::Endian; | |
16 | use siphasher::sip::SipHasher24; | |
17 | ||
18 | pub mod acl; | |
19 | ||
70acf637 WB |
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; | |
a13a2f74 | 37 | |
70acf637 WB |
38 | pub const ISUID : u64 = 0o0004000; |
39 | pub const ISGID : u64 = 0o0002000; | |
40 | pub const ISVTX : u64 = 0o0001000; | |
41 | } | |
42 | ||
6cd4f635 WB |
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 { | |
70acf637 WB |
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 | ||
6cd4f635 WB |
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 | ||
98 | #[derive(Clone, Debug, Default, Endian)] | |
99 | #[repr(C)] | |
100 | pub struct Entry { | |
101 | pub mode: u64, | |
102 | pub flags: u64, | |
103 | pub uid: u32, | |
104 | pub gid: u32, | |
105 | pub mtime: u64, | |
106 | } | |
107 | ||
70acf637 WB |
108 | /// Builder pattern methods. |
109 | impl Entry { | |
110 | pub const fn mode(self, mode: u64) -> Self { | |
111 | Self { mode, ..self } | |
112 | } | |
113 | ||
114 | pub const fn flags(self, flags: u64) -> Self { | |
115 | Self { flags, ..self } | |
116 | } | |
117 | ||
118 | pub const fn uid(self, uid: u32) -> Self { | |
119 | Self { uid, ..self } | |
120 | } | |
121 | ||
122 | pub const fn gid(self, gid: u32) -> Self { | |
123 | Self { gid, ..self } | |
124 | } | |
125 | ||
126 | pub const fn mtime(self, mtime: u64) -> Self { | |
127 | Self { mtime, ..self } | |
128 | } | |
129 | ||
130 | pub const fn set_dir(self) -> Self { | |
131 | let mode = self.mode; | |
132 | self.mode((mode & !mode::IFMT) | mode::IFDIR) | |
133 | } | |
134 | ||
135 | pub const fn set_regular_file(self) -> Self { | |
136 | let mode = self.mode; | |
137 | self.mode((mode & !mode::IFMT) | mode::IFREG) | |
138 | } | |
139 | ||
140 | pub const fn set_symlink(self) -> Self { | |
141 | let mode = self.mode; | |
142 | self.mode((mode & !mode::IFMT) | mode::IFLNK) | |
143 | } | |
144 | ||
145 | pub const fn set_blockdev(self) -> Self { | |
146 | let mode = self.mode; | |
147 | self.mode((mode & !mode::IFMT) | mode::IFBLK) | |
148 | } | |
149 | ||
150 | pub const fn set_chardev(self) -> Self { | |
151 | let mode = self.mode; | |
152 | self.mode((mode & !mode::IFMT) | mode::IFCHR) | |
153 | } | |
154 | ||
155 | pub const fn set_fifo(self) -> Self { | |
156 | let mode = self.mode; | |
157 | self.mode((mode & !mode::IFMT) | mode::IFIFO) | |
158 | } | |
159 | } | |
160 | ||
6da548dd WB |
161 | /// Convenience accessor methods. |
162 | impl Entry { | |
163 | /// Get the mtime as duration since the epoch. | |
164 | pub fn mtime_as_duration(&self) -> std::time::Duration { | |
165 | std::time::Duration::from_nanos(self.mtime) | |
166 | } | |
8617cae3 WB |
167 | |
168 | /// Get the file type portion of the mode bitfield. | |
169 | pub fn get_file_bits(&self) -> u64 { | |
170 | self.mode & mode::IFMT | |
171 | } | |
172 | ||
173 | /// Get the permission portion of the mode bitfield. | |
174 | pub fn get_permission_bits(&self) -> u64 { | |
175 | self.mode & !mode::IFMT | |
176 | } | |
6da548dd WB |
177 | } |
178 | ||
70acf637 WB |
179 | /// Convenience methods. |
180 | impl Entry { | |
21d66e10 WB |
181 | /// Get the file type (`mode & mode::IFMT`). |
182 | pub fn file_type(&self) -> u64 { | |
183 | self.mode & mode::IFMT | |
184 | } | |
185 | ||
186 | /// Get the file mode bits (`mode & !mode::IFMT`). | |
187 | pub fn file_mode(&self) -> u64 { | |
188 | self.mode & !mode::IFMT | |
189 | } | |
190 | ||
70acf637 WB |
191 | /// Check whether this is a directory. |
192 | pub fn is_dir(&self) -> bool { | |
193 | (self.mode & mode::IFMT) == mode::IFDIR | |
194 | } | |
195 | ||
196 | /// Check whether this is a symbolic link. | |
197 | pub fn is_symlink(&self) -> bool { | |
198 | (self.mode & mode::IFMT) == mode::IFLNK | |
199 | } | |
200 | ||
201 | /// Check whether this is a device node. | |
202 | pub fn is_device(&self) -> bool { | |
203 | let fmt = self.mode & mode::IFMT; | |
204 | fmt == mode::IFCHR || fmt == mode::IFBLK | |
205 | } | |
206 | ||
da286c28 WB |
207 | /// Check whether this is a block device node. |
208 | pub fn is_blockdev(&self) -> bool { | |
209 | let fmt = self.mode & mode::IFMT; | |
210 | fmt == mode::IFBLK | |
211 | } | |
212 | ||
213 | /// Check whether this is a character device node. | |
214 | pub fn is_chardev(&self) -> bool { | |
215 | let fmt = self.mode & mode::IFMT; | |
216 | fmt == mode::IFCHR | |
217 | } | |
218 | ||
70acf637 WB |
219 | /// Check whether this is a regular file. |
220 | pub fn is_regular_file(&self) -> bool { | |
221 | (self.mode & mode::IFMT) == mode::IFREG | |
222 | } | |
2287d8b2 WB |
223 | |
224 | /// Check whether this is a named pipe (FIFO). | |
225 | pub fn is_fifo(&self) -> bool { | |
226 | (self.mode & mode::IFMT) == mode::IFIFO | |
227 | } | |
228 | ||
229 | /// Check whether this is a named socket. | |
230 | pub fn is_socket(&self) -> bool { | |
231 | (self.mode & mode::IFMT) == mode::IFSOCK | |
232 | } | |
70acf637 WB |
233 | } |
234 | ||
235 | impl From<&std::fs::Metadata> for Entry { | |
236 | fn from(meta: &std::fs::Metadata) -> Entry { | |
237 | #[cfg(unix)] | |
238 | use std::os::unix::fs::MetadataExt; | |
239 | ||
240 | let this = Entry::default(); | |
241 | ||
242 | #[cfg(unix)] | |
243 | let this = this | |
244 | .uid(meta.uid()) | |
245 | .gid(meta.gid()) | |
0a6c7350 WB |
246 | .mode(meta.mode() as u64); |
247 | ||
248 | let this = match meta.modified() { | |
1250e3ea WB |
249 | Ok(mtime) => this.mtime( |
250 | mtime | |
0a6c7350 WB |
251 | .duration_since(std::time::SystemTime::UNIX_EPOCH) |
252 | .map(|dur| dur.as_nanos() as u64) | |
1250e3ea WB |
253 | .unwrap_or(0u64), |
254 | ), | |
0a6c7350 WB |
255 | Err(_) => this, |
256 | }; | |
70acf637 WB |
257 | |
258 | let file_type = meta.file_type(); | |
259 | let mode = this.mode; | |
260 | let this = if file_type.is_dir() { | |
261 | this.mode(mode | mode::IFDIR) | |
262 | } else if file_type.is_symlink() { | |
263 | this.mode(mode | mode::IFLNK) | |
264 | } else { | |
265 | this.mode(mode | mode::IFREG) | |
266 | }; | |
267 | ||
268 | this | |
269 | } | |
270 | } | |
271 | ||
6cd4f635 WB |
272 | #[derive(Clone, Debug)] |
273 | pub struct Filename { | |
274 | pub name: Vec<u8>, | |
275 | } | |
276 | ||
277 | #[derive(Clone, Debug)] | |
278 | pub struct Symlink { | |
279 | pub data: Vec<u8>, | |
280 | } | |
281 | ||
7b389f88 WB |
282 | impl Symlink { |
283 | pub fn as_os_str(&self) -> &OsStr { | |
284 | self.as_ref() | |
285 | } | |
286 | } | |
287 | ||
353bbf73 WB |
288 | impl AsRef<[u8]> for Symlink { |
289 | fn as_ref(&self) -> &[u8] { | |
290 | &self.data | |
291 | } | |
292 | } | |
293 | ||
54912ae6 WB |
294 | impl AsRef<OsStr> for Symlink { |
295 | fn as_ref(&self) -> &OsStr { | |
353bbf73 | 296 | OsStr::from_bytes(&self.data[..self.data.len().max(1) - 1]) |
54912ae6 WB |
297 | } |
298 | } | |
299 | ||
6cd4f635 WB |
300 | #[derive(Clone, Debug)] |
301 | pub struct Hardlink { | |
b0752929 | 302 | pub offset: u64, |
6cd4f635 WB |
303 | pub data: Vec<u8>, |
304 | } | |
305 | ||
7b389f88 WB |
306 | impl Hardlink { |
307 | pub fn as_os_str(&self) -> &OsStr { | |
308 | self.as_ref() | |
309 | } | |
310 | } | |
311 | ||
353bbf73 WB |
312 | impl AsRef<[u8]> for Hardlink { |
313 | fn as_ref(&self) -> &[u8] { | |
314 | &self.data | |
315 | } | |
316 | } | |
317 | ||
54912ae6 WB |
318 | impl AsRef<OsStr> for Hardlink { |
319 | fn as_ref(&self) -> &OsStr { | |
353bbf73 | 320 | OsStr::from_bytes(&self.data[..self.data.len().max(1) - 1]) |
54912ae6 WB |
321 | } |
322 | } | |
323 | ||
6cd4f635 WB |
324 | #[derive(Clone, Debug, Eq)] |
325 | #[repr(C)] | |
326 | pub struct XAttr { | |
327 | pub(crate) data: Vec<u8>, | |
328 | pub(crate) name_len: usize, | |
329 | } | |
330 | ||
331 | impl XAttr { | |
332 | pub fn new<N: AsRef<[u8]>, V: AsRef<[u8]>>(name: N, value: V) -> Self { | |
333 | let name = name.as_ref(); | |
334 | let value = value.as_ref(); | |
335 | let mut data = Vec::with_capacity(name.len() + value.len() + 1); | |
336 | data.extend(name); | |
337 | data.push(0); | |
338 | data.extend(value); | |
339 | Self { | |
340 | data, | |
341 | name_len: name.len(), | |
342 | } | |
343 | } | |
344 | ||
2c442007 | 345 | pub fn name(&self) -> &CStr { |
2fd112ac | 346 | unsafe { CStr::from_bytes_with_nul_unchecked(&self.data[..self.name_len + 1]) } |
6cd4f635 WB |
347 | } |
348 | ||
349 | pub fn value(&self) -> &[u8] { | |
350 | &self.data[(self.name_len + 1)..] | |
351 | } | |
352 | } | |
353 | ||
354 | impl Ord for XAttr { | |
355 | fn cmp(&self, other: &XAttr) -> Ordering { | |
356 | self.name().cmp(&other.name()) | |
357 | } | |
358 | } | |
359 | ||
360 | impl PartialOrd for XAttr { | |
361 | fn partial_cmp(&self, other: &XAttr) -> Option<Ordering> { | |
362 | Some(self.cmp(other)) | |
363 | } | |
364 | } | |
365 | ||
366 | impl PartialEq for XAttr { | |
367 | fn eq(&self, other: &XAttr) -> bool { | |
368 | self.name() == other.name() | |
369 | } | |
370 | } | |
371 | ||
372 | #[derive(Clone, Debug, Endian)] | |
373 | #[repr(C)] | |
374 | pub struct Device { | |
375 | pub major: u64, | |
376 | pub minor: u64, | |
377 | } | |
378 | ||
9cda3431 WB |
379 | #[cfg(target_os = "linux")] |
380 | impl Device { | |
381 | /// Get a `dev_t` value for this device. | |
382 | #[rustfmt::skip] | |
383 | pub fn to_dev_t(&self) -> u64 { | |
384 | // see bits/sysmacros.h | |
385 | ((self.major & 0x0000_0fff) << 8) | | |
386 | ((self.major & 0xffff_f000) << 32) | | |
387 | (self.minor & 0x0000_00ff) | | |
388 | ((self.minor & 0xffff_ff00) << 12) | |
389 | } | |
2fd112ac WB |
390 | |
391 | /// Get a `Device` from a `dev_t` value. | |
392 | #[rustfmt::skip] | |
393 | pub fn from_dev_t(dev: u64) -> Self { | |
394 | // see to_dev_t | |
395 | Self { | |
396 | major: (dev >> 8) & 0x0000_0fff | | |
397 | (dev >> 32) & 0xffff_f000, | |
398 | minor: dev & 0x0000_00ff | | |
399 | (dev >> 12) & 0xffff_ff00, | |
400 | } | |
401 | } | |
402 | } | |
403 | ||
404 | #[cfg(all(test, target_os = "linux"))] | |
405 | #[test] | |
406 | fn test_linux_devices() { | |
407 | let c_dev = unsafe { ::libc::makedev(0xabcd_1234, 0xdcba_5678) }; | |
408 | let dev = Device::from_dev_t(c_dev); | |
409 | assert_eq!(dev.to_dev_t(), c_dev); | |
9cda3431 WB |
410 | } |
411 | ||
6cd4f635 WB |
412 | #[derive(Clone, Debug)] |
413 | #[repr(C)] | |
414 | pub struct FCaps { | |
415 | pub data: Vec<u8>, | |
416 | } | |
417 | ||
3680d0ee | 418 | #[derive(Clone, Copy, Debug, Endian)] |
6cd4f635 WB |
419 | #[repr(C)] |
420 | pub struct QuotaProjectId { | |
421 | pub projid: u64, | |
422 | } | |
423 | ||
70acf637 | 424 | #[derive(Clone, Debug, Endian)] |
6cd4f635 WB |
425 | #[repr(C)] |
426 | pub struct GoodbyeItem { | |
427 | /// SipHash24 of the directory item name. The last GOODBYE item uses the special hash value | |
428 | /// `PXAR_GOODBYE_TAIL_MARKER`. | |
429 | pub hash: u64, | |
430 | ||
431 | /// The offset from the start of the GOODBYE object to the start of the matching directory item | |
432 | /// (point to a FILENAME). The last GOODBYE item points to the start of the matching ENTRY | |
433 | /// object. | |
434 | pub offset: u64, | |
435 | ||
436 | /// The overall size of the directory item. This includes the FILENAME header. In other words, | |
437 | /// `goodbye_start - offset + size` points to the end of the directory. | |
438 | /// | |
439 | /// The last GOODBYE item repeats the size of the GOODBYE item. | |
440 | pub size: u64, | |
441 | } | |
442 | ||
443 | impl GoodbyeItem { | |
444 | pub fn new(name: &[u8], offset: u64, size: u64) -> Self { | |
445 | let hash = hash_filename(name); | |
446 | Self { hash, offset, size } | |
447 | } | |
448 | } | |
449 | ||
450 | pub fn hash_filename(name: &[u8]) -> u64 { | |
451 | use std::hash::Hasher; | |
452 | let mut hasher = SipHasher24::new_with_keys(0x8574442b0f1d84b3, 0x2736ed30d1c22ec1); | |
453 | hasher.write(name); | |
454 | hasher.finish() | |
455 | } | |
456 | ||
6cd4f635 WB |
457 | pub fn path_is_legal_component(path: &Path) -> bool { |
458 | let mut components = path.components(); | |
459 | match components.next() { | |
460 | Some(std::path::Component::Normal(_)) => (), | |
461 | _ => return false, | |
462 | } | |
463 | components.next().is_none() | |
464 | } | |
465 | ||
466 | pub fn check_file_name(path: &Path) -> io::Result<()> { | |
467 | if !path_is_legal_component(path) { | |
468 | io_bail!("invalid file name in archive: {:?}", path); | |
469 | } else { | |
470 | Ok(()) | |
471 | } | |
472 | } |