1 //! Code for extraction of pxar contents onto the file system.
3 use std
::collections
::HashMap
;
4 use std
::ffi
::{CStr, CString, OsStr, OsString}
;
6 use std
::os
::unix
::ffi
::OsStrExt
;
7 use std
::os
::unix
::io
::{AsRawFd, FromRawFd, RawFd}
;
8 use std
::path
::{Path, PathBuf}
;
9 use std
::sync
::{Arc, Mutex}
;
11 use anyhow
::{bail, format_err, Error}
;
13 use nix
::fcntl
::OFlag
;
14 use nix
::sys
::stat
::Mode
;
16 use pathpatterns
::{MatchEntry, MatchList, MatchType}
;
17 use pxar
::accessor
::aio
::{Accessor, FileContents, FileEntry}
;
18 use pxar
::decoder
::{aio::Decoder, Contents}
;
19 use pxar
::format
::Device
;
20 use pxar
::{Entry, EntryKind, Metadata}
;
22 use proxmox_io
::{sparse_copy, sparse_copy_async}
;
23 use proxmox_sys
::c_result
;
24 use proxmox_sys
::fs
::{create_path, CreateOptions}
;
26 use proxmox_compression
::zip
::{ZipEncoder, ZipEntry}
;
28 use crate::pxar
::dir_stack
::PxarDirStack
;
29 use crate::pxar
::metadata
;
30 use crate::pxar
::Flags
;
32 pub struct PxarExtractOptions
<'a
> {
33 pub match_list
: &'a
[MatchEntry
],
34 pub extract_match_default
: bool
,
35 pub allow_existing_dirs
: bool
,
37 pub on_error
: Option
<ErrorHandler
>,
40 pub type ErrorHandler
= Box
<dyn FnMut(Error
) -> Result
<(), Error
> + Send
>;
42 pub fn extract_archive
<T
, F
>(
43 mut decoder
: pxar
::decoder
::Decoder
<T
>,
47 options
: PxarExtractOptions
,
48 ) -> Result
<(), Error
>
50 T
: pxar
::decoder
::SeqRead
,
53 // we use this to keep track of our directory-traversal
54 decoder
.enable_goodbye_entries(true);
58 .ok_or_else(|| format_err
!("found empty pxar archive"))?
59 .map_err(|err
| format_err
!("error reading pxar archive: {}", err
))?
;
62 bail
!("pxar archive does not start with a directory entry!");
68 Some(CreateOptions
::new().perm(Mode
::from_bits_truncate(0o700))),
70 .map_err(|err
| format_err
!("error creating directory {:?}: {}", destination
, err
))?
;
74 OFlag
::O_DIRECTORY
| OFlag
::O_CLOEXEC
,
77 .map_err(|err
| format_err
!("unable to open target directory {:?}: {}", destination
, err
,))?
;
79 let mut extractor
= Extractor
::new(
81 root
.metadata().clone(),
82 options
.allow_existing_dirs
,
87 if let Some(on_error
) = options
.on_error
{
88 extractor
.on_error(on_error
);
91 let mut match_stack
= Vec
::new();
92 let mut err_path_stack
= vec
![OsString
::from("/")];
93 let mut current_match
= options
.extract_match_default
;
94 while let Some(entry
) = decoder
.next() {
95 let entry
= entry
.map_err(|err
| format_err
!("error reading pxar archive: {}", err
))?
;
97 let file_name_os
= entry
.file_name();
99 // safety check: a file entry in an archive must never contain slashes:
100 if file_name_os
.as_bytes().contains(&b'
/'
) {
101 bail
!("archive file entry contains slashes, which is invalid and a security concern");
104 let file_name
= CString
::new(file_name_os
.as_bytes())
105 .map_err(|_
| format_err
!("encountered file name with null-bytes"))?
;
107 let metadata
= entry
.metadata();
109 extractor
.set_path(entry
.path().as_os_str().to_owned());
111 let match_result
= options
.match_list
.matches(
112 entry
.path().as_os_str().as_bytes(),
113 Some(metadata
.file_type() as u32),
116 let did_match
= match match_result
{
117 Some(MatchType
::Include
) => true,
118 Some(MatchType
::Exclude
) => false,
119 None
=> current_match
,
121 match (did_match
, entry
.kind()) {
122 (_
, EntryKind
::Directory
) => {
123 callback(entry
.path());
125 let create
= current_match
&& match_result
!= Some(MatchType
::Exclude
);
127 .enter_directory(file_name_os
.to_owned(), metadata
.clone(), create
)
128 .map_err(|err
| format_err
!("error at entry {:?}: {}", file_name_os
, err
))?
;
130 // We're starting a new directory, push our old matching state and replace it with
132 match_stack
.push(current_match
);
133 current_match
= did_match
;
135 // When we hit the goodbye table we'll try to apply metadata to the directory, but
136 // the Goodbye entry will not contain the path, so push it to our path stack for
138 err_path_stack
.push(extractor
.clone_path());
142 (_
, EntryKind
::GoodbyeTable
) => {
145 extractor
.set_path(err_path_stack
.pop().ok_or_else(|| {
147 "error at entry {:?}: unexpected end of directory",
154 .map_err(|err
| format_err
!("error at entry {:?}: {}", file_name_os
, err
))?
;
156 // We left a directory, also get back our previous matching state. This is in sync
157 // with `dir_stack` so this should never be empty except for the final goodbye
158 // table, in which case we get back to the default of `true`.
159 current_match
= match_stack
.pop().unwrap_or(true);
163 (true, EntryKind
::Symlink(link
)) => {
164 callback(entry
.path());
165 extractor
.extract_symlink(&file_name
, metadata
, link
.as_ref())
167 (true, EntryKind
::Hardlink(link
)) => {
168 callback(entry
.path());
169 extractor
.extract_hardlink(&file_name
, link
.as_os_str())
171 (true, EntryKind
::Device(dev
)) => {
172 if extractor
.contains_flags(Flags
::WITH_DEVICE_NODES
) {
173 callback(entry
.path());
174 extractor
.extract_device(&file_name
, metadata
, dev
)
179 (true, EntryKind
::Fifo
) => {
180 if extractor
.contains_flags(Flags
::WITH_FIFOS
) {
181 callback(entry
.path());
182 extractor
.extract_special(&file_name
, metadata
, 0)
187 (true, EntryKind
::Socket
) => {
188 if extractor
.contains_flags(Flags
::WITH_SOCKETS
) {
189 callback(entry
.path());
190 extractor
.extract_special(&file_name
, metadata
, 0)
195 (true, EntryKind
::File { size, .. }
) => extractor
.extract_file(
199 &mut decoder
.contents().ok_or_else(|| {
200 format_err
!("found regular file entry without contents in archive")
204 (false, _
) => Ok(()), // skip this
206 .map_err(|err
| format_err
!("error at entry {:?}: {}", file_name_os
, err
))?
;
209 if !extractor
.dir_stack
.is_empty() {
210 bail
!("unexpected eof while decoding pxar archive");
216 /// Common state for file extraction.
217 pub struct Extractor
{
218 feature_flags
: Flags
,
219 allow_existing_dirs
: bool
,
221 dir_stack
: PxarDirStack
,
223 /// For better error output we need to track the current path in the Extractor state.
224 current_path
: Arc
<Mutex
<OsString
>>,
226 /// Error callback. Includes `current_path` in the reformatted error, should return `Ok` to
227 /// continue extracting or the passed error as `Err` to bail out.
228 on_error
: ErrorHandler
,
232 /// Create a new extractor state for a target directory.
236 allow_existing_dirs
: bool
,
238 feature_flags
: Flags
,
241 dir_stack
: PxarDirStack
::new(root_dir
, metadata
),
245 current_path
: Arc
::new(Mutex
::new(OsString
::new())),
246 on_error
: Box
::new(Err
),
250 /// We call this on errors. The error will be reformatted to include `current_path`. The
251 /// callback should decide whether this error was fatal (simply return it) to bail out early,
252 /// or log/remember/accumulate errors somewhere and return `Ok(())` in its place to continue
254 pub fn on_error(&mut self, mut on_error
: Box
<dyn FnMut(Error
) -> Result
<(), Error
> + Send
>) {
255 let path
= Arc
::clone(&self.current_path
);
256 self.on_error
= Box
::new(move |err
: Error
| -> Result
<(), Error
> {
257 on_error(format_err
!("error at {:?}: {}", path
.lock().unwrap(), err
))
261 pub fn set_path(&mut self, path
: OsString
) {
262 *self.current_path
.lock().unwrap() = path
;
265 pub fn clone_path(&self) -> OsString
{
266 self.current_path
.lock().unwrap().clone()
269 /// When encountering a directory during extraction, this is used to keep track of it. If
270 /// `create` is true it is immediately created and its metadata will be updated once we leave
271 /// it. If `create` is false it will only be created if it is going to have any actual content.
272 pub fn enter_directory(
277 ) -> Result
<(), Error
> {
278 self.dir_stack
.push(file_name
, metadata
)?
;
281 self.dir_stack
.create_last_dir(self.allow_existing_dirs
)?
;
287 /// When done with a directory we can apply its metadata if it has been created.
288 pub fn leave_directory(&mut self) -> Result
<(), Error
> {
289 let path_info
= self.dir_stack
.path().to_owned();
294 .map_err(|err
| format_err
!("unexpected end of directory entry: {}", err
))?
295 .ok_or_else(|| format_err
!("broken pxar archive (directory stack underrun)"))?
;
297 if let Some(fd
) = dir
.try_as_borrowed_fd() {
305 .map_err(|err
| format_err
!("failed to apply directory metadata: {}", err
))?
;
311 fn contains_flags(&self, flag
: Flags
) -> bool
{
312 self.feature_flags
.contains(flag
)
315 fn parent_fd(&mut self) -> Result
<RawFd
, Error
> {
317 .last_dir_fd(self.allow_existing_dirs
)
318 .map(|d
| d
.as_raw_fd())
319 .map_err(|err
| format_err
!("failed to get parent directory file descriptor: {}", err
))
322 pub fn extract_symlink(
327 ) -> Result
<(), Error
> {
328 let parent
= self.parent_fd()?
;
329 nix
::unistd
::symlinkat(link
, Some(parent
), file_name
)?
;
335 self.dir_stack
.path(),
340 pub fn extract_hardlink(&mut self, file_name
: &CStr
, link
: &OsStr
) -> Result
<(), Error
> {
341 crate::pxar
::tools
::assert_relative_path(link
)?
;
343 let parent
= self.parent_fd()?
;
344 let root
= self.dir_stack
.root_dir_fd()?
;
345 let target
= CString
::new(link
.as_bytes())?
;
347 Some(root
.as_raw_fd()),
351 nix
::unistd
::LinkatFlags
::NoSymlinkFollow
,
357 pub fn extract_device(
362 ) -> Result
<(), Error
> {
363 self.extract_special(file_name
, metadata
, device
.to_dev_t())
366 pub fn extract_special(
371 ) -> Result
<(), Error
> {
372 let mode
= metadata
.stat
.mode
;
373 let mode
= u32::try_from(mode
).map_err(|_
| {
375 "device node's mode contains illegal bits: 0x{:x} (0o{:o})",
380 let parent
= self.parent_fd()?
;
381 unsafe { c_result!(libc::mknodat(parent, file_name.as_ptr(), mode, device)) }
382 .map_err(|err
| format_err
!("failed to create device node: {}", err
))?
;
389 self.dir_stack
.path(),
399 contents
: &mut dyn io
::Read
,
401 ) -> Result
<(), Error
> {
402 let parent
= self.parent_fd()?
;
403 let mut oflags
= OFlag
::O_CREAT
| OFlag
::O_WRONLY
| OFlag
::O_CLOEXEC
;
405 oflags
|= OFlag
::O_TRUNC
;
407 oflags
|= OFlag
::O_EXCL
;
409 let mut file
= unsafe {
410 std
::fs
::File
::from_raw_fd(
411 nix
::fcntl
::openat(parent
, file_name
, oflags
, Mode
::from_bits(0o600).unwrap())
412 .map_err(|err
| format_err
!("failed to create file {:?}: {}", file_name
, err
))?
,
416 metadata
::apply_initial_flags(
422 .map_err(|err
| format_err
!("failed to apply initial flags: {}", err
))?
;
424 let result
= sparse_copy(&mut *contents
, &mut file
)
425 .map_err(|err
| format_err
!("failed to copy file contents: {}", err
))?
;
427 if size
!= result
.written
{
429 "extracted {} bytes of a file of {} bytes",
435 if result
.seeked_last
{
436 while match nix
::unistd
::ftruncate(file
.as_raw_fd(), size
as i64) {
438 Err(errno
) if errno
== nix
::errno
::Errno
::EINTR
=> true,
439 Err(err
) => bail
!("error setting file size: {}", err
),
447 self.dir_stack
.path(),
452 pub async
fn async_extract_file
<T
: tokio
::io
::AsyncRead
+ Unpin
>(
459 ) -> Result
<(), Error
> {
460 let parent
= self.parent_fd()?
;
461 let mut oflags
= OFlag
::O_CREAT
| OFlag
::O_WRONLY
| OFlag
::O_CLOEXEC
;
463 oflags
|= OFlag
::O_TRUNC
;
465 oflags
|= OFlag
::O_EXCL
;
467 let mut file
= tokio
::fs
::File
::from_std(unsafe {
468 std
::fs
::File
::from_raw_fd(
469 nix
::fcntl
::openat(parent
, file_name
, oflags
, Mode
::from_bits(0o600).unwrap())
470 .map_err(|err
| format_err
!("failed to create file {:?}: {}", file_name
, err
))?
,
474 metadata
::apply_initial_flags(
480 .map_err(|err
| format_err
!("failed to apply initial flags: {}", err
))?
;
482 let result
= sparse_copy_async(&mut *contents
, &mut file
)
484 .map_err(|err
| format_err
!("failed to copy file contents: {}", err
))?
;
486 if size
!= result
.written
{
488 "extracted {} bytes of a file of {} bytes",
494 if result
.seeked_last
{
495 while match nix
::unistd
::ftruncate(file
.as_raw_fd(), size
as i64) {
497 Err(errno
) if errno
== nix
::errno
::Errno
::EINTR
=> true,
498 Err(err
) => bail
!("error setting file size: {}", err
),
506 self.dir_stack
.path(),
512 fn add_metadata_to_header(header
: &mut tar
::Header
, metadata
: &Metadata
) {
513 header
.set_mode(metadata
.stat
.mode
as u32);
514 header
.set_mtime(metadata
.stat
.mtime
.secs
as u64);
515 header
.set_uid(metadata
.stat
.uid
as u64);
516 header
.set_gid(metadata
.stat
.gid
as u64);
519 async
fn tar_add_file
<'a
, W
, T
>(
520 tar
: &mut proxmox_compression
::tar
::Builder
<W
>,
521 contents
: Option
<Contents
<'a
, T
>>,
525 ) -> Result
<(), Error
>
527 T
: pxar
::decoder
::SeqRead
+ Unpin
+ Send
+ Sync
+ '
static,
528 W
: tokio
::io
::AsyncWrite
+ Unpin
+ Send
+ '
static,
530 let mut header
= tar
::Header
::new_gnu();
531 header
.set_entry_type(tar
::EntryType
::Regular
);
532 header
.set_size(size
);
533 add_metadata_to_header(&mut header
, metadata
);
536 Some(content
) => tar
.add_entry(&mut header
, path
, content
).await
,
537 None
=> tar
.add_entry(&mut header
, path
, tokio
::io
::empty()).await
,
539 .map_err(|err
| format_err
!("could not send file entry: {}", err
))?
;
543 /// Creates a tar file from `path` and writes it into `output`
544 pub async
fn create_tar
<T
, W
, P
>(output
: W
, accessor
: Accessor
<T
>, path
: P
) -> Result
<(), Error
>
546 T
: Clone
+ pxar
::accessor
::ReadAt
+ Unpin
+ Send
+ Sync
+ '
static,
547 W
: tokio
::io
::AsyncWrite
+ Unpin
+ Send
+ '
static,
550 let root
= accessor
.open_root().await?
;
554 .ok_or_else(|| format_err
!("error opening '{:?}'", path
.as_ref()))?
;
556 let mut components
= file
.entry().path().components();
557 components
.next_back(); // discard last
558 let prefix
= components
.as_path();
560 let mut tarencoder
= proxmox_compression
::tar
::Builder
::new(output
);
561 let mut hardlinks
: HashMap
<PathBuf
, PathBuf
> = HashMap
::new();
563 if let Ok(dir
) = file
.enter_directory().await
{
564 let entry
= dir
.lookup_self().await?
;
565 let path
= entry
.path().strip_prefix(prefix
)?
;
567 if path
!= Path
::new("/") {
568 let metadata
= entry
.metadata();
569 let mut header
= tar
::Header
::new_gnu();
570 header
.set_entry_type(tar
::EntryType
::Directory
);
571 add_metadata_to_header(&mut header
, metadata
);
575 .add_entry(&mut header
, path
, tokio
::io
::empty())
577 .map_err(|err
| format_err
!("could not send dir entry: {}", err
))?
;
580 let mut decoder
= dir
.decode_full().await?
;
581 decoder
.enable_goodbye_entries(false);
582 while let Some(entry
) = decoder
.next().await
{
583 let entry
= entry
.map_err(|err
| format_err
!("cannot decode entry: {}", err
))?
;
585 let metadata
= entry
.metadata();
586 let path
= entry
.path().strip_prefix(prefix
)?
;
589 EntryKind
::File { .. }
=> {
590 let size
= decoder
.content_size().unwrap_or(0);
591 tar_add_file(&mut tarencoder
, decoder
.contents(), size
, metadata
, path
).await?
593 EntryKind
::Hardlink(link
) => {
594 if !link
.data
.is_empty() {
598 .ok_or_else(|| format_err
!("error looking up '{:?}'", path
))?
;
599 let realfile
= accessor
.follow_hardlink(&entry
).await?
;
600 let metadata
= realfile
.entry().metadata();
601 let realpath
= Path
::new(link
);
603 log
::debug
!("adding '{}' to tar", path
.display());
605 let stripped_path
= match realpath
.strip_prefix(prefix
) {
608 // outside of our tar archive, add the first occurrence to the tar
609 if let Some(path
) = hardlinks
.get(realpath
) {
612 let size
= decoder
.content_size().unwrap_or(0);
621 hardlinks
.insert(realpath
.to_owned(), path
.to_owned());
626 let mut header
= tar
::Header
::new_gnu();
627 header
.set_entry_type(tar
::EntryType
::Link
);
628 add_metadata_to_header(&mut header
, metadata
);
631 .add_link(&mut header
, path
, stripped_path
)
633 .map_err(|err
| format_err
!("could not send hardlink entry: {}", err
))?
;
636 EntryKind
::Symlink(link
) if !link
.data
.is_empty() => {
637 log
::debug
!("adding '{}' to tar", path
.display());
638 let realpath
= Path
::new(link
);
639 let mut header
= tar
::Header
::new_gnu();
640 header
.set_entry_type(tar
::EntryType
::Symlink
);
641 add_metadata_to_header(&mut header
, metadata
);
644 .add_link(&mut header
, path
, realpath
)
646 .map_err(|err
| format_err
!("could not send symlink entry: {}", err
))?
;
649 log
::debug
!("adding '{}' to tar", path
.display());
650 let mut header
= tar
::Header
::new_gnu();
651 header
.set_entry_type(tar
::EntryType
::Fifo
);
652 add_metadata_to_header(&mut header
, metadata
);
654 header
.set_device_major(0)?
;
655 header
.set_device_minor(0)?
;
658 .add_entry(&mut header
, path
, tokio
::io
::empty())
660 .map_err(|err
| format_err
!("could not send fifo entry: {}", err
))?
;
662 EntryKind
::Directory
=> {
663 log
::debug
!("adding '{}' to tar", path
.display());
664 // we cannot add the root path itself
665 if path
!= Path
::new("/") {
666 let mut header
= tar
::Header
::new_gnu();
667 header
.set_entry_type(tar
::EntryType
::Directory
);
668 add_metadata_to_header(&mut header
, metadata
);
672 .add_entry(&mut header
, path
, tokio
::io
::empty())
674 .map_err(|err
| format_err
!("could not send dir entry: {}", err
))?
;
677 EntryKind
::Device(device
) => {
678 log
::debug
!("adding '{}' to tar", path
.display());
679 let entry_type
= if metadata
.stat
.is_chardev() {
682 tar
::EntryType
::Block
684 let mut header
= tar
::Header
::new_gnu();
685 header
.set_entry_type(entry_type
);
686 header
.set_device_major(device
.major
as u32)?
;
687 header
.set_device_minor(device
.minor
as u32)?
;
688 add_metadata_to_header(&mut header
, metadata
);
691 .add_entry(&mut header
, path
, tokio
::io
::empty())
693 .map_err(|err
| format_err
!("could not send device entry: {}", err
))?
;
695 _
=> {}
// ignore all else
700 tarencoder
.finish().await
.map_err(|err
| {
701 log
::error
!("error during finishing of zip: {}", err
);
707 pub async
fn create_zip
<T
, W
, P
>(output
: W
, accessor
: Accessor
<T
>, path
: P
) -> Result
<(), Error
>
709 T
: Clone
+ pxar
::accessor
::ReadAt
+ Unpin
+ Send
+ Sync
+ '
static,
710 W
: tokio
::io
::AsyncWrite
+ Unpin
+ Send
+ '
static,
713 let root
= accessor
.open_root().await?
;
717 .ok_or_else(|| format_err
!("error opening '{:?}'", path
.as_ref()))?
;
720 let mut components
= file
.entry().path().components();
721 components
.next_back(); // discar last
722 components
.as_path().to_owned()
725 let mut zip
= ZipEncoder
::new(output
);
727 if let Ok(dir
) = file
.enter_directory().await
{
728 let entry
= dir
.lookup_self().await?
;
729 let path
= entry
.path().strip_prefix(&prefix
)?
;
730 if path
!= Path
::new("/") {
731 let metadata
= entry
.metadata();
732 let entry
= ZipEntry
::new(
734 metadata
.stat
.mtime
.secs
,
735 metadata
.stat
.mode
as u16,
738 zip
.add_entry
::<FileContents
<T
>>(entry
, None
).await?
;
741 let mut decoder
= dir
.decode_full().await?
;
742 decoder
.enable_goodbye_entries(false);
743 while let Some(entry
) = decoder
.next().await
{
745 let metadata
= entry
.metadata();
746 let path
= entry
.path().strip_prefix(&prefix
)?
;
749 EntryKind
::File { .. }
=> {
750 log
::debug
!("adding '{}' to zip", path
.display());
751 let entry
= ZipEntry
::new(
753 metadata
.stat
.mtime
.secs
,
754 metadata
.stat
.mode
as u16,
757 zip
.add_entry(entry
, decoder
.contents())
759 .map_err(|err
| format_err
!("could not send file entry: {}", err
))?
;
761 EntryKind
::Hardlink(_
) => {
765 .ok_or_else(|| format_err
!("error looking up '{:?}'", path
))?
;
766 let realfile
= accessor
.follow_hardlink(&entry
).await?
;
767 let metadata
= realfile
.entry().metadata();
768 log
::debug
!("adding '{}' to zip", path
.display());
769 let entry
= ZipEntry
::new(
771 metadata
.stat
.mtime
.secs
,
772 metadata
.stat
.mode
as u16,
775 zip
.add_entry(entry
, decoder
.contents())
777 .map_err(|err
| format_err
!("could not send file entry: {}", err
))?
;
779 EntryKind
::Directory
=> {
780 log
::debug
!("adding '{}' to zip", path
.display());
781 let entry
= ZipEntry
::new(
783 metadata
.stat
.mtime
.secs
,
784 metadata
.stat
.mode
as u16,
787 zip
.add_entry
::<FileContents
<T
>>(entry
, None
).await?
;
789 _
=> {}
// ignore all else
794 zip
.finish().await
.map_err(|err
| {
795 eprintln
!("error during finishing of zip: {}", err
);
800 fn get_extractor
<DEST
>(destination
: DEST
, metadata
: Metadata
) -> Result
<Extractor
, Error
>
807 Some(CreateOptions
::new().perm(Mode
::from_bits_truncate(0o700))),
811 "error creating directory {:?}: {}",
812 destination
.as_ref(),
818 destination
.as_ref(),
819 OFlag
::O_DIRECTORY
| OFlag
::O_CLOEXEC
,
824 "unable to open target directory {:?}: {}",
825 destination
.as_ref(),
830 Ok(Extractor
::new(dir
, metadata
, false, false, Flags
::DEFAULT
))
833 pub async
fn extract_sub_dir
<T
, DEST
, PATH
>(
835 decoder
: Accessor
<T
>,
837 ) -> Result
<(), Error
>
839 T
: Clone
+ pxar
::accessor
::ReadAt
+ Unpin
+ Send
+ Sync
+ '
static,
843 let root
= decoder
.open_root().await?
;
845 let mut extractor
= get_extractor(
847 root
.lookup_self().await?
.entry().metadata().clone(),
853 .ok_or_else(|| format_err
!("error opening '{:?}'", path
.as_ref()))?
;
855 recurse_files_extractor(&mut extractor
, file
).await
858 pub async
fn extract_sub_dir_seq
<S
, DEST
>(
860 mut decoder
: Decoder
<S
>,
861 ) -> Result
<(), Error
>
863 S
: pxar
::decoder
::SeqRead
+ Unpin
+ Send
+ '
static,
866 decoder
.enable_goodbye_entries(true);
867 let root
= match decoder
.next().await
{
868 Some(Ok(root
)) => root
,
869 Some(Err(err
)) => bail
!("error getting root entry from pxar: {}", err
),
870 None
=> bail
!("cannot extract empty archive"),
873 let mut extractor
= get_extractor(destination
, root
.metadata().clone())?
;
875 if let Err(err
) = seq_files_extractor(&mut extractor
, decoder
).await
{
876 log
::error
!("error extracting pxar archive: {}", err
);
883 extractor
: &mut Extractor
,
886 ) -> Result
<(), Error
> {
887 let metadata
= entry
.metadata();
889 EntryKind
::Symlink(link
) => {
890 extractor
.extract_symlink(file_name
, metadata
, link
.as_ref())?
;
892 EntryKind
::Hardlink(link
) => {
893 extractor
.extract_hardlink(file_name
, link
.as_os_str())?
;
895 EntryKind
::Device(dev
) => {
896 if extractor
.contains_flags(Flags
::WITH_DEVICE_NODES
) {
897 extractor
.extract_device(file_name
, metadata
, dev
)?
;
901 if extractor
.contains_flags(Flags
::WITH_FIFOS
) {
902 extractor
.extract_special(file_name
, metadata
, 0)?
;
905 EntryKind
::Socket
=> {
906 if extractor
.contains_flags(Flags
::WITH_SOCKETS
) {
907 extractor
.extract_special(file_name
, metadata
, 0)?
;
910 _
=> bail
!("extract_special used with unsupported entry kind"),
915 fn get_filename(entry
: &Entry
) -> Result
<(OsString
, CString
), Error
> {
916 let file_name_os
= entry
.file_name().to_owned();
918 // safety check: a file entry in an archive must never contain slashes:
919 if file_name_os
.as_bytes().contains(&b'
/'
) {
920 bail
!("archive file entry contains slashes, which is invalid and a security concern");
923 let file_name
= CString
::new(file_name_os
.as_bytes())
924 .map_err(|_
| format_err
!("encountered file name with null-bytes"))?
;
926 Ok((file_name_os
, file_name
))
929 async
fn recurse_files_extractor
<T
>(
930 extractor
: &mut Extractor
,
932 ) -> Result
<(), Error
>
934 T
: Clone
+ pxar
::accessor
::ReadAt
+ Unpin
+ Send
+ Sync
+ '
static,
936 let entry
= file
.entry();
937 let metadata
= entry
.metadata();
938 let (file_name_os
, file_name
) = get_filename(entry
)?
;
940 log
::debug
!("extracting: {}", file
.path().display());
943 EntryKind
::Directory
=> {
945 .enter_directory(file_name_os
.to_owned(), metadata
.clone(), true)
946 .map_err(|err
| format_err
!("error at entry {:?}: {}", file_name_os
, err
))?
;
948 let dir
= file
.enter_directory().await?
;
949 let mut seq_decoder
= dir
.decode_full().await?
;
950 seq_decoder
.enable_goodbye_entries(true);
951 seq_files_extractor(extractor
, seq_decoder
).await?
;
952 extractor
.leave_directory()?
;
954 EntryKind
::File { size, .. }
=> {
960 &mut file
.contents().await
.map_err(|_
| {
961 format_err
!("found regular file entry without contents in archive")
967 EntryKind
::GoodbyeTable
=> {}
// ignore
968 _
=> extract_special(extractor
, entry
, &file_name
)?
,
973 async
fn seq_files_extractor
<T
>(
974 extractor
: &mut Extractor
,
975 mut decoder
: pxar
::decoder
::aio
::Decoder
<T
>,
976 ) -> Result
<(), Error
>
978 T
: pxar
::decoder
::SeqRead
,
980 let mut dir_level
= 0;
982 let entry
= match decoder
.next().await
{
983 Some(entry
) => entry?
,
984 None
=> return Ok(()),
987 let metadata
= entry
.metadata();
988 let (file_name_os
, file_name
) = get_filename(&entry
)?
;
990 if !matches
!(entry
.kind(), EntryKind
::GoodbyeTable
) {
991 log
::debug
!("extracting: {}", entry
.path().display());
994 if let Err(err
) = async
{
996 EntryKind
::Directory
=> {
999 .enter_directory(file_name_os
.to_owned(), metadata
.clone(), true)
1000 .map_err(|err
| format_err
!("error at entry {:?}: {}", file_name_os
, err
))?
;
1002 EntryKind
::File { size, .. }
=> {
1004 .async_extract_file(
1008 &mut decoder
.contents().ok_or_else(|| {
1009 format_err
!("found regular file entry without contents in archive")
1011 extractor
.overwrite
,
1015 EntryKind
::GoodbyeTable
=> {
1017 extractor
.leave_directory()?
;
1019 _
=> extract_special(extractor
, &entry
, &file_name
)?
,
1021 Ok(()) as Result
<(), Error
>
1025 let display
= entry
.path().display().to_string();
1027 "error extracting {}: {}",
1028 if matches
!(entry
.kind(), EntryKind
::GoodbyeTable
) {
1038 // we've encountered one Goodbye more then Directory, meaning we've left the dir we
1039 // started in - exit early, otherwise the extractor might panic