]>
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. | |
f0279c01 WB |
7 | //! |
8 | //! An archive contains items in the following order: | |
9 | //! * `ENTRY` -- containing general stat() data and related bits | |
10 | //! * `XATTR` -- one extended attribute | |
11 | //! * ... -- more of these when there are multiple defined | |
12 | //! * `ACL_USER` -- one `USER ACL` entry | |
13 | //! * ... -- more of these when there are multiple defined | |
14 | //! * `ACL_GROUP` -- one `GROUP ACL` entry | |
15 | //! * ... -- more of these when there are multiple defined | |
16 | //! * `ACL_GROUP_OBJ` -- The `ACL_GROUP_OBJ` | |
17 | //! * `ACL_DEFAULT` -- The various default ACL fields if there's one defined | |
18 | //! * `ACL_DEFAULT_USER` -- one USER ACL entry | |
19 | //! * ... -- more of these when multiple are defined | |
20 | //! * `ACL_DEFAULT_GROUP` -- one GROUP ACL entry | |
21 | //! * ... -- more of these when multiple are defined | |
22 | //! * `FCAPS` -- file capability in Linux disk format | |
23 | //! * `QUOTA_PROJECT_ID` -- the ext4/xfs quota project ID | |
24 | //! * `PAYLOAD` -- file contents, if it is one | |
25 | //! * `SYMLINK` -- symlink target, if it is one | |
26 | //! * `DEVICE` -- device major/minor, if it is a block/char device | |
27 | //! | |
28 | //! If we are serializing a directory, then this is followed by: | |
29 | //! | |
30 | //! * `FILENAME` -- name of the first directory entry (strictly ordered!) | |
31 | //! * `<archive>` -- serialization of the first directory entry's metadata and contents, | |
32 | //! following the exact same archive format | |
33 | //! * `FILENAME` -- name of the second directory entry (strictly ordered!) | |
34 | //! * `<archive>` -- serialization of the second directory entry | |
35 | //! * ... | |
36 | //! * `GOODBYE` -- lookup table at the end of a list of directory entries | |
6cd4f635 WB |
37 | |
38 | use std::cmp::Ordering; | |
2c442007 | 39 | use std::ffi::{CStr, OsStr}; |
4a13b8a3 FG |
40 | use std::fmt; |
41 | use std::fmt::Display; | |
6cd4f635 WB |
42 | use std::io; |
43 | use std::mem::size_of; | |
54912ae6 | 44 | use std::os::unix::ffi::OsStrExt; |
6cd4f635 | 45 | use std::path::Path; |
939f2468 | 46 | use std::time::{Duration, SystemTime}; |
6cd4f635 WB |
47 | |
48 | use endian_trait::Endian; | |
49 | use siphasher::sip::SipHasher24; | |
50 | ||
51 | pub mod acl; | |
52 | ||
1b1e52a4 WB |
53 | // generated with: |
54 | // $ echo -n 'PROXMOX ARCHIVE FORMAT' | sha1sum | sed -re 's/^(.{16})(.{16}).*$/0x\1, 0x\2/' | |
55 | pub const PXAR_HASH_KEY_1: u64 = 0x83ac3f1cfbb450db; | |
56 | pub const PXAR_HASH_KEY_2: u64 = 0xaa4f1b6879369fbd; | |
57 | ||
70acf637 WB |
58 | /// While these constants correspond to `libc::S_` constants, we need these to be fixed for the |
59 | /// format itself, so we redefine them here. | |
60 | /// | |
61 | /// Additionally this gets rid of a bunch of casts between u32 and u64. | |
62 | /// | |
63 | /// You can usually find the values for these in `/usr/include/linux/stat.h`. | |
64 | #[rustfmt::skip] | |
65 | pub mod mode { | |
66 | pub const IFMT : u64 = 0o0170000; | |
67 | ||
68 | pub const IFSOCK : u64 = 0o0140000; | |
69 | pub const IFLNK : u64 = 0o0120000; | |
70 | pub const IFREG : u64 = 0o0100000; | |
71 | pub const IFBLK : u64 = 0o0060000; | |
72 | pub const IFDIR : u64 = 0o0040000; | |
73 | pub const IFCHR : u64 = 0o0020000; | |
74 | pub const IFIFO : u64 = 0o0010000; | |
a13a2f74 | 75 | |
70acf637 WB |
76 | pub const ISUID : u64 = 0o0004000; |
77 | pub const ISGID : u64 = 0o0002000; | |
78 | pub const ISVTX : u64 = 0o0001000; | |
79 | } | |
80 | ||
1b1e52a4 WB |
81 | pub const PXAR_ENTRY: u64 = 0x11da850a1c1cceff; |
82 | pub const PXAR_FILENAME: u64 = 0x16701121063917b3; | |
83 | pub const PXAR_SYMLINK: u64 = 0x27f971e7dbf5dc5f; | |
84 | pub const PXAR_DEVICE: u64 = 0x9fc9e906586d5ce9; | |
85 | pub const PXAR_XATTR: u64 = 0x0dab0229b57dcd03; | |
86 | pub const PXAR_ACL_USER: u64 = 0x2ce8540a457d55b8; | |
87 | pub const PXAR_ACL_GROUP: u64 = 0x136e3eceb04c03ab; | |
88 | pub const PXAR_ACL_GROUP_OBJ: u64 = 0x10868031e9582876; | |
89 | pub const PXAR_ACL_DEFAULT: u64 = 0xbbbb13415a6896f5; | |
90 | pub const PXAR_ACL_DEFAULT_USER: u64 = 0xc89357b40532cd1f; | |
91 | pub const PXAR_ACL_DEFAULT_GROUP: u64 = 0xf90a8a5816038ffe; | |
92 | pub const PXAR_FCAPS: u64 = 0x2da9dd9db5f7fb67; | |
93 | pub const PXAR_QUOTA_PROJID: u64 = 0xe07540e82f7d1cbb; | |
6cd4f635 | 94 | /// Marks item as hardlink |
1b1e52a4 | 95 | pub const PXAR_HARDLINK: u64 = 0x51269c8422bd7275; |
6cd4f635 | 96 | /// Marks the beginnig of the payload (actual content) of regular files |
1b1e52a4 | 97 | pub const PXAR_PAYLOAD: u64 = 0x28147a1b0b7c1a25; |
6cd4f635 | 98 | /// Marks item as entry of goodbye table |
1b1e52a4 | 99 | pub const PXAR_GOODBYE: u64 = 0x2fec4fa642d5731d; |
6cd4f635 | 100 | /// The end marker used in the GOODBYE object |
1b1e52a4 | 101 | pub const PXAR_GOODBYE_TAIL_MARKER: u64 = 0xef5eed5b753e1555; |
6cd4f635 WB |
102 | |
103 | #[derive(Debug, Endian)] | |
104 | #[repr(C)] | |
105 | pub struct Header { | |
106 | /// The item type (see `PXAR_` constants). | |
107 | pub htype: u64, | |
108 | /// The size of the item, including the size of `Header`. | |
109 | full_size: u64, | |
110 | } | |
111 | ||
112 | impl Header { | |
70acf637 WB |
113 | #[inline] |
114 | pub fn with_full_size(htype: u64, full_size: u64) -> Self { | |
115 | Self { htype, full_size } | |
116 | } | |
117 | ||
118 | #[inline] | |
119 | pub fn with_content_size(htype: u64, content_size: u64) -> Self { | |
120 | Self::with_full_size(htype, content_size + size_of::<Header>() as u64) | |
121 | } | |
122 | ||
6cd4f635 WB |
123 | #[inline] |
124 | pub fn full_size(&self) -> u64 { | |
125 | self.full_size | |
126 | } | |
127 | ||
128 | #[inline] | |
129 | pub fn content_size(&self) -> u64 { | |
130 | self.full_size() - (size_of::<Self>() as u64) | |
131 | } | |
ec0761f9 FG |
132 | |
133 | #[inline] | |
134 | pub fn max_content_size(&self) -> u64 { | |
135 | match self.htype { | |
136 | // + null-termination | |
137 | PXAR_FILENAME => crate::util::MAX_FILENAME_LEN + 1, | |
138 | // + null-termination | |
139 | PXAR_SYMLINK => crate::util::MAX_PATH_LEN + 1, | |
140 | // + null-termination + offset | |
141 | PXAR_HARDLINK => crate::util::MAX_PATH_LEN + 1 + (size_of::<u64>() as u64), | |
142 | PXAR_DEVICE => size_of::<Device>() as u64, | |
143 | PXAR_XATTR | PXAR_FCAPS => crate::util::MAX_XATTR_LEN, | |
144 | PXAR_ACL_USER | PXAR_ACL_DEFAULT_USER => size_of::<acl::User>() as u64, | |
145 | PXAR_ACL_GROUP | PXAR_ACL_DEFAULT_GROUP => size_of::<acl::Group>() as u64, | |
146 | PXAR_ACL_DEFAULT => size_of::<acl::Default>() as u64, | |
93fef2fd | 147 | PXAR_ACL_GROUP_OBJ => size_of::<acl::GroupObject>() as u64, |
ec0761f9 FG |
148 | PXAR_QUOTA_PROJID => size_of::<QuotaProjectId>() as u64, |
149 | PXAR_ENTRY => size_of::<Entry>() as u64, | |
08bc9bbc WB |
150 | PXAR_PAYLOAD | PXAR_GOODBYE => std::u64::MAX - (size_of::<Self>() as u64), |
151 | _ => std::u64::MAX - (size_of::<Self>() as u64), | |
ec0761f9 FG |
152 | } |
153 | } | |
154 | ||
155 | #[inline] | |
156 | pub fn check_header_size(&self) -> io::Result<()> { | |
157 | if self.full_size() < size_of::<Header>() as u64 { | |
158 | io_bail!("invalid header {} - too small ({})", self, self.full_size()); | |
159 | } | |
160 | ||
161 | if self.content_size() > self.max_content_size() { | |
710e6c8b WB |
162 | io_bail!( |
163 | "invalid content size ({} > {}) of entry with {}", | |
164 | self.content_size(), | |
165 | self.max_content_size(), | |
166 | self | |
167 | ); | |
ec0761f9 FG |
168 | } |
169 | Ok(()) | |
170 | } | |
171 | } | |
4a13b8a3 FG |
172 | |
173 | impl Display for Header { | |
174 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
175 | let readable = match self.htype { | |
176 | PXAR_FILENAME => "FILENAME", | |
177 | PXAR_SYMLINK => "SYMLINK", | |
178 | PXAR_HARDLINK => "HARDLINK", | |
179 | PXAR_DEVICE => "DEVICE", | |
180 | PXAR_XATTR => "XATTR", | |
181 | PXAR_FCAPS => "FCAPS", | |
182 | PXAR_ACL_USER => "ACL_USER", | |
183 | PXAR_ACL_DEFAULT_USER => "ACL_DEFAULT_USER", | |
184 | PXAR_ACL_GROUP => "ACL_GROUP", | |
185 | PXAR_ACL_DEFAULT_GROUP => "ACL_DEFAULT_GROUP", | |
186 | PXAR_ACL_DEFAULT => "ACL_DEFAULT", | |
187 | PXAR_ACL_GROUP_OBJ => "ACL_GROUP_OBJ", | |
188 | PXAR_QUOTA_PROJID => "QUOTA_PROJID", | |
189 | PXAR_ENTRY => "ENTRY", | |
190 | PXAR_PAYLOAD => "PAYLOAD", | |
191 | PXAR_GOODBYE => "GOODBYE", | |
192 | _ => "UNKNOWN", | |
193 | }; | |
194 | write!(f, "{} header ({:x})", readable, self.htype) | |
195 | } | |
6cd4f635 WB |
196 | } |
197 | ||
939f2468 WB |
198 | #[derive(Clone, Debug, Eq, PartialEq)] |
199 | pub enum SignedDuration { | |
200 | Positive(Duration), | |
201 | Negative(Duration), | |
202 | } | |
203 | ||
204 | #[derive(Clone, Debug, Default, Endian, Eq, PartialEq)] | |
205 | #[repr(C)] | |
206 | pub struct StatxTimestamp { | |
207 | /// Seconds since the epoch (unix time). | |
208 | pub secs: i64, | |
209 | ||
210 | /// Nanoseconds since this struct's `secs`. | |
211 | pub nanos: u32, | |
212 | } | |
213 | ||
214 | impl From<SystemTime> for StatxTimestamp { | |
215 | fn from(time: SystemTime) -> Self { | |
216 | match time.duration_since(SystemTime::UNIX_EPOCH) { | |
217 | Ok(positive) => Self::from_duration_since_epoch(positive), | |
218 | Err(negative) => Self::from_duration_before_epoch(negative.duration()), | |
219 | } | |
220 | } | |
221 | } | |
222 | ||
223 | impl StatxTimestamp { | |
224 | /// `const` version of `Default` | |
225 | pub const fn zero() -> Self { | |
226 | Self { secs: 0, nanos: 0 } | |
227 | } | |
228 | ||
229 | /// Turn a positive duration relative to the unix epoch into a time stamp. | |
230 | pub fn from_duration_since_epoch(duration: Duration) -> Self { | |
231 | Self { | |
232 | secs: duration.as_secs() as i64, | |
233 | nanos: duration.subsec_nanos(), | |
234 | } | |
235 | } | |
236 | ||
237 | /// Turn a *negative* duration from relative to the unix epoch into a time stamp. | |
238 | pub fn from_duration_before_epoch(duration: Duration) -> Self { | |
239 | match duration.subsec_nanos() { | |
240 | 0 => Self { | |
241 | secs: duration.as_secs() as i64, | |
242 | nanos: 0, | |
243 | }, | |
244 | nanos => Self { | |
245 | secs: -(duration.as_secs() as i64) - 1, | |
246 | nanos: 1_000_000_000 - nanos, | |
247 | }, | |
248 | } | |
249 | } | |
250 | ||
251 | /// Get the duration since the epoch. an `Ok` value is a positive duration, an `Err` value is a | |
252 | /// negative duration. | |
253 | pub fn to_duration(&self) -> SignedDuration { | |
254 | if self.secs >= 0 { | |
255 | SignedDuration::Positive(Duration::new(self.secs as u64, self.nanos)) | |
256 | } else { | |
257 | SignedDuration::Negative(Duration::new( | |
258 | -(self.secs + 1) as u64, | |
259 | 1_000_000_000 - self.nanos, | |
260 | )) | |
261 | } | |
262 | } | |
263 | ||
264 | /// Get a `std::time::SystemTime` from this time stamp. | |
265 | pub fn system_time(&self) -> SystemTime { | |
266 | match self.to_duration() { | |
267 | SignedDuration::Positive(positive) => SystemTime::UNIX_EPOCH + positive, | |
268 | SignedDuration::Negative(negative) => SystemTime::UNIX_EPOCH - negative, | |
269 | } | |
270 | } | |
271 | } | |
272 | ||
273 | #[test] | |
274 | fn test_statx_timestamp() { | |
275 | const MAY_1_2015_1530: i64 = 1430487000; | |
276 | let system_time = SystemTime::UNIX_EPOCH + Duration::new(MAY_1_2015_1530 as u64, 1_000_000); | |
277 | let tx = StatxTimestamp::from(system_time); | |
278 | assert_eq!( | |
279 | tx, | |
280 | StatxTimestamp { | |
281 | secs: MAY_1_2015_1530, | |
282 | nanos: 1_000_000, | |
283 | } | |
284 | ); | |
285 | assert_eq!(tx.system_time(), system_time); | |
286 | ||
287 | const MAY_1_1960_1530: i64 = -305112600; | |
288 | let system_time = SystemTime::UNIX_EPOCH - Duration::new(-MAY_1_1960_1530 as u64, 1_000_000); | |
289 | let tx = StatxTimestamp::from(system_time); | |
290 | assert_eq!( | |
291 | tx, | |
292 | StatxTimestamp { | |
293 | secs: MAY_1_1960_1530 - 1, | |
294 | nanos: 999_000_000, | |
295 | } | |
296 | ); | |
297 | assert_eq!(tx.system_time(), system_time); | |
298 | } | |
299 | ||
6cd4f635 | 300 | #[derive(Clone, Debug, Default, Endian)] |
0104cf77 | 301 | #[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))] |
6cd4f635 WB |
302 | #[repr(C)] |
303 | pub struct Entry { | |
304 | pub mode: u64, | |
305 | pub flags: u64, | |
306 | pub uid: u32, | |
307 | pub gid: u32, | |
308 | pub mtime: u64, | |
309 | } | |
310 | ||
70acf637 WB |
311 | /// Builder pattern methods. |
312 | impl Entry { | |
313 | pub const fn mode(self, mode: u64) -> Self { | |
314 | Self { mode, ..self } | |
315 | } | |
316 | ||
317 | pub const fn flags(self, flags: u64) -> Self { | |
318 | Self { flags, ..self } | |
319 | } | |
320 | ||
321 | pub const fn uid(self, uid: u32) -> Self { | |
322 | Self { uid, ..self } | |
323 | } | |
324 | ||
325 | pub const fn gid(self, gid: u32) -> Self { | |
326 | Self { gid, ..self } | |
327 | } | |
328 | ||
329 | pub const fn mtime(self, mtime: u64) -> Self { | |
330 | Self { mtime, ..self } | |
331 | } | |
332 | ||
333 | pub const fn set_dir(self) -> Self { | |
334 | let mode = self.mode; | |
335 | self.mode((mode & !mode::IFMT) | mode::IFDIR) | |
336 | } | |
337 | ||
338 | pub const fn set_regular_file(self) -> Self { | |
339 | let mode = self.mode; | |
340 | self.mode((mode & !mode::IFMT) | mode::IFREG) | |
341 | } | |
342 | ||
343 | pub const fn set_symlink(self) -> Self { | |
344 | let mode = self.mode; | |
345 | self.mode((mode & !mode::IFMT) | mode::IFLNK) | |
346 | } | |
347 | ||
348 | pub const fn set_blockdev(self) -> Self { | |
349 | let mode = self.mode; | |
350 | self.mode((mode & !mode::IFMT) | mode::IFBLK) | |
351 | } | |
352 | ||
353 | pub const fn set_chardev(self) -> Self { | |
354 | let mode = self.mode; | |
355 | self.mode((mode & !mode::IFMT) | mode::IFCHR) | |
356 | } | |
357 | ||
358 | pub const fn set_fifo(self) -> Self { | |
359 | let mode = self.mode; | |
360 | self.mode((mode & !mode::IFMT) | mode::IFIFO) | |
361 | } | |
362 | } | |
363 | ||
6da548dd WB |
364 | /// Convenience accessor methods. |
365 | impl Entry { | |
366 | /// Get the mtime as duration since the epoch. | |
367 | pub fn mtime_as_duration(&self) -> std::time::Duration { | |
368 | std::time::Duration::from_nanos(self.mtime) | |
369 | } | |
8617cae3 WB |
370 | |
371 | /// Get the file type portion of the mode bitfield. | |
372 | pub fn get_file_bits(&self) -> u64 { | |
373 | self.mode & mode::IFMT | |
374 | } | |
375 | ||
376 | /// Get the permission portion of the mode bitfield. | |
377 | pub fn get_permission_bits(&self) -> u64 { | |
378 | self.mode & !mode::IFMT | |
379 | } | |
6da548dd WB |
380 | } |
381 | ||
70acf637 WB |
382 | /// Convenience methods. |
383 | impl Entry { | |
21d66e10 WB |
384 | /// Get the file type (`mode & mode::IFMT`). |
385 | pub fn file_type(&self) -> u64 { | |
386 | self.mode & mode::IFMT | |
387 | } | |
388 | ||
389 | /// Get the file mode bits (`mode & !mode::IFMT`). | |
390 | pub fn file_mode(&self) -> u64 { | |
391 | self.mode & !mode::IFMT | |
392 | } | |
393 | ||
70acf637 WB |
394 | /// Check whether this is a directory. |
395 | pub fn is_dir(&self) -> bool { | |
396 | (self.mode & mode::IFMT) == mode::IFDIR | |
397 | } | |
398 | ||
399 | /// Check whether this is a symbolic link. | |
400 | pub fn is_symlink(&self) -> bool { | |
401 | (self.mode & mode::IFMT) == mode::IFLNK | |
402 | } | |
403 | ||
404 | /// Check whether this is a device node. | |
405 | pub fn is_device(&self) -> bool { | |
406 | let fmt = self.mode & mode::IFMT; | |
407 | fmt == mode::IFCHR || fmt == mode::IFBLK | |
408 | } | |
409 | ||
da286c28 WB |
410 | /// Check whether this is a block device node. |
411 | pub fn is_blockdev(&self) -> bool { | |
412 | let fmt = self.mode & mode::IFMT; | |
413 | fmt == mode::IFBLK | |
414 | } | |
415 | ||
416 | /// Check whether this is a character device node. | |
417 | pub fn is_chardev(&self) -> bool { | |
418 | let fmt = self.mode & mode::IFMT; | |
419 | fmt == mode::IFCHR | |
420 | } | |
421 | ||
70acf637 WB |
422 | /// Check whether this is a regular file. |
423 | pub fn is_regular_file(&self) -> bool { | |
424 | (self.mode & mode::IFMT) == mode::IFREG | |
425 | } | |
2287d8b2 WB |
426 | |
427 | /// Check whether this is a named pipe (FIFO). | |
428 | pub fn is_fifo(&self) -> bool { | |
429 | (self.mode & mode::IFMT) == mode::IFIFO | |
430 | } | |
431 | ||
432 | /// Check whether this is a named socket. | |
433 | pub fn is_socket(&self) -> bool { | |
434 | (self.mode & mode::IFMT) == mode::IFSOCK | |
435 | } | |
70acf637 WB |
436 | } |
437 | ||
438 | impl From<&std::fs::Metadata> for Entry { | |
439 | fn from(meta: &std::fs::Metadata) -> Entry { | |
440 | #[cfg(unix)] | |
441 | use std::os::unix::fs::MetadataExt; | |
442 | ||
443 | let this = Entry::default(); | |
444 | ||
445 | #[cfg(unix)] | |
446 | let this = this | |
447 | .uid(meta.uid()) | |
448 | .gid(meta.gid()) | |
0a6c7350 WB |
449 | .mode(meta.mode() as u64); |
450 | ||
451 | let this = match meta.modified() { | |
1250e3ea WB |
452 | Ok(mtime) => this.mtime( |
453 | mtime | |
0a6c7350 WB |
454 | .duration_since(std::time::SystemTime::UNIX_EPOCH) |
455 | .map(|dur| dur.as_nanos() as u64) | |
1250e3ea WB |
456 | .unwrap_or(0u64), |
457 | ), | |
0a6c7350 WB |
458 | Err(_) => this, |
459 | }; | |
70acf637 WB |
460 | |
461 | let file_type = meta.file_type(); | |
462 | let mode = this.mode; | |
1b25fc08 | 463 | if file_type.is_dir() { |
70acf637 WB |
464 | this.mode(mode | mode::IFDIR) |
465 | } else if file_type.is_symlink() { | |
466 | this.mode(mode | mode::IFLNK) | |
467 | } else { | |
468 | this.mode(mode | mode::IFREG) | |
1b25fc08 | 469 | } |
70acf637 WB |
470 | } |
471 | } | |
472 | ||
6cd4f635 WB |
473 | #[derive(Clone, Debug)] |
474 | pub struct Filename { | |
475 | pub name: Vec<u8>, | |
476 | } | |
477 | ||
478 | #[derive(Clone, Debug)] | |
479 | pub struct Symlink { | |
480 | pub data: Vec<u8>, | |
481 | } | |
482 | ||
7b389f88 WB |
483 | impl Symlink { |
484 | pub fn as_os_str(&self) -> &OsStr { | |
485 | self.as_ref() | |
486 | } | |
487 | } | |
488 | ||
353bbf73 WB |
489 | impl AsRef<[u8]> for Symlink { |
490 | fn as_ref(&self) -> &[u8] { | |
491 | &self.data | |
492 | } | |
493 | } | |
494 | ||
54912ae6 WB |
495 | impl AsRef<OsStr> for Symlink { |
496 | fn as_ref(&self) -> &OsStr { | |
353bbf73 | 497 | OsStr::from_bytes(&self.data[..self.data.len().max(1) - 1]) |
54912ae6 WB |
498 | } |
499 | } | |
500 | ||
6cd4f635 WB |
501 | #[derive(Clone, Debug)] |
502 | pub struct Hardlink { | |
b0752929 | 503 | pub offset: u64, |
6cd4f635 WB |
504 | pub data: Vec<u8>, |
505 | } | |
506 | ||
7b389f88 WB |
507 | impl Hardlink { |
508 | pub fn as_os_str(&self) -> &OsStr { | |
509 | self.as_ref() | |
510 | } | |
511 | } | |
512 | ||
353bbf73 WB |
513 | impl AsRef<[u8]> for Hardlink { |
514 | fn as_ref(&self) -> &[u8] { | |
515 | &self.data | |
516 | } | |
517 | } | |
518 | ||
54912ae6 WB |
519 | impl AsRef<OsStr> for Hardlink { |
520 | fn as_ref(&self) -> &OsStr { | |
353bbf73 | 521 | OsStr::from_bytes(&self.data[..self.data.len().max(1) - 1]) |
54912ae6 WB |
522 | } |
523 | } | |
524 | ||
6cd4f635 WB |
525 | #[derive(Clone, Debug, Eq)] |
526 | #[repr(C)] | |
527 | pub struct XAttr { | |
528 | pub(crate) data: Vec<u8>, | |
529 | pub(crate) name_len: usize, | |
530 | } | |
531 | ||
532 | impl XAttr { | |
533 | pub fn new<N: AsRef<[u8]>, V: AsRef<[u8]>>(name: N, value: V) -> Self { | |
534 | let name = name.as_ref(); | |
535 | let value = value.as_ref(); | |
536 | let mut data = Vec::with_capacity(name.len() + value.len() + 1); | |
537 | data.extend(name); | |
538 | data.push(0); | |
539 | data.extend(value); | |
540 | Self { | |
541 | data, | |
542 | name_len: name.len(), | |
543 | } | |
544 | } | |
545 | ||
2c442007 | 546 | pub fn name(&self) -> &CStr { |
2fd112ac | 547 | unsafe { CStr::from_bytes_with_nul_unchecked(&self.data[..self.name_len + 1]) } |
6cd4f635 WB |
548 | } |
549 | ||
550 | pub fn value(&self) -> &[u8] { | |
551 | &self.data[(self.name_len + 1)..] | |
552 | } | |
553 | } | |
554 | ||
555 | impl Ord for XAttr { | |
556 | fn cmp(&self, other: &XAttr) -> Ordering { | |
557 | self.name().cmp(&other.name()) | |
558 | } | |
559 | } | |
560 | ||
561 | impl PartialOrd for XAttr { | |
562 | fn partial_cmp(&self, other: &XAttr) -> Option<Ordering> { | |
563 | Some(self.cmp(other)) | |
564 | } | |
565 | } | |
566 | ||
567 | impl PartialEq for XAttr { | |
568 | fn eq(&self, other: &XAttr) -> bool { | |
569 | self.name() == other.name() | |
570 | } | |
571 | } | |
572 | ||
0104cf77 | 573 | #[derive(Clone, Debug, Endian, Eq, PartialEq)] |
6cd4f635 WB |
574 | #[repr(C)] |
575 | pub struct Device { | |
576 | pub major: u64, | |
577 | pub minor: u64, | |
578 | } | |
579 | ||
9cda3431 WB |
580 | #[cfg(target_os = "linux")] |
581 | impl Device { | |
582 | /// Get a `dev_t` value for this device. | |
583 | #[rustfmt::skip] | |
584 | pub fn to_dev_t(&self) -> u64 { | |
585 | // see bits/sysmacros.h | |
586 | ((self.major & 0x0000_0fff) << 8) | | |
587 | ((self.major & 0xffff_f000) << 32) | | |
588 | (self.minor & 0x0000_00ff) | | |
589 | ((self.minor & 0xffff_ff00) << 12) | |
590 | } | |
2fd112ac WB |
591 | |
592 | /// Get a `Device` from a `dev_t` value. | |
593 | #[rustfmt::skip] | |
594 | pub fn from_dev_t(dev: u64) -> Self { | |
595 | // see to_dev_t | |
596 | Self { | |
597 | major: (dev >> 8) & 0x0000_0fff | | |
598 | (dev >> 32) & 0xffff_f000, | |
599 | minor: dev & 0x0000_00ff | | |
600 | (dev >> 12) & 0xffff_ff00, | |
601 | } | |
602 | } | |
603 | } | |
604 | ||
605 | #[cfg(all(test, target_os = "linux"))] | |
606 | #[test] | |
607 | fn test_linux_devices() { | |
608 | let c_dev = unsafe { ::libc::makedev(0xabcd_1234, 0xdcba_5678) }; | |
609 | let dev = Device::from_dev_t(c_dev); | |
610 | assert_eq!(dev.to_dev_t(), c_dev); | |
9cda3431 WB |
611 | } |
612 | ||
6cd4f635 | 613 | #[derive(Clone, Debug)] |
0104cf77 | 614 | #[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))] |
6cd4f635 WB |
615 | #[repr(C)] |
616 | pub struct FCaps { | |
617 | pub data: Vec<u8>, | |
618 | } | |
619 | ||
0104cf77 | 620 | #[derive(Clone, Copy, Debug, Endian, Eq, PartialEq)] |
6cd4f635 WB |
621 | #[repr(C)] |
622 | pub struct QuotaProjectId { | |
623 | pub projid: u64, | |
624 | } | |
625 | ||
70acf637 | 626 | #[derive(Clone, Debug, Endian)] |
6cd4f635 WB |
627 | #[repr(C)] |
628 | pub struct GoodbyeItem { | |
629 | /// SipHash24 of the directory item name. The last GOODBYE item uses the special hash value | |
630 | /// `PXAR_GOODBYE_TAIL_MARKER`. | |
631 | pub hash: u64, | |
632 | ||
633 | /// The offset from the start of the GOODBYE object to the start of the matching directory item | |
634 | /// (point to a FILENAME). The last GOODBYE item points to the start of the matching ENTRY | |
635 | /// object. | |
636 | pub offset: u64, | |
637 | ||
638 | /// The overall size of the directory item. This includes the FILENAME header. In other words, | |
639 | /// `goodbye_start - offset + size` points to the end of the directory. | |
640 | /// | |
641 | /// The last GOODBYE item repeats the size of the GOODBYE item. | |
642 | pub size: u64, | |
643 | } | |
644 | ||
645 | impl GoodbyeItem { | |
646 | pub fn new(name: &[u8], offset: u64, size: u64) -> Self { | |
647 | let hash = hash_filename(name); | |
648 | Self { hash, offset, size } | |
649 | } | |
650 | } | |
651 | ||
652 | pub fn hash_filename(name: &[u8]) -> u64 { | |
653 | use std::hash::Hasher; | |
1b1e52a4 WB |
654 | |
655 | let mut hasher = SipHasher24::new_with_keys(PXAR_HASH_KEY_1, PXAR_HASH_KEY_2); | |
6cd4f635 WB |
656 | hasher.write(name); |
657 | hasher.finish() | |
658 | } | |
659 | ||
6cd4f635 WB |
660 | pub fn path_is_legal_component(path: &Path) -> bool { |
661 | let mut components = path.components(); | |
662 | match components.next() { | |
663 | Some(std::path::Component::Normal(_)) => (), | |
664 | _ => return false, | |
665 | } | |
666 | components.next().is_none() | |
667 | } | |
668 | ||
669 | pub fn check_file_name(path: &Path) -> io::Result<()> { | |
670 | if !path_is_legal_component(path) { | |
671 | io_bail!("invalid file name in archive: {:?}", path); | |
672 | } else { | |
673 | Ok(()) | |
674 | } | |
675 | } |