]> git.proxmox.com Git - pxar.git/blame - src/format/mod.rs
bump version to 0.1.6-1
[pxar.git] / src / format / mod.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())
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)]
273pub struct Filename {
274 pub name: Vec<u8>,
275}
276
277#[derive(Clone, Debug)]
278pub struct Symlink {
279 pub data: Vec<u8>,
280}
281
7b389f88
WB
282impl Symlink {
283 pub fn as_os_str(&self) -> &OsStr {
284 self.as_ref()
285 }
286}
287
353bbf73
WB
288impl AsRef<[u8]> for Symlink {
289 fn as_ref(&self) -> &[u8] {
290 &self.data
291 }
292}
293
54912ae6
WB
294impl 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)]
301pub struct Hardlink {
b0752929 302 pub offset: u64,
6cd4f635
WB
303 pub data: Vec<u8>,
304}
305
7b389f88
WB
306impl Hardlink {
307 pub fn as_os_str(&self) -> &OsStr {
308 self.as_ref()
309 }
310}
311
353bbf73
WB
312impl AsRef<[u8]> for Hardlink {
313 fn as_ref(&self) -> &[u8] {
314 &self.data
315 }
316}
317
54912ae6
WB
318impl 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)]
326pub struct XAttr {
327 pub(crate) data: Vec<u8>,
328 pub(crate) name_len: usize,
329}
330
331impl 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
354impl Ord for XAttr {
355 fn cmp(&self, other: &XAttr) -> Ordering {
356 self.name().cmp(&other.name())
357 }
358}
359
360impl PartialOrd for XAttr {
361 fn partial_cmp(&self, other: &XAttr) -> Option<Ordering> {
362 Some(self.cmp(other))
363 }
364}
365
366impl 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)]
374pub struct Device {
375 pub major: u64,
376 pub minor: u64,
377}
378
9cda3431
WB
379#[cfg(target_os = "linux")]
380impl 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]
406fn 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)]
414pub struct FCaps {
415 pub data: Vec<u8>,
416}
417
3680d0ee 418#[derive(Clone, Copy, Debug, Endian)]
6cd4f635
WB
419#[repr(C)]
420pub struct QuotaProjectId {
421 pub projid: u64,
422}
423
70acf637 424#[derive(Clone, Debug, Endian)]
6cd4f635
WB
425#[repr(C)]
426pub 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
443impl 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
450pub 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
457pub 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
466pub 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}