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 SequentialDecoder
<'a
, R
: Read
> {
30 const HEADER_SIZE
: u64 = std
::mem
::size_of
::<CaFormatHeader
>() as u64;
32 impl <'a
, R
: Read
> SequentialDecoder
<'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 pub (crate) fn get_reader_mut(&mut self) -> & mut R
{
43 pub (crate) fn read_item
<T
: Endian
>(&mut self) -> Result
<T
, Error
> {
45 let mut result
: T
= unsafe { std::mem::uninitialized() }
;
47 let buffer
= unsafe { std
::slice
::from_raw_parts_mut(
48 &mut result
as *mut T
as *mut u8,
49 std
::mem
::size_of
::<T
>()
52 self.reader
.read_exact(buffer
)?
;
57 fn read_symlink(&mut self, size
: u64) -> Result
<PathBuf
, Error
> {
58 if size
< (HEADER_SIZE
+ 2) {
59 bail
!("dectected short symlink target.");
61 let target_len
= size
- HEADER_SIZE
;
63 if target_len
> (libc
::PATH_MAX
as u64) {
64 bail
!("symlink target too long ({}).", target_len
);
67 let mut buffer
= vec
![0u8; target_len
as usize];
68 self.reader
.read_exact(&mut buffer
)?
;
70 let last_byte
= buffer
.pop().unwrap();
72 bail
!("symlink target not nul terminated.");
75 Ok(PathBuf
::from(std
::ffi
::OsString
::from_vec(buffer
)))
78 pub (crate) fn read_filename(&mut self, size
: u64) -> Result
<OsString
, Error
> {
79 if size
< (HEADER_SIZE
+ 2) {
80 bail
!("dectected short filename");
82 let name_len
= size
- HEADER_SIZE
;
84 if name_len
> ((libc
::FILENAME_MAX
as u64) + 1) {
85 bail
!("filename too long ({}).", name_len
);
88 let mut buffer
= vec
![0u8; name_len
as usize];
89 self.reader
.read_exact(&mut buffer
)?
;
91 let last_byte
= buffer
.pop().unwrap();
93 bail
!("filename entry not nul terminated.");
96 if (buffer
.len() == 1 && buffer
[0] == b'
.'
) || (buffer
.len() == 2 && buffer
[0] == b'
.'
&& buffer
[1] == b'
.'
) {
97 bail
!("found invalid filename with slashes.");
100 if buffer
.iter().find(|b
| (**b
== b'
/'
)).is_some() {
101 bail
!("found invalid filename with slashes.");
104 let name
= std
::ffi
::OsString
::from_vec(buffer
);
106 bail
!("found empty filename.");
112 fn restore_attributes(&mut self, _entry
: &CaFormatEntry
) -> Result
<CaFormatHeader
, Error
> {
115 let head
: CaFormatHeader
= self.read_item()?
;
118 _
=> return Ok(head
),
123 fn restore_mode(&mut self, entry
: &CaFormatEntry
, fd
: RawFd
) -> Result
<(), Error
> {
125 let mode
= Mode
::from_bits_truncate((entry
.mode
as u32) & 0o7777);
127 nix
::sys
::stat
::fchmod(fd
, mode
)?
;
132 fn restore_mode_at(&mut self, entry
: &CaFormatEntry
, dirfd
: RawFd
, filename
: &OsStr
) -> Result
<(), Error
> {
134 let mode
= Mode
::from_bits_truncate((entry
.mode
as u32) & 0o7777);
136 // NOTE: we want :FchmodatFlags::NoFollowSymlink, but fchmodat does not support that
137 // on linux (see man fchmodat). Fortunately, we can simply avoid calling this on symlinks.
138 nix
::sys
::stat
::fchmodat(Some(dirfd
), filename
, mode
, nix
::sys
::stat
::FchmodatFlags
::FollowSymlink
)?
;
143 fn restore_ugid(&mut self, entry
: &CaFormatEntry
, fd
: RawFd
) -> Result
<(), Error
> {
145 let uid
= entry
.uid
as u32;
146 let gid
= entry
.gid
as u32;
148 let res
= unsafe { libc::fchown(fd, uid, gid) }
;
154 fn restore_ugid_at(&mut self, entry
: &CaFormatEntry
, dirfd
: RawFd
, filename
: &OsStr
) -> Result
<(), Error
> {
156 let uid
= entry
.uid
as u32;
157 let gid
= entry
.gid
as u32;
159 let res
= filename
.with_nix_path(|cstr
| unsafe {
160 libc
::fchownat(dirfd
, cstr
.as_ptr(), uid
, gid
, libc
::AT_SYMLINK_NOFOLLOW
)
167 fn restore_mtime(&mut self, entry
: &CaFormatEntry
, fd
: RawFd
) -> Result
<(), Error
> {
169 let times
= nsec_to_update_timespec(entry
.mtime
);
171 let res
= unsafe { libc::futimens(fd, ×[0]) }
;
177 fn restore_mtime_at(&mut self, entry
: &CaFormatEntry
, dirfd
: RawFd
, filename
: &OsStr
) -> Result
<(), Error
> {
179 let times
= nsec_to_update_timespec(entry
.mtime
);
181 let res
= filename
.with_nix_path(|cstr
| unsafe {
182 libc
::utimensat(dirfd
, cstr
.as_ptr(), ×
[0], libc
::AT_SYMLINK_NOFOLLOW
)
189 fn restore_device_at(&mut self, entry
: &CaFormatEntry
, dirfd
: RawFd
, filename
: &OsStr
, device
: &CaFormatDevice
) -> Result
<(), Error
> {
191 let rdev
= nix
::sys
::stat
::makedev(device
.major
, device
.minor
);
192 let mode
= ((entry
.mode
as u32) & libc
::S_IFMT
) | 0o0600;
193 let res
= filename
.with_nix_path(|cstr
| unsafe {
194 libc
::mknodat(dirfd
, cstr
.as_ptr(), mode
, rdev
)
201 fn restore_socket_at(&mut self, dirfd
: RawFd
, filename
: &OsStr
) -> Result
<(), Error
> {
203 let mode
= libc
::S_IFSOCK
| 0o0600;
204 let res
= filename
.with_nix_path(|cstr
| unsafe {
205 libc
::mknodat(dirfd
, cstr
.as_ptr(), mode
, 0)
212 fn restore_fifo_at(&mut self, dirfd
: RawFd
, filename
: &OsStr
) -> Result
<(), Error
> {
214 let mode
= libc
::S_IFIFO
| 0o0600;
215 let res
= filename
.with_nix_path(|cstr
| unsafe {
216 libc
::mkfifoat(dirfd
, cstr
.as_ptr(), mode
)
223 fn skip_bytes(&mut self, count
: usize) -> Result
<(), Error
> {
226 let todo
= count
- done
;
227 let n
= if todo
> self.skip_buffer
.len() { self.skip_buffer.len() }
else { todo }
;
228 let data
= &mut self.skip_buffer
[..n
];
229 self.reader
.read_exact(data
)?
;
235 /// Restore an archive into the specified directory.
237 /// The directory is created if it does not exist.
242 ) -> Result
<(), Error
>
243 where F
: Fn(&Path
) -> Result
<(), Error
>
246 let _
= std
::fs
::create_dir(path
);
248 let dir
= match nix
::dir
::Dir
::open(path
, nix
::fcntl
::OFlag
::O_DIRECTORY
, nix
::sys
::stat
::Mode
::empty()) {
250 Err(err
) => bail
!("unable to open target directory {:?} - {}", path
, err
),
253 self.restore_sequential(&mut path
.to_owned(), &OsString
::new(), &dir
, callback
)
256 fn restore_sequential
<F
>(
258 path
: &mut PathBuf
, // used for error reporting
259 filename
: &OsStr
, // repeats path last component
260 parent
: &nix
::dir
::Dir
,
262 ) -> Result
<(), Error
>
263 where F
: Fn(&Path
) -> Result
<(), Error
>
266 let parent_fd
= parent
.as_raw_fd();
269 let head
: CaFormatHeader
= self.read_item()?
;
270 check_ca_header
::<CaFormatEntry
>(&head
, CA_FORMAT_ENTRY
)?
;
271 let entry
: CaFormatEntry
= self.read_item()?
;
275 let mode
= entry
.mode
as u32; //fixme: upper 32bits?
277 let ifmt
= mode
& libc
::S_IFMT
;
279 if ifmt
== libc
::S_IFDIR
{
281 if filename
.is_empty() {
282 dir
= nix
::dir
::Dir
::openat(parent_fd
, ".", OFlag
::O_DIRECTORY
, Mode
::empty())?
;
284 dir
= match dir_mkdirat(parent_fd
, filename
, true) {
286 Err(err
) => bail
!("unable to open directory {:?} - {}", path
, err
),
290 let mut head
= self.restore_attributes(&entry
)?
;
292 while head
.htype
== CA_FORMAT_FILENAME
{
293 let name
= self.read_filename(head
.size
)?
;
295 //println!("NAME: {:?}", path);
296 self.restore_sequential(path
, &name
, &dir
, callback
)?
;
299 head
= self.read_item()?
;
302 if head
.htype
!= CA_FORMAT_GOODBYE
{
303 bail
!("got unknown header type inside directory entry {:016x}", head
.htype
);
306 //println!("Skip Goodbye");
307 if head
.size
< HEADER_SIZE { bail!("detected short goodbye table"); }
309 self.skip_bytes((head
.size
- HEADER_SIZE
) as usize)?
;
311 self.restore_mode(&entry
, dir
.as_raw_fd())?
;
312 self.restore_mtime(&entry
, dir
.as_raw_fd())?
;
313 self.restore_ugid(&entry
, dir
.as_raw_fd())?
;
318 if filename
.is_empty() {
319 bail
!("got empty file name at {:?}", path
)
322 if ifmt
== libc
::S_IFLNK
{
323 // fixme: create symlink
324 //fixme: restore permission, acls, xattr, ...
326 let head
: CaFormatHeader
= self.read_item()?
;
328 CA_FORMAT_SYMLINK
=> {
329 let target
= self.read_symlink(head
.size
)?
;
330 //println!("TARGET: {:?}", target);
331 if let Err(err
) = symlinkat(&target
, parent_fd
, filename
) {
332 bail
!("create symlink {:?} failed - {}", path
, err
);
336 bail
!("got unknown header type inside symlink entry {:016x}", head
.htype
);
340 // self.restore_mode_at(&entry, parent_fd, filename)?; //not supported on symlinks
341 self.restore_ugid_at(&entry
, parent_fd
, filename
)?
;
342 self.restore_mtime_at(&entry
, parent_fd
, filename
)?
;
347 if ifmt
== libc
::S_IFSOCK
{
349 self.restore_socket_at(parent_fd
, filename
)?
;
351 self.restore_mode_at(&entry
, parent_fd
, filename
)?
;
352 self.restore_ugid_at(&entry
, parent_fd
, filename
)?
;
353 self.restore_mtime_at(&entry
, parent_fd
, filename
)?
;
358 if ifmt
== libc
::S_IFIFO
{
360 self.restore_fifo_at(parent_fd
, filename
)?
;
362 self.restore_mode_at(&entry
, parent_fd
, filename
)?
;
363 self.restore_ugid_at(&entry
, parent_fd
, filename
)?
;
364 self.restore_mtime_at(&entry
, parent_fd
, filename
)?
;
369 if (ifmt
== libc
::S_IFBLK
) || (ifmt
== libc
::S_IFCHR
) {
371 let head
: CaFormatHeader
= self.read_item()?
;
373 CA_FORMAT_DEVICE
=> {
374 let device
: CaFormatDevice
= self.read_item()?
;
375 self.restore_device_at(&entry
, parent_fd
, filename
, &device
)?
;
378 bail
!("got unknown header type inside device entry {:016x}", head
.htype
);
382 self.restore_mode_at(&entry
, parent_fd
, filename
)?
;
383 self.restore_ugid_at(&entry
, parent_fd
, filename
)?
;
384 self.restore_mtime_at(&entry
, parent_fd
, filename
)?
;
389 if ifmt
== libc
::S_IFREG
{
391 let mut read_buffer
: [u8; 64*1024] = unsafe { std::mem::uninitialized() }
;
393 let flags
= OFlag
::O_CREAT
|OFlag
::O_WRONLY
|OFlag
::O_EXCL
;
394 let open_mode
= Mode
::from_bits_truncate(0o0600 | mode
);
396 let mut file
= match file_openat(parent_fd
, filename
, flags
, open_mode
) {
398 Err(err
) => bail
!("open file {:?} failed - {}", path
, err
),
401 let head
= self.restore_attributes(&entry
)?
;
403 if head
.htype
!= CA_FORMAT_PAYLOAD
{
404 bail
!("got unknown header type for file entry {:016x}", head
.htype
);
407 if head
.size
< HEADER_SIZE
{
408 bail
!("detected short payload");
410 let need
= (head
.size
- HEADER_SIZE
) as usize;
411 //self.reader.seek(SeekFrom::Current(need as i64))?;
415 let todo
= need
- done
;
416 let n
= if todo
> read_buffer
.len() { read_buffer.len() }
else { todo }
;
417 let data
= &mut read_buffer
[..n
];
418 self.reader
.read_exact(data
)?
;
419 file
.write_all(data
)?
;
423 self.restore_mode(&entry
, file
.as_raw_fd())?
;
424 self.restore_mtime(&entry
, file
.as_raw_fd())?
;
425 self.restore_ugid(&entry
, file
.as_raw_fd())?
;
433 /// List/Dump archive content.
435 /// Simply print the list of contained files. This dumps archive
436 /// format details when the verbose flag is set (useful for debug).
437 pub fn dump_entry
<W
: std
::io
::Write
>(
442 ) -> Result
<(), Error
> {
444 let print_head
= |head
: &CaFormatHeader
| {
445 println
!("Type: {:016x}", head
.htype
);
446 println
!("Size: {}", head
.size
);
449 let head
: CaFormatHeader
= self.read_item()?
;
451 println
!("Path: {:?}", path
);
454 println
!("{:?}", path
);
457 check_ca_header
::<CaFormatEntry
>(&head
, CA_FORMAT_ENTRY
)?
;
458 let entry
: CaFormatEntry
= self.read_item()?
;
461 println
!("Mode: {:08x} {:08x}", entry
.mode
, (entry
.mode
as u32) & libc
::S_IFDIR
);
463 // fixme: dump attributes (ACLs, ...)
465 let ifmt
= (entry
.mode
as u32) & libc
::S_IFMT
;
467 if ifmt
== libc
::S_IFDIR
{
469 let mut entry_count
= 0;
472 let head
: CaFormatHeader
= self.read_item()?
;
478 CA_FORMAT_FILENAME
=> {
479 let name
= self.read_filename(head
.size
)?
;
480 if verbose { println!("Name: {:?}
", name); }
483 self.dump_entry(path, verbose, output)?;
486 CA_FORMAT_GOODBYE => {
487 let table_size = (head.size - HEADER_SIZE) as usize;
489 println!("Goodbye
: {:?}
", path);
490 self.dump_goodby_entries(entry_count, table_size)?;
492 self.skip_bytes(table_size)?;
497 panic!("got unexpected header
type inside directory
");
503 let head: CaFormatHeader = self.read_item()?;
510 CA_FORMAT_SYMLINK => {
511 let target = self.read_symlink(head.size)?;
513 println!("Symlink
: {:?}
", target);
516 CA_FORMAT_DEVICE => {
517 let device: CaFormatDevice = self.read_item()?;
519 println!("Device
: {}
, {}
", device.major, device.minor);
522 CA_FORMAT_PAYLOAD => {
523 let payload_size = (head.size - HEADER_SIZE) as usize;
525 println!("Payload
: {}
", payload_size);
527 self.skip_bytes(payload_size)?;
530 panic!("got unexpected header
type inside non
-directory
");
538 fn dump_goodby_entries(
542 ) -> Result<(), Error> {
544 let item_size = std::mem::size_of::<CaFormatGoodbyeItem>();
545 if table_size < item_size {
546 bail!("Goodbye table to
small ({}
< {}
)", table_size, item_size);
548 if (table_size % item_size) != 0 {
549 bail!("Goodbye table with strange
size ({}
)", table_size);
552 let entries = table_size / item_size;
554 if entry_count != (entries - 1) {
555 bail!("Goodbye table with wrong entry
count ({}
!= {}
)", entry_count, entries - 1);
561 let item: CaFormatGoodbyeItem = self.read_item()?;
563 if item.hash == CA_FORMAT_GOODBYE_TAIL_MARKER {
564 if count != entries {
565 bail!("unexpected goodbye tail marker
");
567 println!("Goodby tail mark
.");
570 println!("Goodby item
: offset {}
, size {}
, hash {:016x}
", item.offset, item.size, item.hash);
571 if count >= (table_size / item_size) {
572 bail!("too many goodbye
items (no tail marker
)");
580 fn file_openat(parent: RawFd, filename: &OsStr, flags: OFlag, mode: Mode) -> Result<std::fs::File, Error> {
582 let fd = filename.with_nix_path(|cstr| {
583 nix::fcntl::openat(parent, cstr.as_ref(), flags, mode)
586 let file = unsafe { std::fs::File::from_raw_fd(fd) };
591 fn dir_mkdirat(parent: RawFd, filename: &OsStr, create_new: bool) -> Result<nix::dir::Dir, nix::Error> {
593 // call mkdirat first
594 let res = filename.with_nix_path(|cstr| unsafe {
595 libc::mkdirat(parent, cstr.as_ptr(), libc::S_IRWXU)
598 match Errno::result(res) {
601 if err == nix::Error::Sys(nix::errno::Errno::EEXIST) {
602 if create_new { return Err(err); }
609 let dir = nix::dir::Dir::openat(parent, filename, OFlag::O_DIRECTORY, Mode::empty())?;
614 fn symlinkat(target: &Path, parent: RawFd, linkname: &OsStr) -> Result<(), Error> {
616 target.with_nix_path(|target| {
617 linkname.with_nix_path(|linkname| {
618 let res = unsafe { libc::symlinkat(target.as_ptr(), parent, linkname.as_ptr()) };
625 fn nsec_to_update_timespec(mtime_nsec: u64) -> [libc::timespec; 2] {
628 const UTIME_OMIT: i64 = ((1 << 30) - 2);
629 const NANOS_PER_SEC: i64 = 1_000_000_000;
631 let sec = (mtime_nsec as i64) / NANOS_PER_SEC;
632 let nsec = (mtime_nsec as i64) % NANOS_PER_SEC;
634 let times: [libc::timespec; 2] = [
635 libc::timespec { tv_sec: 0, tv_nsec: UTIME_OMIT },
636 libc::timespec { tv_sec: sec, tv_nsec: nsec },