]>
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 | ||
644e844d WB |
81 | /// Beginning of an entry (current version). |
82 | pub const PXAR_ENTRY: u64 = 0xd5956474e588acef; | |
83 | /// Previous version of the entry struct | |
84 | pub const PXAR_ENTRY_V1: u64 = 0x11da850a1c1cceff; | |
1b1e52a4 WB |
85 | pub const PXAR_FILENAME: u64 = 0x16701121063917b3; |
86 | pub const PXAR_SYMLINK: u64 = 0x27f971e7dbf5dc5f; | |
87 | pub const PXAR_DEVICE: u64 = 0x9fc9e906586d5ce9; | |
88 | pub const PXAR_XATTR: u64 = 0x0dab0229b57dcd03; | |
89 | pub const PXAR_ACL_USER: u64 = 0x2ce8540a457d55b8; | |
90 | pub const PXAR_ACL_GROUP: u64 = 0x136e3eceb04c03ab; | |
91 | pub const PXAR_ACL_GROUP_OBJ: u64 = 0x10868031e9582876; | |
92 | pub const PXAR_ACL_DEFAULT: u64 = 0xbbbb13415a6896f5; | |
93 | pub const PXAR_ACL_DEFAULT_USER: u64 = 0xc89357b40532cd1f; | |
94 | pub const PXAR_ACL_DEFAULT_GROUP: u64 = 0xf90a8a5816038ffe; | |
95 | pub const PXAR_FCAPS: u64 = 0x2da9dd9db5f7fb67; | |
96 | pub const PXAR_QUOTA_PROJID: u64 = 0xe07540e82f7d1cbb; | |
6cd4f635 | 97 | /// Marks item as hardlink |
1b1e52a4 | 98 | pub const PXAR_HARDLINK: u64 = 0x51269c8422bd7275; |
6cd4f635 | 99 | /// Marks the beginnig of the payload (actual content) of regular files |
1b1e52a4 | 100 | pub const PXAR_PAYLOAD: u64 = 0x28147a1b0b7c1a25; |
6cd4f635 | 101 | /// Marks item as entry of goodbye table |
1b1e52a4 | 102 | pub const PXAR_GOODBYE: u64 = 0x2fec4fa642d5731d; |
6cd4f635 | 103 | /// The end marker used in the GOODBYE object |
1b1e52a4 | 104 | pub const PXAR_GOODBYE_TAIL_MARKER: u64 = 0xef5eed5b753e1555; |
6cd4f635 WB |
105 | |
106 | #[derive(Debug, Endian)] | |
107 | #[repr(C)] | |
108 | pub struct Header { | |
109 | /// The item type (see `PXAR_` constants). | |
110 | pub htype: u64, | |
111 | /// The size of the item, including the size of `Header`. | |
112 | full_size: u64, | |
113 | } | |
114 | ||
115 | impl Header { | |
70acf637 WB |
116 | #[inline] |
117 | pub fn with_full_size(htype: u64, full_size: u64) -> Self { | |
118 | Self { htype, full_size } | |
119 | } | |
120 | ||
121 | #[inline] | |
122 | pub fn with_content_size(htype: u64, content_size: u64) -> Self { | |
123 | Self::with_full_size(htype, content_size + size_of::<Header>() as u64) | |
124 | } | |
125 | ||
6cd4f635 WB |
126 | #[inline] |
127 | pub fn full_size(&self) -> u64 { | |
128 | self.full_size | |
129 | } | |
130 | ||
131 | #[inline] | |
132 | pub fn content_size(&self) -> u64 { | |
133 | self.full_size() - (size_of::<Self>() as u64) | |
134 | } | |
ec0761f9 FG |
135 | |
136 | #[inline] | |
137 | pub fn max_content_size(&self) -> u64 { | |
138 | match self.htype { | |
139 | // + null-termination | |
140 | PXAR_FILENAME => crate::util::MAX_FILENAME_LEN + 1, | |
141 | // + null-termination | |
142 | PXAR_SYMLINK => crate::util::MAX_PATH_LEN + 1, | |
143 | // + null-termination + offset | |
144 | PXAR_HARDLINK => crate::util::MAX_PATH_LEN + 1 + (size_of::<u64>() as u64), | |
145 | PXAR_DEVICE => size_of::<Device>() as u64, | |
146 | PXAR_XATTR | PXAR_FCAPS => crate::util::MAX_XATTR_LEN, | |
147 | PXAR_ACL_USER | PXAR_ACL_DEFAULT_USER => size_of::<acl::User>() as u64, | |
148 | PXAR_ACL_GROUP | PXAR_ACL_DEFAULT_GROUP => size_of::<acl::Group>() as u64, | |
149 | PXAR_ACL_DEFAULT => size_of::<acl::Default>() as u64, | |
93fef2fd | 150 | PXAR_ACL_GROUP_OBJ => size_of::<acl::GroupObject>() as u64, |
ec0761f9 | 151 | PXAR_QUOTA_PROJID => size_of::<QuotaProjectId>() as u64, |
2ea8aff2 | 152 | PXAR_ENTRY => size_of::<Stat>() as u64, |
1a2a5570 WB |
153 | PXAR_PAYLOAD | PXAR_GOODBYE => u64::MAX - (size_of::<Self>() as u64), |
154 | _ => u64::MAX - (size_of::<Self>() as u64), | |
ec0761f9 FG |
155 | } |
156 | } | |
157 | ||
158 | #[inline] | |
159 | pub fn check_header_size(&self) -> io::Result<()> { | |
160 | if self.full_size() < size_of::<Header>() as u64 { | |
161 | io_bail!("invalid header {} - too small ({})", self, self.full_size()); | |
162 | } | |
163 | ||
164 | if self.content_size() > self.max_content_size() { | |
710e6c8b WB |
165 | io_bail!( |
166 | "invalid content size ({} > {}) of entry with {}", | |
167 | self.content_size(), | |
168 | self.max_content_size(), | |
169 | self | |
170 | ); | |
ec0761f9 FG |
171 | } |
172 | Ok(()) | |
173 | } | |
174 | } | |
4a13b8a3 FG |
175 | |
176 | impl Display for Header { | |
177 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
178 | let readable = match self.htype { | |
179 | PXAR_FILENAME => "FILENAME", | |
180 | PXAR_SYMLINK => "SYMLINK", | |
181 | PXAR_HARDLINK => "HARDLINK", | |
182 | PXAR_DEVICE => "DEVICE", | |
183 | PXAR_XATTR => "XATTR", | |
184 | PXAR_FCAPS => "FCAPS", | |
185 | PXAR_ACL_USER => "ACL_USER", | |
186 | PXAR_ACL_DEFAULT_USER => "ACL_DEFAULT_USER", | |
187 | PXAR_ACL_GROUP => "ACL_GROUP", | |
188 | PXAR_ACL_DEFAULT_GROUP => "ACL_DEFAULT_GROUP", | |
189 | PXAR_ACL_DEFAULT => "ACL_DEFAULT", | |
190 | PXAR_ACL_GROUP_OBJ => "ACL_GROUP_OBJ", | |
191 | PXAR_QUOTA_PROJID => "QUOTA_PROJID", | |
192 | PXAR_ENTRY => "ENTRY", | |
193 | PXAR_PAYLOAD => "PAYLOAD", | |
194 | PXAR_GOODBYE => "GOODBYE", | |
195 | _ => "UNKNOWN", | |
196 | }; | |
197 | write!(f, "{} header ({:x})", readable, self.htype) | |
198 | } | |
6cd4f635 WB |
199 | } |
200 | ||
939f2468 WB |
201 | #[derive(Clone, Debug, Eq, PartialEq)] |
202 | pub enum SignedDuration { | |
203 | Positive(Duration), | |
204 | Negative(Duration), | |
205 | } | |
206 | ||
207 | #[derive(Clone, Debug, Default, Endian, Eq, PartialEq)] | |
208 | #[repr(C)] | |
209 | pub struct StatxTimestamp { | |
210 | /// Seconds since the epoch (unix time). | |
211 | pub secs: i64, | |
212 | ||
213 | /// Nanoseconds since this struct's `secs`. | |
214 | pub nanos: u32, | |
180186c5 WB |
215 | |
216 | /// (Erroneously introduced padding...) | |
217 | _zero: u32, | |
939f2468 WB |
218 | } |
219 | ||
220 | impl From<SystemTime> for StatxTimestamp { | |
221 | fn from(time: SystemTime) -> Self { | |
222 | match time.duration_since(SystemTime::UNIX_EPOCH) { | |
223 | Ok(positive) => Self::from_duration_since_epoch(positive), | |
224 | Err(negative) => Self::from_duration_before_epoch(negative.duration()), | |
225 | } | |
226 | } | |
227 | } | |
228 | ||
229 | impl StatxTimestamp { | |
180186c5 WB |
230 | /// Create a timestamp from seconds and nanoseconds. |
231 | pub const fn new(secs: i64, nanos: u32) -> Self { | |
232 | Self { | |
737f75cf WB |
233 | secs, |
234 | nanos, | |
235 | _zero: 0, | |
180186c5 WB |
236 | } |
237 | } | |
238 | ||
939f2468 WB |
239 | /// `const` version of `Default` |
240 | pub const fn zero() -> Self { | |
737f75cf WB |
241 | Self { |
242 | secs: 0, | |
243 | nanos: 0, | |
244 | _zero: 0, | |
245 | } | |
939f2468 WB |
246 | } |
247 | ||
2ea8aff2 WB |
248 | #[cfg(all(test, target_os = "linux"))] |
249 | /// From data found in `struct stat` (`libc::stat`). | |
250 | pub fn from_stat(sec: i64, nsec: u32) -> Self { | |
251 | if sec < 0 { | |
252 | Self::from_duration_before_epoch(Duration::new((-sec) as u64, nsec)) | |
253 | } else { | |
254 | Self::from_duration_since_epoch(Duration::new(sec as u64, nsec)) | |
255 | } | |
256 | } | |
257 | ||
939f2468 WB |
258 | /// Turn a positive duration relative to the unix epoch into a time stamp. |
259 | pub fn from_duration_since_epoch(duration: Duration) -> Self { | |
260 | Self { | |
261 | secs: duration.as_secs() as i64, | |
262 | nanos: duration.subsec_nanos(), | |
180186c5 | 263 | _zero: 0, |
939f2468 WB |
264 | } |
265 | } | |
266 | ||
267 | /// Turn a *negative* duration from relative to the unix epoch into a time stamp. | |
268 | pub fn from_duration_before_epoch(duration: Duration) -> Self { | |
269 | match duration.subsec_nanos() { | |
270 | 0 => Self { | |
bb6779b5 | 271 | secs: -(duration.as_secs() as i64), |
939f2468 | 272 | nanos: 0, |
180186c5 | 273 | _zero: 0, |
939f2468 WB |
274 | }, |
275 | nanos => Self { | |
276 | secs: -(duration.as_secs() as i64) - 1, | |
277 | nanos: 1_000_000_000 - nanos, | |
180186c5 | 278 | _zero: 0, |
939f2468 WB |
279 | }, |
280 | } | |
281 | } | |
282 | ||
283 | /// Get the duration since the epoch. an `Ok` value is a positive duration, an `Err` value is a | |
284 | /// negative duration. | |
285 | pub fn to_duration(&self) -> SignedDuration { | |
286 | if self.secs >= 0 { | |
287 | SignedDuration::Positive(Duration::new(self.secs as u64, self.nanos)) | |
288 | } else { | |
bb6779b5 | 289 | // this handles the nanos=0 case as `Duration::new()` performs the carry-over. |
939f2468 WB |
290 | SignedDuration::Negative(Duration::new( |
291 | -(self.secs + 1) as u64, | |
292 | 1_000_000_000 - self.nanos, | |
293 | )) | |
294 | } | |
295 | } | |
296 | ||
297 | /// Get a `std::time::SystemTime` from this time stamp. | |
298 | pub fn system_time(&self) -> SystemTime { | |
299 | match self.to_duration() { | |
300 | SignedDuration::Positive(positive) => SystemTime::UNIX_EPOCH + positive, | |
301 | SignedDuration::Negative(negative) => SystemTime::UNIX_EPOCH - negative, | |
302 | } | |
303 | } | |
304 | } | |
305 | ||
306 | #[test] | |
307 | fn test_statx_timestamp() { | |
737f75cf WB |
308 | assert_eq!( |
309 | size_of::<StatxTimestamp>(), | |
310 | 16, | |
311 | "StatxTimestamp size needs to be 16 bytes" | |
312 | ); | |
939f2468 WB |
313 | const MAY_1_2015_1530: i64 = 1430487000; |
314 | let system_time = SystemTime::UNIX_EPOCH + Duration::new(MAY_1_2015_1530 as u64, 1_000_000); | |
315 | let tx = StatxTimestamp::from(system_time); | |
316 | assert_eq!( | |
317 | tx, | |
318 | StatxTimestamp { | |
319 | secs: MAY_1_2015_1530, | |
320 | nanos: 1_000_000, | |
180186c5 | 321 | _zero: 0, |
939f2468 WB |
322 | } |
323 | ); | |
324 | assert_eq!(tx.system_time(), system_time); | |
325 | ||
326 | const MAY_1_1960_1530: i64 = -305112600; | |
327 | let system_time = SystemTime::UNIX_EPOCH - Duration::new(-MAY_1_1960_1530 as u64, 1_000_000); | |
328 | let tx = StatxTimestamp::from(system_time); | |
329 | assert_eq!( | |
330 | tx, | |
331 | StatxTimestamp { | |
332 | secs: MAY_1_1960_1530 - 1, | |
333 | nanos: 999_000_000, | |
180186c5 | 334 | _zero: 0, |
939f2468 WB |
335 | } |
336 | ); | |
337 | assert_eq!(tx.system_time(), system_time); | |
939f2468 | 338 | |
bb6779b5 WB |
339 | let system_time = SystemTime::UNIX_EPOCH - Duration::new(-MAY_1_1960_1530 as u64, 0); |
340 | let tx = StatxTimestamp::from(system_time); | |
341 | assert_eq!( | |
342 | tx, | |
343 | StatxTimestamp { | |
344 | secs: MAY_1_1960_1530, | |
345 | nanos: 0, | |
180186c5 | 346 | _zero: 0, |
bb6779b5 WB |
347 | } |
348 | ); | |
349 | assert_eq!(tx.system_time(), system_time); | |
350 | } | |
6cd4f635 | 351 | #[derive(Clone, Debug, Default, Endian)] |
0104cf77 | 352 | #[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))] |
6cd4f635 | 353 | #[repr(C)] |
2ea8aff2 | 354 | pub struct Stat_V1 { |
6cd4f635 WB |
355 | pub mode: u64, |
356 | pub flags: u64, | |
357 | pub uid: u32, | |
358 | pub gid: u32, | |
359 | pub mtime: u64, | |
360 | } | |
361 | ||
1646a72d WB |
362 | impl From<Stat_V1> for Stat { |
363 | fn from(v1: Stat_V1) -> Stat { | |
2ea8aff2 | 364 | Stat { |
1646a72d WB |
365 | mode: v1.mode, |
366 | flags: v1.flags, | |
367 | uid: v1.uid, | |
368 | gid: v1.gid, | |
369 | mtime: StatxTimestamp::from_duration_since_epoch(Duration::from_nanos(v1.mtime)), | |
644e844d WB |
370 | } |
371 | } | |
372 | } | |
373 | ||
374 | #[derive(Clone, Debug, Default, Endian)] | |
375 | #[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))] | |
376 | #[repr(C)] | |
2ea8aff2 | 377 | pub struct Stat { |
644e844d WB |
378 | pub mode: u64, |
379 | pub flags: u64, | |
380 | pub uid: u32, | |
381 | pub gid: u32, | |
382 | pub mtime: StatxTimestamp, | |
383 | } | |
384 | ||
70acf637 | 385 | /// Builder pattern methods. |
2ea8aff2 | 386 | impl Stat { |
70acf637 WB |
387 | pub const fn mode(self, mode: u64) -> Self { |
388 | Self { mode, ..self } | |
389 | } | |
390 | ||
391 | pub const fn flags(self, flags: u64) -> Self { | |
392 | Self { flags, ..self } | |
393 | } | |
394 | ||
395 | pub const fn uid(self, uid: u32) -> Self { | |
396 | Self { uid, ..self } | |
397 | } | |
398 | ||
399 | pub const fn gid(self, gid: u32) -> Self { | |
400 | Self { gid, ..self } | |
401 | } | |
402 | ||
644e844d | 403 | pub const fn mtime(self, mtime: StatxTimestamp) -> Self { |
70acf637 WB |
404 | Self { mtime, ..self } |
405 | } | |
406 | ||
407 | pub const fn set_dir(self) -> Self { | |
408 | let mode = self.mode; | |
409 | self.mode((mode & !mode::IFMT) | mode::IFDIR) | |
410 | } | |
411 | ||
412 | pub const fn set_regular_file(self) -> Self { | |
413 | let mode = self.mode; | |
414 | self.mode((mode & !mode::IFMT) | mode::IFREG) | |
415 | } | |
416 | ||
417 | pub const fn set_symlink(self) -> Self { | |
418 | let mode = self.mode; | |
419 | self.mode((mode & !mode::IFMT) | mode::IFLNK) | |
420 | } | |
421 | ||
422 | pub const fn set_blockdev(self) -> Self { | |
423 | let mode = self.mode; | |
424 | self.mode((mode & !mode::IFMT) | mode::IFBLK) | |
425 | } | |
426 | ||
427 | pub const fn set_chardev(self) -> Self { | |
428 | let mode = self.mode; | |
429 | self.mode((mode & !mode::IFMT) | mode::IFCHR) | |
430 | } | |
431 | ||
432 | pub const fn set_fifo(self) -> Self { | |
433 | let mode = self.mode; | |
434 | self.mode((mode & !mode::IFMT) | mode::IFIFO) | |
435 | } | |
436 | } | |
437 | ||
6da548dd | 438 | /// Convenience accessor methods. |
2ea8aff2 | 439 | impl Stat { |
fc2f1b9f | 440 | /// Get the mtime as duration since the epoch. |
644e844d WB |
441 | pub fn mtime_as_duration(&self) -> SignedDuration { |
442 | self.mtime.to_duration() | |
6da548dd | 443 | } |
8617cae3 WB |
444 | |
445 | /// Get the file type portion of the mode bitfield. | |
446 | pub fn get_file_bits(&self) -> u64 { | |
447 | self.mode & mode::IFMT | |
448 | } | |
449 | ||
450 | /// Get the permission portion of the mode bitfield. | |
451 | pub fn get_permission_bits(&self) -> u64 { | |
452 | self.mode & !mode::IFMT | |
453 | } | |
6da548dd WB |
454 | } |
455 | ||
70acf637 | 456 | /// Convenience methods. |
2ea8aff2 | 457 | impl Stat { |
21d66e10 WB |
458 | /// Get the file type (`mode & mode::IFMT`). |
459 | pub fn file_type(&self) -> u64 { | |
460 | self.mode & mode::IFMT | |
461 | } | |
462 | ||
463 | /// Get the file mode bits (`mode & !mode::IFMT`). | |
464 | pub fn file_mode(&self) -> u64 { | |
465 | self.mode & !mode::IFMT | |
466 | } | |
467 | ||
70acf637 WB |
468 | /// Check whether this is a directory. |
469 | pub fn is_dir(&self) -> bool { | |
470 | (self.mode & mode::IFMT) == mode::IFDIR | |
471 | } | |
472 | ||
473 | /// Check whether this is a symbolic link. | |
474 | pub fn is_symlink(&self) -> bool { | |
475 | (self.mode & mode::IFMT) == mode::IFLNK | |
476 | } | |
477 | ||
478 | /// Check whether this is a device node. | |
479 | pub fn is_device(&self) -> bool { | |
480 | let fmt = self.mode & mode::IFMT; | |
481 | fmt == mode::IFCHR || fmt == mode::IFBLK | |
482 | } | |
483 | ||
da286c28 WB |
484 | /// Check whether this is a block device node. |
485 | pub fn is_blockdev(&self) -> bool { | |
486 | let fmt = self.mode & mode::IFMT; | |
487 | fmt == mode::IFBLK | |
488 | } | |
489 | ||
490 | /// Check whether this is a character device node. | |
491 | pub fn is_chardev(&self) -> bool { | |
492 | let fmt = self.mode & mode::IFMT; | |
493 | fmt == mode::IFCHR | |
494 | } | |
495 | ||
70acf637 WB |
496 | /// Check whether this is a regular file. |
497 | pub fn is_regular_file(&self) -> bool { | |
498 | (self.mode & mode::IFMT) == mode::IFREG | |
499 | } | |
2287d8b2 WB |
500 | |
501 | /// Check whether this is a named pipe (FIFO). | |
502 | pub fn is_fifo(&self) -> bool { | |
503 | (self.mode & mode::IFMT) == mode::IFIFO | |
504 | } | |
505 | ||
506 | /// Check whether this is a named socket. | |
507 | pub fn is_socket(&self) -> bool { | |
508 | (self.mode & mode::IFMT) == mode::IFSOCK | |
509 | } | |
70acf637 WB |
510 | } |
511 | ||
2ea8aff2 WB |
512 | impl From<&std::fs::Metadata> for Stat { |
513 | fn from(meta: &std::fs::Metadata) -> Stat { | |
70acf637 WB |
514 | #[cfg(unix)] |
515 | use std::os::unix::fs::MetadataExt; | |
516 | ||
2ea8aff2 | 517 | let this = Stat::default(); |
70acf637 WB |
518 | |
519 | #[cfg(unix)] | |
520 | let this = this | |
521 | .uid(meta.uid()) | |
522 | .gid(meta.gid()) | |
0a6c7350 WB |
523 | .mode(meta.mode() as u64); |
524 | ||
525 | let this = match meta.modified() { | |
644e844d | 526 | Ok(mtime) => this.mtime(mtime.into()), |
0a6c7350 WB |
527 | Err(_) => this, |
528 | }; | |
70acf637 WB |
529 | |
530 | let file_type = meta.file_type(); | |
531 | let mode = this.mode; | |
1b25fc08 | 532 | if file_type.is_dir() { |
70acf637 WB |
533 | this.mode(mode | mode::IFDIR) |
534 | } else if file_type.is_symlink() { | |
535 | this.mode(mode | mode::IFLNK) | |
536 | } else { | |
537 | this.mode(mode | mode::IFREG) | |
1b25fc08 | 538 | } |
70acf637 WB |
539 | } |
540 | } | |
541 | ||
6cd4f635 WB |
542 | #[derive(Clone, Debug)] |
543 | pub struct Filename { | |
544 | pub name: Vec<u8>, | |
545 | } | |
546 | ||
547 | #[derive(Clone, Debug)] | |
548 | pub struct Symlink { | |
549 | pub data: Vec<u8>, | |
550 | } | |
551 | ||
7b389f88 WB |
552 | impl Symlink { |
553 | pub fn as_os_str(&self) -> &OsStr { | |
554 | self.as_ref() | |
555 | } | |
556 | } | |
557 | ||
353bbf73 WB |
558 | impl AsRef<[u8]> for Symlink { |
559 | fn as_ref(&self) -> &[u8] { | |
560 | &self.data | |
561 | } | |
562 | } | |
563 | ||
54912ae6 WB |
564 | impl AsRef<OsStr> for Symlink { |
565 | fn as_ref(&self) -> &OsStr { | |
353bbf73 | 566 | OsStr::from_bytes(&self.data[..self.data.len().max(1) - 1]) |
54912ae6 WB |
567 | } |
568 | } | |
569 | ||
6cd4f635 WB |
570 | #[derive(Clone, Debug)] |
571 | pub struct Hardlink { | |
b0752929 | 572 | pub offset: u64, |
6cd4f635 WB |
573 | pub data: Vec<u8>, |
574 | } | |
575 | ||
7b389f88 WB |
576 | impl Hardlink { |
577 | pub fn as_os_str(&self) -> &OsStr { | |
578 | self.as_ref() | |
579 | } | |
580 | } | |
581 | ||
353bbf73 WB |
582 | impl AsRef<[u8]> for Hardlink { |
583 | fn as_ref(&self) -> &[u8] { | |
584 | &self.data | |
585 | } | |
586 | } | |
587 | ||
54912ae6 WB |
588 | impl AsRef<OsStr> for Hardlink { |
589 | fn as_ref(&self) -> &OsStr { | |
353bbf73 | 590 | OsStr::from_bytes(&self.data[..self.data.len().max(1) - 1]) |
54912ae6 WB |
591 | } |
592 | } | |
593 | ||
6cd4f635 WB |
594 | #[derive(Clone, Debug, Eq)] |
595 | #[repr(C)] | |
596 | pub struct XAttr { | |
597 | pub(crate) data: Vec<u8>, | |
598 | pub(crate) name_len: usize, | |
599 | } | |
600 | ||
601 | impl XAttr { | |
602 | pub fn new<N: AsRef<[u8]>, V: AsRef<[u8]>>(name: N, value: V) -> Self { | |
603 | let name = name.as_ref(); | |
604 | let value = value.as_ref(); | |
605 | let mut data = Vec::with_capacity(name.len() + value.len() + 1); | |
606 | data.extend(name); | |
607 | data.push(0); | |
608 | data.extend(value); | |
609 | Self { | |
610 | data, | |
611 | name_len: name.len(), | |
612 | } | |
613 | } | |
614 | ||
2c442007 | 615 | pub fn name(&self) -> &CStr { |
2fd112ac | 616 | unsafe { CStr::from_bytes_with_nul_unchecked(&self.data[..self.name_len + 1]) } |
6cd4f635 WB |
617 | } |
618 | ||
619 | pub fn value(&self) -> &[u8] { | |
620 | &self.data[(self.name_len + 1)..] | |
621 | } | |
622 | } | |
623 | ||
624 | impl Ord for XAttr { | |
625 | fn cmp(&self, other: &XAttr) -> Ordering { | |
626 | self.name().cmp(&other.name()) | |
627 | } | |
628 | } | |
629 | ||
630 | impl PartialOrd for XAttr { | |
631 | fn partial_cmp(&self, other: &XAttr) -> Option<Ordering> { | |
632 | Some(self.cmp(other)) | |
633 | } | |
634 | } | |
635 | ||
636 | impl PartialEq for XAttr { | |
637 | fn eq(&self, other: &XAttr) -> bool { | |
638 | self.name() == other.name() | |
639 | } | |
640 | } | |
641 | ||
0104cf77 | 642 | #[derive(Clone, Debug, Endian, Eq, PartialEq)] |
6cd4f635 WB |
643 | #[repr(C)] |
644 | pub struct Device { | |
645 | pub major: u64, | |
646 | pub minor: u64, | |
647 | } | |
648 | ||
9cda3431 WB |
649 | #[cfg(target_os = "linux")] |
650 | impl Device { | |
651 | /// Get a `dev_t` value for this device. | |
652 | #[rustfmt::skip] | |
653 | pub fn to_dev_t(&self) -> u64 { | |
654 | // see bits/sysmacros.h | |
655 | ((self.major & 0x0000_0fff) << 8) | | |
656 | ((self.major & 0xffff_f000) << 32) | | |
657 | (self.minor & 0x0000_00ff) | | |
658 | ((self.minor & 0xffff_ff00) << 12) | |
659 | } | |
2fd112ac WB |
660 | |
661 | /// Get a `Device` from a `dev_t` value. | |
662 | #[rustfmt::skip] | |
663 | pub fn from_dev_t(dev: u64) -> Self { | |
664 | // see to_dev_t | |
665 | Self { | |
666 | major: (dev >> 8) & 0x0000_0fff | | |
667 | (dev >> 32) & 0xffff_f000, | |
668 | minor: dev & 0x0000_00ff | | |
669 | (dev >> 12) & 0xffff_ff00, | |
670 | } | |
671 | } | |
672 | } | |
673 | ||
674 | #[cfg(all(test, target_os = "linux"))] | |
675 | #[test] | |
676 | fn test_linux_devices() { | |
677 | let c_dev = unsafe { ::libc::makedev(0xabcd_1234, 0xdcba_5678) }; | |
678 | let dev = Device::from_dev_t(c_dev); | |
679 | assert_eq!(dev.to_dev_t(), c_dev); | |
9cda3431 WB |
680 | } |
681 | ||
6cd4f635 | 682 | #[derive(Clone, Debug)] |
0104cf77 | 683 | #[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))] |
6cd4f635 WB |
684 | #[repr(C)] |
685 | pub struct FCaps { | |
686 | pub data: Vec<u8>, | |
687 | } | |
688 | ||
0104cf77 | 689 | #[derive(Clone, Copy, Debug, Endian, Eq, PartialEq)] |
6cd4f635 WB |
690 | #[repr(C)] |
691 | pub struct QuotaProjectId { | |
692 | pub projid: u64, | |
693 | } | |
694 | ||
e5a2495e WB |
695 | /// An entry in the "goodbye table" in a pxar archive. This is required for random access inside |
696 | /// pxar archives. | |
70acf637 | 697 | #[derive(Clone, Debug, Endian)] |
6cd4f635 WB |
698 | #[repr(C)] |
699 | pub struct GoodbyeItem { | |
700 | /// SipHash24 of the directory item name. The last GOODBYE item uses the special hash value | |
701 | /// `PXAR_GOODBYE_TAIL_MARKER`. | |
702 | pub hash: u64, | |
703 | ||
704 | /// The offset from the start of the GOODBYE object to the start of the matching directory item | |
705 | /// (point to a FILENAME). The last GOODBYE item points to the start of the matching ENTRY | |
706 | /// object. | |
707 | pub offset: u64, | |
708 | ||
709 | /// The overall size of the directory item. This includes the FILENAME header. In other words, | |
710 | /// `goodbye_start - offset + size` points to the end of the directory. | |
711 | /// | |
712 | /// The last GOODBYE item repeats the size of the GOODBYE item. | |
713 | pub size: u64, | |
714 | } | |
715 | ||
716 | impl GoodbyeItem { | |
e5a2495e WB |
717 | /// Create a new [`GoodbyeItem`] by hashing the name, and storing the hash along with the |
718 | /// offset and size information. | |
6cd4f635 WB |
719 | pub fn new(name: &[u8], offset: u64, size: u64) -> Self { |
720 | let hash = hash_filename(name); | |
721 | Self { hash, offset, size } | |
722 | } | |
723 | } | |
724 | ||
e5a2495e | 725 | /// Hash a file name for use in the goodbye table. |
6cd4f635 WB |
726 | pub fn hash_filename(name: &[u8]) -> u64 { |
727 | use std::hash::Hasher; | |
1b1e52a4 WB |
728 | |
729 | let mut hasher = SipHasher24::new_with_keys(PXAR_HASH_KEY_1, PXAR_HASH_KEY_2); | |
6cd4f635 WB |
730 | hasher.write(name); |
731 | hasher.finish() | |
732 | } | |
733 | ||
e5a2495e WB |
734 | /// Returns `true` if the path consists only of [`Normal`](std::path::Component::Normal) |
735 | /// components. | |
6cd4f635 WB |
736 | pub fn path_is_legal_component(path: &Path) -> bool { |
737 | let mut components = path.components(); | |
738 | match components.next() { | |
739 | Some(std::path::Component::Normal(_)) => (), | |
740 | _ => return false, | |
741 | } | |
742 | components.next().is_none() | |
743 | } | |
744 | ||
e5a2495e WB |
745 | /// Assert sure the path consists only of [`Normal`](std::path::Component::Normal) components. |
746 | /// | |
747 | /// Returns an [`io::Error`](std::io::Error) of type [`Other`](std::io::ErrorKind::Other) if that's | |
748 | /// not the case. | |
6cd4f635 WB |
749 | pub fn check_file_name(path: &Path) -> io::Result<()> { |
750 | if !path_is_legal_component(path) { | |
751 | io_bail!("invalid file name in archive: {:?}", path); | |
752 | } else { | |
753 | Ok(()) | |
754 | } | |
755 | } |