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 mut 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
)
221 path
: &Path
, // used for error reporting
223 ) -> Result
<(), Error
>
224 where F
: Fn(&Path
) -> Result
<(), Error
>
227 let _
= std
::fs
::create_dir(path
);
229 let dir
= match nix
::dir
::Dir
::open(path
, nix
::fcntl
::OFlag
::O_DIRECTORY
, nix
::sys
::stat
::Mode
::empty()) {
231 Err(err
) => bail
!("unable to open target directory {:?} - {}", path
, err
),
234 self.restore_sequential(&mut path
.to_owned(), &OsString
::new(), &dir
, callback
)
237 fn restore_sequential
<F
>(
239 path
: &mut PathBuf
, // used for error reporting
240 filename
: &OsStr
, // repeats path last component
241 parent
: &nix
::dir
::Dir
,
243 ) -> Result
<(), Error
>
244 where F
: Fn(&Path
) -> Result
<(), Error
>
247 let parent_fd
= parent
.as_raw_fd();
250 let head
: CaFormatHeader
= self.read_item()?
;
251 check_ca_header
::<CaFormatEntry
>(&head
, CA_FORMAT_ENTRY
)?
;
252 let entry
: CaFormatEntry
= self.read_item()?
;
254 let mode
= entry
.mode
as u32; //fixme: upper 32bits?
256 let ifmt
= mode
& libc
::S_IFMT
;
258 if ifmt
== libc
::S_IFDIR
{
260 if filename
.is_empty() {
261 dir
= nix
::dir
::Dir
::openat(parent_fd
, ".", OFlag
::O_DIRECTORY
, Mode
::empty())?
;
263 dir
= match dir_mkdirat(parent_fd
, filename
, true) {
265 Err(err
) => bail
!("unable to open directory {:?} - {}", path
, err
),
269 let mut head
= self.restore_attributes(&entry
)?
;
271 while head
.htype
== CA_FORMAT_FILENAME
{
272 let name
= self.read_filename(head
.size
)?
;
274 println
!("NAME: {:?}", path
);
276 self.restore_sequential(path
, &name
, &dir
, callback
)?
;
279 head
= self.read_item()?
;
282 if head
.htype
!= CA_FORMAT_GOODBYE
{
283 bail
!("got unknown header type inside directory entry {:016x}", head
.htype
);
286 println
!("Skip Goodbye");
287 if head
.size
< HEADER_SIZE { bail!("detected short goodbye table"); }
289 // self.reader.seek(SeekFrom::Current((head.size - HEADER_SIZE) as i64))?;
291 let skip
= (head
.size
- HEADER_SIZE
) as usize;
293 let todo
= skip
- done
;
294 let n
= if todo
> self.skip_buffer
.len() { self.skip_buffer.len() }
else { todo }
;
295 let data
= &mut self.skip_buffer
[..n
];
296 self.reader
.read_exact(data
)?
;
301 self.restore_mode(&entry
, dir
.as_raw_fd())?
;
302 self.restore_mtime(&entry
, dir
.as_raw_fd())?
;
303 self.restore_ugid(&entry
, dir
.as_raw_fd())?
;
308 if filename
.is_empty() {
309 bail
!("got empty file name at {:?}", path
)
312 if ifmt
== libc
::S_IFLNK
{
313 // fixme: create symlink
314 //fixme: restore permission, acls, xattr, ...
316 let head
: CaFormatHeader
= self.read_item()?
;
318 CA_FORMAT_SYMLINK
=> {
319 let target
= self.read_symlink(head
.size
)?
;
320 println
!("TARGET: {:?}", target
);
321 if let Err(err
) = symlinkat(&target
, parent_fd
, filename
) {
322 bail
!("create symlink {:?} failed - {}", path
, err
);
326 bail
!("got unknown header type inside symlink entry {:016x}", head
.htype
);
330 // self.restore_mode_at(&entry, parent_fd, filename)?; //not supported on symlinks
331 self.restore_ugid_at(&entry
, parent_fd
, filename
)?
;
332 self.restore_mtime_at(&entry
, parent_fd
, filename
)?
;
337 if ifmt
== libc
::S_IFSOCK
{
339 self.restore_socket_at(parent_fd
, filename
)?
;
341 self.restore_mode_at(&entry
, parent_fd
, filename
)?
;
342 self.restore_ugid_at(&entry
, parent_fd
, filename
)?
;
343 self.restore_mtime_at(&entry
, parent_fd
, filename
)?
;
348 if ifmt
== libc
::S_IFIFO
{
350 self.restore_fifo_at(parent_fd
, filename
)?
;
352 self.restore_mode_at(&entry
, parent_fd
, filename
)?
;
353 self.restore_ugid_at(&entry
, parent_fd
, filename
)?
;
354 self.restore_mtime_at(&entry
, parent_fd
, filename
)?
;
359 if (ifmt
== libc
::S_IFBLK
) || (ifmt
== libc
::S_IFCHR
) {
361 let head
: CaFormatHeader
= self.read_item()?
;
363 CA_FORMAT_DEVICE
=> {
364 let device
: CaFormatDevice
= self.read_item()?
;
365 self.restore_device_at(&entry
, parent_fd
, filename
, &device
)?
;
368 bail
!("got unknown header type inside device entry {:016x}", head
.htype
);
372 self.restore_mode_at(&entry
, parent_fd
, filename
)?
;
373 self.restore_ugid_at(&entry
, parent_fd
, filename
)?
;
374 self.restore_mtime_at(&entry
, parent_fd
, filename
)?
;
379 if ifmt
== libc
::S_IFREG
{
381 let mut read_buffer
: [u8; 64*1024] = unsafe { std::mem::uninitialized() }
;
383 let flags
= OFlag
::O_CREAT
|OFlag
::O_WRONLY
|OFlag
::O_EXCL
;
384 let open_mode
= Mode
::from_bits_truncate(0o0600 | mode
);
386 let mut file
= match file_openat(parent_fd
, filename
, flags
, open_mode
) {
388 Err(err
) => bail
!("open file {:?} failed - {}", path
, err
),
391 let head
= self.restore_attributes(&entry
)?
;
393 if head
.htype
!= CA_FORMAT_PAYLOAD
{
394 bail
!("got unknown header type for file entry {:016x}", head
.htype
);
397 if head
.size
< HEADER_SIZE
{
398 bail
!("detected short payload");
400 let need
= (head
.size
- HEADER_SIZE
) as usize;
401 //self.reader.seek(SeekFrom::Current(need as i64))?;
405 let todo
= need
- done
;
406 let n
= if todo
> read_buffer
.len() { read_buffer.len() }
else { todo }
;
407 let data
= &mut read_buffer
[..n
];
408 self.reader
.read_exact(data
)?
;
409 file
.write_all(data
)?
;
413 self.restore_mode(&entry
, file
.as_raw_fd())?
;
414 self.restore_mtime(&entry
, file
.as_raw_fd())?
;
415 self.restore_ugid(&entry
, file
.as_raw_fd())?
;
424 fn file_openat(parent
: RawFd
, filename
: &OsStr
, flags
: OFlag
, mode
: Mode
) -> Result
<std
::fs
::File
, Error
> {
426 let fd
= filename
.with_nix_path(|cstr
| {
427 nix
::fcntl
::openat(parent
, cstr
.as_ref(), flags
, mode
)
430 let file
= unsafe { std::fs::File::from_raw_fd(fd) }
;
435 fn dir_mkdirat(parent
: RawFd
, filename
: &OsStr
, create_new
: bool
) -> Result
<nix
::dir
::Dir
, nix
::Error
> {
437 // call mkdirat first
438 let res
= filename
.with_nix_path(|cstr
| unsafe {
439 libc
::mkdirat(parent
, cstr
.as_ptr(), libc
::S_IRWXU
)
442 match Errno
::result(res
) {
445 if err
== nix
::Error
::Sys(nix
::errno
::Errno
::EEXIST
) {
446 if create_new { return Err(err); }
453 let dir
= nix
::dir
::Dir
::openat(parent
, filename
, OFlag
::O_DIRECTORY
, Mode
::empty())?
;
458 fn symlinkat(target
: &Path
, parent
: RawFd
, linkname
: &OsStr
) -> Result
<(), Error
> {
460 target
.with_nix_path(|target
| {
461 linkname
.with_nix_path(|linkname
| {
462 let res
= unsafe { libc::symlinkat(target.as_ptr(), parent, linkname.as_ptr()) }
;
469 fn nsec_to_update_timespec(mtime_nsec
: u64) -> [libc
::timespec
; 2] {
472 const UTIME_OMIT
: i64 = ((1 << 30) - 2);
473 const NANOS_PER_SEC
: i64 = 1_000_000_000;
475 let sec
= (mtime_nsec
as i64) / NANOS_PER_SEC
;
476 let nsec
= (mtime_nsec
as i64) % NANOS_PER_SEC
;
478 let times
: [libc
::timespec
; 2] = [
479 libc
::timespec { tv_sec: 0, tv_nsec: UTIME_OMIT }
,
480 libc
::timespec { tv_sec: sec, tv_nsec: nsec }
,