1 //! *pxar* format decoder.
3 //! This module contain the code to decode *pxar* archive files.
6 use endian_trait
::Endian
;
8 use super::format_definition
::*;
10 use std
::io
::{Read, Write}
;
11 use std
::path
::{Path, PathBuf}
;
13 use std
::os
::unix
::io
::AsRawFd
;
14 use std
::os
::unix
::io
::RawFd
;
15 use std
::os
::unix
::io
::FromRawFd
;
16 use std
::os
::unix
::ffi
::{OsStringExt}
;
17 use std
::ffi
::{OsStr, OsString}
;
19 use nix
::fcntl
::OFlag
;
20 use nix
::sys
::stat
::Mode
;
21 use nix
::errno
::Errno
;
24 // This one need Read, but works without Seek
25 pub struct PxarDecoder
<'a
, R
: Read
> {
30 const HEADER_SIZE
: u64 = std
::mem
::size_of
::<CaFormatHeader
>() as u64;
32 impl <'a
, R
: Read
> PxarDecoder
<'a
, R
> {
34 pub fn new(reader
: &'a
mut R
) -> Self {
35 let skip_buffer
= vec
![0u8; 64*1024];
36 Self { reader, skip_buffer }
39 fn read_item
<T
: Endian
>(&mut self) -> Result
<T
, Error
> {
41 let mut result
: T
= unsafe { std::mem::uninitialized() }
;
43 let buffer
= unsafe { std
::slice
::from_raw_parts_mut(
44 &mut result
as *mut T
as *mut u8,
45 std
::mem
::size_of
::<T
>()
48 self.reader
.read_exact(buffer
)?
;
53 fn read_symlink(&mut self, size
: u64) -> Result
<PathBuf
, Error
> {
54 if size
< (HEADER_SIZE
+ 2) {
55 bail
!("dectected short symlink target.");
57 let target_len
= size
- HEADER_SIZE
;
59 if target_len
> (libc
::PATH_MAX
as u64) {
60 bail
!("symlink target too long ({}).", target_len
);
63 let mut buffer
= vec
![0u8; target_len
as usize];
64 self.reader
.read_exact(&mut buffer
)?
;
66 let last_byte
= buffer
.pop().unwrap();
68 bail
!("symlink target not nul terminated.");
71 Ok(PathBuf
::from(std
::ffi
::OsString
::from_vec(buffer
)))
74 fn read_filename(&mut self, size
: u64) -> Result
<OsString
, Error
> {
75 if size
< (HEADER_SIZE
+ 2) {
76 bail
!("dectected short filename");
78 let name_len
= size
- HEADER_SIZE
;
80 if name_len
> ((libc
::FILENAME_MAX
as u64) + 1) {
81 bail
!("filename too long ({}).", name_len
);
84 let mut buffer
= vec
![0u8; name_len
as usize];
85 self.reader
.read_exact(&mut buffer
)?
;
87 let last_byte
= buffer
.pop().unwrap();
89 bail
!("filename entry not nul terminated.");
92 if (buffer
.len() == 1 && buffer
[0] == b'
.'
) || (buffer
.len() == 2 && buffer
[0] == b'
.'
&& buffer
[1] == b'
.'
) {
93 bail
!("found invalid filename with slashes.");
96 if buffer
.iter().find(|b
| (**b
== b'
/'
)).is_some() {
97 bail
!("found invalid filename with slashes.");
100 let name
= std
::ffi
::OsString
::from_vec(buffer
);
102 bail
!("found empty filename.");
108 fn restore_attributes(&mut self, _entry
: &CaFormatEntry
) -> Result
<CaFormatHeader
, Error
> {
111 let head
: CaFormatHeader
= self.read_item()?
;
114 _
=> return Ok(head
),
119 fn restore_mode(&mut self, entry
: &CaFormatEntry
, fd
: RawFd
) -> Result
<(), Error
> {
121 let mode
= Mode
::from_bits_truncate((entry
.mode
as u32) & 0o7777);
123 nix
::sys
::stat
::fchmod(fd
, mode
)?
;
128 fn restore_mode_at(&mut self, entry
: &CaFormatEntry
, dirfd
: RawFd
, filename
: &OsStr
) -> Result
<(), Error
> {
130 let mode
= Mode
::from_bits_truncate((entry
.mode
as u32) & 0o7777);
132 // NOTE: we want :FchmodatFlags::NoFollowSymlink, but fchmodat does not support that
133 // on linux (see man fchmodat). Fortunately, we can simply avoid calling this on symlinks.
134 nix
::sys
::stat
::fchmodat(Some(dirfd
), filename
, mode
, nix
::sys
::stat
::FchmodatFlags
::FollowSymlink
)?
;
139 fn restore_ugid(&mut self, entry
: &CaFormatEntry
, fd
: RawFd
) -> Result
<(), Error
> {
141 let uid
= entry
.uid
as u32;
142 let gid
= entry
.gid
as u32;
144 let res
= unsafe { libc::fchown(fd, uid, gid) }
;
150 fn restore_ugid_at(&mut self, entry
: &CaFormatEntry
, dirfd
: RawFd
, filename
: &OsStr
) -> Result
<(), Error
> {
152 let uid
= entry
.uid
as u32;
153 let gid
= entry
.gid
as u32;
155 let res
= filename
.with_nix_path(|cstr
| unsafe {
156 libc
::fchownat(dirfd
, cstr
.as_ptr(), uid
, gid
, libc
::AT_SYMLINK_NOFOLLOW
)
163 fn restore_mtime(&mut self, entry
: &CaFormatEntry
, fd
: RawFd
) -> Result
<(), Error
> {
165 let times
= nsec_to_update_timespec(entry
.mtime
);
167 let res
= unsafe { libc::futimens(fd, ×[0]) }
;
173 fn restore_mtime_at(&mut self, entry
: &CaFormatEntry
, dirfd
: RawFd
, filename
: &OsStr
) -> Result
<(), Error
> {
175 let times
= nsec_to_update_timespec(entry
.mtime
);
177 let res
= filename
.with_nix_path(|cstr
| unsafe {
178 libc
::utimensat(dirfd
, cstr
.as_ptr(), ×
[0], libc
::AT_SYMLINK_NOFOLLOW
)
185 fn restore_device_at(&mut self, entry
: &CaFormatEntry
, dirfd
: RawFd
, filename
: &OsStr
, device
: &CaFormatDevice
) -> Result
<(), Error
> {
187 let rdev
= nix
::sys
::stat
::makedev(device
.major
, device
.minor
);
188 let mode
= ((entry
.mode
as u32) & libc
::S_IFMT
) | 0o0600;
189 let res
= filename
.with_nix_path(|cstr
| unsafe {
190 libc
::mknodat(dirfd
, cstr
.as_ptr(), mode
, rdev
)
197 fn restore_socket_at(&mut self, dirfd
: RawFd
, filename
: &OsStr
) -> Result
<(), Error
> {
199 let mode
= libc
::S_IFSOCK
| 0o0600;
200 let res
= filename
.with_nix_path(|cstr
| unsafe {
201 libc
::mknodat(dirfd
, cstr
.as_ptr(), mode
, 0)
208 fn restore_fifo_at(&mut self, dirfd
: RawFd
, filename
: &OsStr
) -> Result
<(), Error
> {
210 let mode
= libc
::S_IFIFO
| 0o0600;
211 let res
= filename
.with_nix_path(|cstr
| unsafe {
212 libc
::mkfifoat(dirfd
, cstr
.as_ptr(), mode
)
219 fn skip_bytes(&mut self, count
: usize) -> Result
<(), Error
> {
222 let todo
= count
- done
;
223 let n
= if todo
> self.skip_buffer
.len() { self.skip_buffer.len() }
else { todo }
;
224 let data
= &mut self.skip_buffer
[..n
];
225 self.reader
.read_exact(data
)?
;
231 /// Restore an archive into the specified directory.
233 /// The directory is created if it does not exist.
238 ) -> Result
<(), Error
>
239 where F
: Fn(&Path
) -> Result
<(), Error
>
242 let _
= std
::fs
::create_dir(path
);
244 let dir
= match nix
::dir
::Dir
::open(path
, nix
::fcntl
::OFlag
::O_DIRECTORY
, nix
::sys
::stat
::Mode
::empty()) {
246 Err(err
) => bail
!("unable to open target directory {:?} - {}", path
, err
),
249 self.restore_sequential(&mut path
.to_owned(), &OsString
::new(), &dir
, callback
)
252 fn restore_sequential
<F
>(
254 path
: &mut PathBuf
, // used for error reporting
255 filename
: &OsStr
, // repeats path last component
256 parent
: &nix
::dir
::Dir
,
258 ) -> Result
<(), Error
>
259 where F
: Fn(&Path
) -> Result
<(), Error
>
262 let parent_fd
= parent
.as_raw_fd();
265 let head
: CaFormatHeader
= self.read_item()?
;
266 check_ca_header
::<CaFormatEntry
>(&head
, CA_FORMAT_ENTRY
)?
;
267 let entry
: CaFormatEntry
= self.read_item()?
;
269 let mode
= entry
.mode
as u32; //fixme: upper 32bits?
271 let ifmt
= mode
& libc
::S_IFMT
;
273 if ifmt
== libc
::S_IFDIR
{
275 if filename
.is_empty() {
276 dir
= nix
::dir
::Dir
::openat(parent_fd
, ".", OFlag
::O_DIRECTORY
, Mode
::empty())?
;
278 dir
= match dir_mkdirat(parent_fd
, filename
, true) {
280 Err(err
) => bail
!("unable to open directory {:?} - {}", path
, err
),
284 let mut head
= self.restore_attributes(&entry
)?
;
286 while head
.htype
== CA_FORMAT_FILENAME
{
287 let name
= self.read_filename(head
.size
)?
;
289 println
!("NAME: {:?}", path
);
291 self.restore_sequential(path
, &name
, &dir
, callback
)?
;
294 head
= self.read_item()?
;
297 if head
.htype
!= CA_FORMAT_GOODBYE
{
298 bail
!("got unknown header type inside directory entry {:016x}", head
.htype
);
301 println
!("Skip Goodbye");
302 if head
.size
< HEADER_SIZE { bail!("detected short goodbye table"); }
304 self.skip_bytes((head
.size
- HEADER_SIZE
) as usize)?
;
306 self.restore_mode(&entry
, dir
.as_raw_fd())?
;
307 self.restore_mtime(&entry
, dir
.as_raw_fd())?
;
308 self.restore_ugid(&entry
, dir
.as_raw_fd())?
;
313 if filename
.is_empty() {
314 bail
!("got empty file name at {:?}", path
)
317 if ifmt
== libc
::S_IFLNK
{
318 // fixme: create symlink
319 //fixme: restore permission, acls, xattr, ...
321 let head
: CaFormatHeader
= self.read_item()?
;
323 CA_FORMAT_SYMLINK
=> {
324 let target
= self.read_symlink(head
.size
)?
;
325 println
!("TARGET: {:?}", target
);
326 if let Err(err
) = symlinkat(&target
, parent_fd
, filename
) {
327 bail
!("create symlink {:?} failed - {}", path
, err
);
331 bail
!("got unknown header type inside symlink entry {:016x}", head
.htype
);
335 // self.restore_mode_at(&entry, parent_fd, filename)?; //not supported on symlinks
336 self.restore_ugid_at(&entry
, parent_fd
, filename
)?
;
337 self.restore_mtime_at(&entry
, parent_fd
, filename
)?
;
342 if ifmt
== libc
::S_IFSOCK
{
344 self.restore_socket_at(parent_fd
, filename
)?
;
346 self.restore_mode_at(&entry
, parent_fd
, filename
)?
;
347 self.restore_ugid_at(&entry
, parent_fd
, filename
)?
;
348 self.restore_mtime_at(&entry
, parent_fd
, filename
)?
;
353 if ifmt
== libc
::S_IFIFO
{
355 self.restore_fifo_at(parent_fd
, filename
)?
;
357 self.restore_mode_at(&entry
, parent_fd
, filename
)?
;
358 self.restore_ugid_at(&entry
, parent_fd
, filename
)?
;
359 self.restore_mtime_at(&entry
, parent_fd
, filename
)?
;
364 if (ifmt
== libc
::S_IFBLK
) || (ifmt
== libc
::S_IFCHR
) {
366 let head
: CaFormatHeader
= self.read_item()?
;
368 CA_FORMAT_DEVICE
=> {
369 let device
: CaFormatDevice
= self.read_item()?
;
370 self.restore_device_at(&entry
, parent_fd
, filename
, &device
)?
;
373 bail
!("got unknown header type inside device entry {:016x}", head
.htype
);
377 self.restore_mode_at(&entry
, parent_fd
, filename
)?
;
378 self.restore_ugid_at(&entry
, parent_fd
, filename
)?
;
379 self.restore_mtime_at(&entry
, parent_fd
, filename
)?
;
384 if ifmt
== libc
::S_IFREG
{
386 let mut read_buffer
: [u8; 64*1024] = unsafe { std::mem::uninitialized() }
;
388 let flags
= OFlag
::O_CREAT
|OFlag
::O_WRONLY
|OFlag
::O_EXCL
;
389 let open_mode
= Mode
::from_bits_truncate(0o0600 | mode
);
391 let mut file
= match file_openat(parent_fd
, filename
, flags
, open_mode
) {
393 Err(err
) => bail
!("open file {:?} failed - {}", path
, err
),
396 let head
= self.restore_attributes(&entry
)?
;
398 if head
.htype
!= CA_FORMAT_PAYLOAD
{
399 bail
!("got unknown header type for file entry {:016x}", head
.htype
);
402 if head
.size
< HEADER_SIZE
{
403 bail
!("detected short payload");
405 let need
= (head
.size
- HEADER_SIZE
) as usize;
406 //self.reader.seek(SeekFrom::Current(need as i64))?;
410 let todo
= need
- done
;
411 let n
= if todo
> read_buffer
.len() { read_buffer.len() }
else { todo }
;
412 let data
= &mut read_buffer
[..n
];
413 self.reader
.read_exact(data
)?
;
414 file
.write_all(data
)?
;
418 self.restore_mode(&entry
, file
.as_raw_fd())?
;
419 self.restore_mtime(&entry
, file
.as_raw_fd())?
;
420 self.restore_ugid(&entry
, file
.as_raw_fd())?
;
428 /// List/Dump archive content.
430 /// Simply print the list of contained files. This dumps archive
431 /// format details when the verbose flag is set (useful for debug).
432 pub fn dump_entry
<W
: std
::io
::Write
>(
437 ) -> Result
<(), Error
> {
439 let print_head
= |head
: &CaFormatHeader
| {
440 println
!("Type: {:016x}", head
.htype
);
441 println
!("Size: {}", head
.size
);
444 let head
: CaFormatHeader
= self.read_item()?
;
446 println
!("Path: {:?}", path
);
449 println
!("{:?}", path
);
452 check_ca_header
::<CaFormatEntry
>(&head
, CA_FORMAT_ENTRY
)?
;
453 let entry
: CaFormatEntry
= self.read_item()?
;
456 println
!("Mode: {:08x} {:08x}", entry
.mode
, (entry
.mode
as u32) & libc
::S_IFDIR
);
458 // fixme: dump attributes (ACLs, ...)
460 let ifmt
= (entry
.mode
as u32) & libc
::S_IFMT
;
462 if ifmt
== libc
::S_IFDIR
{
464 let mut entry_count
= 0;
467 let head
: CaFormatHeader
= self.read_item()?
;
473 CA_FORMAT_FILENAME
=> {
474 let name
= self.read_filename(head
.size
)?
;
475 if verbose { println!("Name: {:?}
", name); }
478 self.dump_entry(path, verbose, output)?;
481 CA_FORMAT_GOODBYE => {
482 let table_size = (head.size - HEADER_SIZE) as usize;
484 println!("Goodbye
: {:?}
", path);
485 self.dump_goodby_entries(entry_count, table_size)?;
487 self.skip_bytes(table_size)?;
492 panic!("got unexpected header
type inside directory
");
498 let head: CaFormatHeader = self.read_item()?;
505 CA_FORMAT_SYMLINK => {
506 let target = self.read_symlink(head.size)?;
508 println!("Symlink
: {:?}
", target);
511 CA_FORMAT_DEVICE => {
512 let device: CaFormatDevice = self.read_item()?;
514 println!("Device
: {}
, {}
", device.major, device.minor);
517 CA_FORMAT_PAYLOAD => {
518 let payload_size = (head.size - HEADER_SIZE) as usize;
520 println!("Payload
: {}
", payload_size);
522 self.skip_bytes(payload_size)?;
525 panic!("got unexpected header
type inside non
-directory
");
533 fn dump_goodby_entries(
537 ) -> Result<(), Error> {
539 let item_size = std::mem::size_of::<CaFormatGoodbyeItem>();
540 if table_size < item_size {
541 bail!("Goodbye table to
small ({}
< {}
)", table_size, item_size);
543 if (table_size % item_size) != 0 {
544 bail!("Goodbye table with strange
size ({}
)", table_size);
547 let entries = table_size / item_size;
549 if entry_count != (entries - 1) {
550 bail!("Goodbye table with wrong entry
count ({}
!= {}
)", entry_count, entries - 1);
556 let item: CaFormatGoodbyeItem = self.read_item()?;
558 if item.hash == CA_FORMAT_GOODBYE_TAIL_MARKER {
559 if count != entries {
560 bail!("unexpected goodbye tail marker
");
562 println!("Goodby tail mark
.");
565 println!("Goodby item
: offset {}
, size {}
, hash {:016x}
", item.offset, item.size, item.hash);
566 if count >= (table_size / item_size) {
567 bail!("too many goodbye
items (no tail marker
)");
575 fn file_openat(parent: RawFd, filename: &OsStr, flags: OFlag, mode: Mode) -> Result<std::fs::File, Error> {
577 let fd = filename.with_nix_path(|cstr| {
578 nix::fcntl::openat(parent, cstr.as_ref(), flags, mode)
581 let file = unsafe { std::fs::File::from_raw_fd(fd) };
586 fn dir_mkdirat(parent: RawFd, filename: &OsStr, create_new: bool) -> Result<nix::dir::Dir, nix::Error> {
588 // call mkdirat first
589 let res = filename.with_nix_path(|cstr| unsafe {
590 libc::mkdirat(parent, cstr.as_ptr(), libc::S_IRWXU)
593 match Errno::result(res) {
596 if err == nix::Error::Sys(nix::errno::Errno::EEXIST) {
597 if create_new { return Err(err); }
604 let dir = nix::dir::Dir::openat(parent, filename, OFlag::O_DIRECTORY, Mode::empty())?;
609 fn symlinkat(target: &Path, parent: RawFd, linkname: &OsStr) -> Result<(), Error> {
611 target.with_nix_path(|target| {
612 linkname.with_nix_path(|linkname| {
613 let res = unsafe { libc::symlinkat(target.as_ptr(), parent, linkname.as_ptr()) };
620 fn nsec_to_update_timespec(mtime_nsec: u64) -> [libc::timespec; 2] {
623 const UTIME_OMIT: i64 = ((1 << 30) - 2);
624 const NANOS_PER_SEC: i64 = 1_000_000_000;
626 let sec = (mtime_nsec as i64) / NANOS_PER_SEC;
627 let nsec = (mtime_nsec as i64) % NANOS_PER_SEC;
629 let times: [libc::timespec; 2] = [
630 libc::timespec { tv_sec: 0, tv_nsec: UTIME_OMIT },
631 libc::timespec { tv_sec: sec, tv_nsec: nsec },