]> git.proxmox.com Git - pxar.git/blame - src/format.rs
hardlinks also contain the offset
[pxar.git] / src / format.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};
6cd4f635
WB
10use std::io;
11use std::mem::size_of;
54912ae6 12use std::os::unix::ffi::OsStrExt;
6cd4f635
WB
13use std::path::Path;
14
15use endian_trait::Endian;
16use siphasher::sip::SipHasher24;
17
18pub 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]
27pub 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
43pub const PXAR_ENTRY: u64 = 0x1396fabcea5bbb51;
44pub const PXAR_FILENAME: u64 = 0x6dbb6ebcb3161f0b;
45pub const PXAR_SYMLINK: u64 = 0x664a6fb6830e0d6c;
46pub const PXAR_DEVICE: u64 = 0xac3dace369dfe643;
47pub const PXAR_XATTR: u64 = 0xb8157091f80bc486;
48pub const PXAR_ACL_USER: u64 = 0x297dc88b2ef12faf;
49pub const PXAR_ACL_GROUP: u64 = 0x36f2acb56cb3dd0b;
50pub const PXAR_ACL_GROUP_OBJ: u64 = 0x23047110441f38f3;
51pub const PXAR_ACL_DEFAULT: u64 = 0xfe3eeda6823c8cd0;
52pub const PXAR_ACL_DEFAULT_USER: u64 = 0xbdf03df9bd010a91;
53pub const PXAR_ACL_DEFAULT_GROUP: u64 = 0xa0cb1168782d1f51;
54pub const PXAR_FCAPS: u64 = 0xf7267db0afed0629;
55pub const PXAR_QUOTA_PROJID: u64 = 0x161baf2d8772a72b;
56
57/// Marks item as hardlink
58/// compute_goodbye_hash(b"__PROXMOX_FORMAT_HARDLINK__");
59pub const PXAR_HARDLINK: u64 = 0x2c5e06f634f65b86;
60/// Marks the beginnig of the payload (actual content) of regular files
61pub const PXAR_PAYLOAD: u64 = 0x8b9e1d93d6dcffc9;
62/// Marks item as entry of goodbye table
63pub const PXAR_GOODBYE: u64 = 0xdfd35c5e8327c403;
64/// The end marker used in the GOODBYE object
65pub const PXAR_GOODBYE_TAIL_MARKER: u64 = 0x57446fa533702943;
66
67#[derive(Debug, Endian)]
68#[repr(C)]
69pub 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
76impl 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)]
100pub 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.
109impl 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.
162impl 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.
180impl 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
235impl 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
6cd4f635
WB
263#[derive(Clone, Debug)]
264pub struct Filename {
265 pub name: Vec<u8>,
266}
267
268#[derive(Clone, Debug)]
269pub struct Symlink {
270 pub data: Vec<u8>,
271}
272
7b389f88
WB
273impl Symlink {
274 pub fn as_os_str(&self) -> &OsStr {
275 self.as_ref()
276 }
277}
278
353bbf73
WB
279impl AsRef<[u8]> for Symlink {
280 fn as_ref(&self) -> &[u8] {
281 &self.data
282 }
283}
284
54912ae6
WB
285impl AsRef<OsStr> for Symlink {
286 fn as_ref(&self) -> &OsStr {
353bbf73 287 OsStr::from_bytes(&self.data[..self.data.len().max(1) - 1])
54912ae6
WB
288 }
289}
290
6cd4f635
WB
291#[derive(Clone, Debug)]
292pub struct Hardlink {
b0752929 293 pub offset: u64,
6cd4f635
WB
294 pub data: Vec<u8>,
295}
296
7b389f88
WB
297impl Hardlink {
298 pub fn as_os_str(&self) -> &OsStr {
299 self.as_ref()
300 }
301}
302
353bbf73
WB
303impl AsRef<[u8]> for Hardlink {
304 fn as_ref(&self) -> &[u8] {
305 &self.data
306 }
307}
308
54912ae6
WB
309impl AsRef<OsStr> for Hardlink {
310 fn as_ref(&self) -> &OsStr {
353bbf73 311 OsStr::from_bytes(&self.data[..self.data.len().max(1) - 1])
54912ae6
WB
312 }
313}
314
6cd4f635
WB
315#[derive(Clone, Debug, Eq)]
316#[repr(C)]
317pub struct XAttr {
318 pub(crate) data: Vec<u8>,
319 pub(crate) name_len: usize,
320}
321
322impl 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
2c442007 336 pub fn name(&self) -> &CStr {
2fd112ac 337 unsafe { CStr::from_bytes_with_nul_unchecked(&self.data[..self.name_len + 1]) }
6cd4f635
WB
338 }
339
340 pub fn value(&self) -> &[u8] {
341 &self.data[(self.name_len + 1)..]
342 }
343}
344
345impl Ord for XAttr {
346 fn cmp(&self, other: &XAttr) -> Ordering {
347 self.name().cmp(&other.name())
348 }
349}
350
351impl PartialOrd for XAttr {
352 fn partial_cmp(&self, other: &XAttr) -> Option<Ordering> {
353 Some(self.cmp(other))
354 }
355}
356
357impl 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)]
365pub struct Device {
366 pub major: u64,
367 pub minor: u64,
368}
369
9cda3431
WB
370#[cfg(target_os = "linux")]
371impl 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 }
2fd112ac
WB
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]
397fn 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);
9cda3431
WB
401}
402
6cd4f635
WB
403#[derive(Clone, Debug)]
404#[repr(C)]
405pub struct FCaps {
406 pub data: Vec<u8>,
407}
408
3680d0ee 409#[derive(Clone, Copy, Debug, Endian)]
6cd4f635
WB
410#[repr(C)]
411pub struct QuotaProjectId {
412 pub projid: u64,
413}
414
70acf637 415#[derive(Clone, Debug, Endian)]
6cd4f635
WB
416#[repr(C)]
417pub 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
434impl 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
441pub 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
6cd4f635
WB
448pub 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
457pub 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}