]> git.proxmox.com Git - pxar.git/blob - src/format.rs
eec3676f0d53b6f2687a6e757a7f09ee917be661
[pxar.git] / src / format.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
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
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
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 }
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 }
177 }
178
179 /// Convenience methods.
180 impl Entry {
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
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
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
219 /// Check whether this is a regular file.
220 pub fn is_regular_file(&self) -> bool {
221 (self.mode & mode::IFMT) == mode::IFREG
222 }
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 }
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())
246 .mode(meta.mode() as u64)
247 .mtime(meta.mtime() as u64);
248
249 let file_type = meta.file_type();
250 let mode = this.mode;
251 let this = if file_type.is_dir() {
252 this.mode(mode | mode::IFDIR)
253 } else if file_type.is_symlink() {
254 this.mode(mode | mode::IFLNK)
255 } else {
256 this.mode(mode | mode::IFREG)
257 };
258
259 this
260 }
261 }
262
263 #[derive(Clone, Debug)]
264 pub struct Filename {
265 pub name: Vec<u8>,
266 }
267
268 #[derive(Clone, Debug)]
269 pub struct Symlink {
270 pub data: Vec<u8>,
271 }
272
273 impl Symlink {
274 pub fn as_os_str(&self) -> &OsStr {
275 self.as_ref()
276 }
277 }
278
279 impl AsRef<[u8]> for Symlink {
280 fn as_ref(&self) -> &[u8] {
281 &self.data
282 }
283 }
284
285 impl AsRef<OsStr> for Symlink {
286 fn as_ref(&self) -> &OsStr {
287 OsStr::from_bytes(&self.data[..self.data.len().max(1) - 1])
288 }
289 }
290
291 #[derive(Clone, Debug)]
292 pub struct Hardlink {
293 pub offset: u64,
294 pub data: Vec<u8>,
295 }
296
297 impl Hardlink {
298 pub fn as_os_str(&self) -> &OsStr {
299 self.as_ref()
300 }
301 }
302
303 impl AsRef<[u8]> for Hardlink {
304 fn as_ref(&self) -> &[u8] {
305 &self.data
306 }
307 }
308
309 impl AsRef<OsStr> for Hardlink {
310 fn as_ref(&self) -> &OsStr {
311 OsStr::from_bytes(&self.data[..self.data.len().max(1) - 1])
312 }
313 }
314
315 #[derive(Clone, Debug, Eq)]
316 #[repr(C)]
317 pub struct XAttr {
318 pub(crate) data: Vec<u8>,
319 pub(crate) name_len: usize,
320 }
321
322 impl XAttr {
323 pub fn new<N: AsRef<[u8]>, V: AsRef<[u8]>>(name: N, value: V) -> Self {
324 let name = name.as_ref();
325 let value = value.as_ref();
326 let mut data = Vec::with_capacity(name.len() + value.len() + 1);
327 data.extend(name);
328 data.push(0);
329 data.extend(value);
330 Self {
331 data,
332 name_len: name.len(),
333 }
334 }
335
336 pub fn name(&self) -> &CStr {
337 unsafe { CStr::from_bytes_with_nul_unchecked(&self.data[..self.name_len + 1]) }
338 }
339
340 pub fn value(&self) -> &[u8] {
341 &self.data[(self.name_len + 1)..]
342 }
343 }
344
345 impl Ord for XAttr {
346 fn cmp(&self, other: &XAttr) -> Ordering {
347 self.name().cmp(&other.name())
348 }
349 }
350
351 impl PartialOrd for XAttr {
352 fn partial_cmp(&self, other: &XAttr) -> Option<Ordering> {
353 Some(self.cmp(other))
354 }
355 }
356
357 impl PartialEq for XAttr {
358 fn eq(&self, other: &XAttr) -> bool {
359 self.name() == other.name()
360 }
361 }
362
363 #[derive(Clone, Debug, Endian)]
364 #[repr(C)]
365 pub struct Device {
366 pub major: u64,
367 pub minor: u64,
368 }
369
370 #[cfg(target_os = "linux")]
371 impl Device {
372 /// Get a `dev_t` value for this device.
373 #[rustfmt::skip]
374 pub fn to_dev_t(&self) -> u64 {
375 // see bits/sysmacros.h
376 ((self.major & 0x0000_0fff) << 8) |
377 ((self.major & 0xffff_f000) << 32) |
378 (self.minor & 0x0000_00ff) |
379 ((self.minor & 0xffff_ff00) << 12)
380 }
381
382 /// Get a `Device` from a `dev_t` value.
383 #[rustfmt::skip]
384 pub fn from_dev_t(dev: u64) -> Self {
385 // see to_dev_t
386 Self {
387 major: (dev >> 8) & 0x0000_0fff |
388 (dev >> 32) & 0xffff_f000,
389 minor: dev & 0x0000_00ff |
390 (dev >> 12) & 0xffff_ff00,
391 }
392 }
393 }
394
395 #[cfg(all(test, target_os = "linux"))]
396 #[test]
397 fn test_linux_devices() {
398 let c_dev = unsafe { ::libc::makedev(0xabcd_1234, 0xdcba_5678) };
399 let dev = Device::from_dev_t(c_dev);
400 assert_eq!(dev.to_dev_t(), c_dev);
401 }
402
403 #[derive(Clone, Debug)]
404 #[repr(C)]
405 pub struct FCaps {
406 pub data: Vec<u8>,
407 }
408
409 #[derive(Clone, Copy, Debug, Endian)]
410 #[repr(C)]
411 pub struct QuotaProjectId {
412 pub projid: u64,
413 }
414
415 #[derive(Clone, Debug, Endian)]
416 #[repr(C)]
417 pub struct GoodbyeItem {
418 /// SipHash24 of the directory item name. The last GOODBYE item uses the special hash value
419 /// `PXAR_GOODBYE_TAIL_MARKER`.
420 pub hash: u64,
421
422 /// The offset from the start of the GOODBYE object to the start of the matching directory item
423 /// (point to a FILENAME). The last GOODBYE item points to the start of the matching ENTRY
424 /// object.
425 pub offset: u64,
426
427 /// The overall size of the directory item. This includes the FILENAME header. In other words,
428 /// `goodbye_start - offset + size` points to the end of the directory.
429 ///
430 /// The last GOODBYE item repeats the size of the GOODBYE item.
431 pub size: u64,
432 }
433
434 impl GoodbyeItem {
435 pub fn new(name: &[u8], offset: u64, size: u64) -> Self {
436 let hash = hash_filename(name);
437 Self { hash, offset, size }
438 }
439 }
440
441 pub fn hash_filename(name: &[u8]) -> u64 {
442 use std::hash::Hasher;
443 let mut hasher = SipHasher24::new_with_keys(0x8574442b0f1d84b3, 0x2736ed30d1c22ec1);
444 hasher.write(name);
445 hasher.finish()
446 }
447
448 pub fn path_is_legal_component(path: &Path) -> bool {
449 let mut components = path.components();
450 match components.next() {
451 Some(std::path::Component::Normal(_)) => (),
452 _ => return false,
453 }
454 components.next().is_none()
455 }
456
457 pub fn check_file_name(path: &Path) -> io::Result<()> {
458 if !path_is_legal_component(path) {
459 io_bail!("invalid file name in archive: {:?}", path);
460 } else {
461 Ok(())
462 }
463 }