1 //! Code for extraction of pxar contents onto the file system.
3 use std
::collections
::HashMap
;
4 use std
::convert
::TryFrom
;
5 use std
::ffi
::{CStr, CString, OsStr, OsString}
;
7 use std
::os
::unix
::ffi
::OsStrExt
;
8 use std
::os
::unix
::io
::{AsRawFd, FromRawFd, RawFd}
;
9 use std
::path
::{Path, PathBuf}
;
10 use std
::sync
::{Arc, Mutex}
;
12 use anyhow
::{bail, format_err, Error}
;
14 use nix
::fcntl
::OFlag
;
15 use nix
::sys
::stat
::Mode
;
17 use pathpatterns
::{MatchEntry, MatchList, MatchType}
;
18 use pxar
::accessor
::aio
::{Accessor, FileContents, FileEntry}
;
19 use pxar
::decoder
::{aio::Decoder, Contents}
;
20 use pxar
::format
::Device
;
21 use pxar
::{Entry, EntryKind, Metadata}
;
23 use proxmox_io
::{sparse_copy, sparse_copy_async}
;
24 use proxmox_sys
::c_result
;
25 use proxmox_sys
::fs
::{create_path, CreateOptions}
;
27 use proxmox_compression
::zip
::{ZipEncoder, ZipEntry}
;
29 use crate::pxar
::dir_stack
::PxarDirStack
;
30 use crate::pxar
::metadata
;
31 use crate::pxar
::Flags
;
33 pub struct PxarExtractOptions
<'a
> {
34 pub match_list
: &'a
[MatchEntry
],
35 pub extract_match_default
: bool
,
36 pub allow_existing_dirs
: bool
,
38 pub on_error
: Option
<ErrorHandler
>,
41 pub type ErrorHandler
= Box
<dyn FnMut(Error
) -> Result
<(), Error
> + Send
>;
43 pub fn extract_archive
<T
, F
>(
44 mut decoder
: pxar
::decoder
::Decoder
<T
>,
48 options
: PxarExtractOptions
,
49 ) -> Result
<(), Error
>
51 T
: pxar
::decoder
::SeqRead
,
54 // we use this to keep track of our directory-traversal
55 decoder
.enable_goodbye_entries(true);
59 .ok_or_else(|| format_err
!("found empty pxar archive"))?
60 .map_err(|err
| format_err
!("error reading pxar archive: {}", err
))?
;
63 bail
!("pxar archive does not start with a directory entry!");
69 Some(CreateOptions
::new().perm(Mode
::from_bits_truncate(0o700))),
71 .map_err(|err
| format_err
!("error creating directory {:?}: {}", destination
, err
))?
;
75 OFlag
::O_DIRECTORY
| OFlag
::O_CLOEXEC
,
78 .map_err(|err
| format_err
!("unable to open target directory {:?}: {}", destination
, err
,))?
;
80 let mut extractor
= Extractor
::new(
82 root
.metadata().clone(),
83 options
.allow_existing_dirs
,
88 if let Some(on_error
) = options
.on_error
{
89 extractor
.on_error(on_error
);
92 let mut match_stack
= Vec
::new();
93 let mut err_path_stack
= vec
![OsString
::from("/")];
94 let mut current_match
= options
.extract_match_default
;
95 while let Some(entry
) = decoder
.next() {
96 let entry
= entry
.map_err(|err
| format_err
!("error reading pxar archive: {}", err
))?
;
98 let file_name_os
= entry
.file_name();
100 // safety check: a file entry in an archive must never contain slashes:
101 if file_name_os
.as_bytes().contains(&b'
/'
) {
102 bail
!("archive file entry contains slashes, which is invalid and a security concern");
105 let file_name
= CString
::new(file_name_os
.as_bytes())
106 .map_err(|_
| format_err
!("encountered file name with null-bytes"))?
;
108 let metadata
= entry
.metadata();
110 extractor
.set_path(entry
.path().as_os_str().to_owned());
112 let match_result
= options
.match_list
.matches(
113 entry
.path().as_os_str().as_bytes(),
114 Some(metadata
.file_type() as u32),
117 let did_match
= match match_result
{
118 Some(MatchType
::Include
) => true,
119 Some(MatchType
::Exclude
) => false,
120 None
=> current_match
,
122 match (did_match
, entry
.kind()) {
123 (_
, EntryKind
::Directory
) => {
124 callback(entry
.path());
126 let create
= current_match
&& match_result
!= Some(MatchType
::Exclude
);
128 .enter_directory(file_name_os
.to_owned(), metadata
.clone(), create
)
129 .map_err(|err
| format_err
!("error at entry {:?}: {}", file_name_os
, err
))?
;
131 // We're starting a new directory, push our old matching state and replace it with
133 match_stack
.push(current_match
);
134 current_match
= did_match
;
136 // When we hit the goodbye table we'll try to apply metadata to the directory, but
137 // the Goodbye entry will not contain the path, so push it to our path stack for
139 err_path_stack
.push(extractor
.clone_path());
143 (_
, EntryKind
::GoodbyeTable
) => {
146 extractor
.set_path(err_path_stack
.pop().ok_or_else(|| {
148 "error at entry {:?}: unexpected end of directory",
155 .map_err(|err
| format_err
!("error at entry {:?}: {}", file_name_os
, err
))?
;
157 // We left a directory, also get back our previous matching state. This is in sync
158 // with `dir_stack` so this should never be empty except for the final goodbye
159 // table, in which case we get back to the default of `true`.
160 current_match
= match_stack
.pop().unwrap_or(true);
164 (true, EntryKind
::Symlink(link
)) => {
165 callback(entry
.path());
166 extractor
.extract_symlink(&file_name
, metadata
, link
.as_ref())
168 (true, EntryKind
::Hardlink(link
)) => {
169 callback(entry
.path());
170 extractor
.extract_hardlink(&file_name
, link
.as_os_str())
172 (true, EntryKind
::Device(dev
)) => {
173 if extractor
.contains_flags(Flags
::WITH_DEVICE_NODES
) {
174 callback(entry
.path());
175 extractor
.extract_device(&file_name
, metadata
, dev
)
180 (true, EntryKind
::Fifo
) => {
181 if extractor
.contains_flags(Flags
::WITH_FIFOS
) {
182 callback(entry
.path());
183 extractor
.extract_special(&file_name
, metadata
, 0)
188 (true, EntryKind
::Socket
) => {
189 if extractor
.contains_flags(Flags
::WITH_SOCKETS
) {
190 callback(entry
.path());
191 extractor
.extract_special(&file_name
, metadata
, 0)
196 (true, EntryKind
::File { size, .. }
) => extractor
.extract_file(
200 &mut decoder
.contents().ok_or_else(|| {
201 format_err
!("found regular file entry without contents in archive")
205 (false, _
) => Ok(()), // skip this
207 .map_err(|err
| format_err
!("error at entry {:?}: {}", file_name_os
, err
))?
;
210 if !extractor
.dir_stack
.is_empty() {
211 bail
!("unexpected eof while decoding pxar archive");
217 /// Common state for file extraction.
218 pub struct Extractor
{
219 feature_flags
: Flags
,
220 allow_existing_dirs
: bool
,
222 dir_stack
: PxarDirStack
,
224 /// For better error output we need to track the current path in the Extractor state.
225 current_path
: Arc
<Mutex
<OsString
>>,
227 /// Error callback. Includes `current_path` in the reformatted error, should return `Ok` to
228 /// continue extracting or the passed error as `Err` to bail out.
229 on_error
: ErrorHandler
,
233 /// Create a new extractor state for a target directory.
237 allow_existing_dirs
: bool
,
239 feature_flags
: Flags
,
242 dir_stack
: PxarDirStack
::new(root_dir
, metadata
),
246 current_path
: Arc
::new(Mutex
::new(OsString
::new())),
247 on_error
: Box
::new(Err
),
251 /// We call this on errors. The error will be reformatted to include `current_path`. The
252 /// callback should decide whether this error was fatal (simply return it) to bail out early,
253 /// or log/remember/accumulate errors somewhere and return `Ok(())` in its place to continue
255 pub fn on_error(&mut self, mut on_error
: Box
<dyn FnMut(Error
) -> Result
<(), Error
> + Send
>) {
256 let path
= Arc
::clone(&self.current_path
);
257 self.on_error
= Box
::new(move |err
: Error
| -> Result
<(), Error
> {
258 on_error(format_err
!("error at {:?}: {}", path
.lock().unwrap(), err
))
262 pub fn set_path(&mut self, path
: OsString
) {
263 *self.current_path
.lock().unwrap() = path
;
266 pub fn clone_path(&self) -> OsString
{
267 self.current_path
.lock().unwrap().clone()
270 /// When encountering a directory during extraction, this is used to keep track of it. If
271 /// `create` is true it is immediately created and its metadata will be updated once we leave
272 /// it. If `create` is false it will only be created if it is going to have any actual content.
273 pub fn enter_directory(
278 ) -> Result
<(), Error
> {
279 self.dir_stack
.push(file_name
, metadata
)?
;
282 self.dir_stack
.create_last_dir(self.allow_existing_dirs
)?
;
288 /// When done with a directory we can apply its metadata if it has been created.
289 pub fn leave_directory(&mut self) -> Result
<(), Error
> {
290 let path_info
= self.dir_stack
.path().to_owned();
295 .map_err(|err
| format_err
!("unexpected end of directory entry: {}", err
))?
296 .ok_or_else(|| format_err
!("broken pxar archive (directory stack underrun)"))?
;
298 if let Some(fd
) = dir
.try_as_borrowed_fd() {
306 .map_err(|err
| format_err
!("failed to apply directory metadata: {}", err
))?
;
312 fn contains_flags(&self, flag
: Flags
) -> bool
{
313 self.feature_flags
.contains(flag
)
316 fn parent_fd(&mut self) -> Result
<RawFd
, Error
> {
318 .last_dir_fd(self.allow_existing_dirs
)
319 .map(|d
| d
.as_raw_fd())
320 .map_err(|err
| format_err
!("failed to get parent directory file descriptor: {}", err
))
323 pub fn extract_symlink(
328 ) -> Result
<(), Error
> {
329 let parent
= self.parent_fd()?
;
330 nix
::unistd
::symlinkat(link
, Some(parent
), file_name
)?
;
336 self.dir_stack
.path(),
341 pub fn extract_hardlink(&mut self, file_name
: &CStr
, link
: &OsStr
) -> Result
<(), Error
> {
342 crate::pxar
::tools
::assert_relative_path(link
)?
;
344 let parent
= self.parent_fd()?
;
345 let root
= self.dir_stack
.root_dir_fd()?
;
346 let target
= CString
::new(link
.as_bytes())?
;
348 Some(root
.as_raw_fd()),
352 nix
::unistd
::LinkatFlags
::NoSymlinkFollow
,
358 pub fn extract_device(
363 ) -> Result
<(), Error
> {
364 self.extract_special(file_name
, metadata
, device
.to_dev_t())
367 pub fn extract_special(
372 ) -> Result
<(), Error
> {
373 let mode
= metadata
.stat
.mode
;
374 let mode
= u32::try_from(mode
).map_err(|_
| {
376 "device node's mode contains illegal bits: 0x{:x} (0o{:o})",
381 let parent
= self.parent_fd()?
;
382 unsafe { c_result!(libc::mknodat(parent, file_name.as_ptr(), mode, device)) }
383 .map_err(|err
| format_err
!("failed to create device node: {}", err
))?
;
390 self.dir_stack
.path(),
400 contents
: &mut dyn io
::Read
,
402 ) -> Result
<(), Error
> {
403 let parent
= self.parent_fd()?
;
404 let mut oflags
= OFlag
::O_CREAT
| OFlag
::O_WRONLY
| OFlag
::O_CLOEXEC
;
406 oflags
= oflags
| OFlag
::O_TRUNC
;
408 oflags
= oflags
| OFlag
::O_EXCL
;
410 let mut file
= unsafe {
411 std
::fs
::File
::from_raw_fd(
412 nix
::fcntl
::openat(parent
, file_name
, oflags
, Mode
::from_bits(0o600).unwrap())
413 .map_err(|err
| format_err
!("failed to create file {:?}: {}", file_name
, err
))?
,
417 metadata
::apply_initial_flags(
423 .map_err(|err
| format_err
!("failed to apply initial flags: {}", err
))?
;
425 let result
= sparse_copy(&mut *contents
, &mut file
)
426 .map_err(|err
| format_err
!("failed to copy file contents: {}", err
))?
;
428 if size
!= result
.written
{
430 "extracted {} bytes of a file of {} bytes",
436 if result
.seeked_last
{
437 while match nix
::unistd
::ftruncate(file
.as_raw_fd(), size
as i64) {
439 Err(errno
) if errno
== nix
::errno
::Errno
::EINTR
=> true,
440 Err(err
) => bail
!("error setting file size: {}", err
),
448 self.dir_stack
.path(),
453 pub async
fn async_extract_file
<T
: tokio
::io
::AsyncRead
+ Unpin
>(
460 ) -> Result
<(), Error
> {
461 let parent
= self.parent_fd()?
;
462 let mut oflags
= OFlag
::O_CREAT
| OFlag
::O_WRONLY
| OFlag
::O_CLOEXEC
;
464 oflags
= oflags
| OFlag
::O_TRUNC
;
466 oflags
= oflags
| OFlag
::O_EXCL
;
468 let mut file
= tokio
::fs
::File
::from_std(unsafe {
469 std
::fs
::File
::from_raw_fd(
470 nix
::fcntl
::openat(parent
, file_name
, oflags
, Mode
::from_bits(0o600).unwrap())
471 .map_err(|err
| format_err
!("failed to create file {:?}: {}", file_name
, err
))?
,
475 metadata
::apply_initial_flags(
481 .map_err(|err
| format_err
!("failed to apply initial flags: {}", err
))?
;
483 let result
= sparse_copy_async(&mut *contents
, &mut file
)
485 .map_err(|err
| format_err
!("failed to copy file contents: {}", err
))?
;
487 if size
!= result
.written
{
489 "extracted {} bytes of a file of {} bytes",
495 if result
.seeked_last
{
496 while match nix
::unistd
::ftruncate(file
.as_raw_fd(), size
as i64) {
498 Err(errno
) if errno
== nix
::errno
::Errno
::EINTR
=> true,
499 Err(err
) => bail
!("error setting file size: {}", err
),
507 self.dir_stack
.path(),
513 fn add_metadata_to_header(header
: &mut tar
::Header
, metadata
: &Metadata
) {
514 header
.set_mode(metadata
.stat
.mode
as u32);
515 header
.set_mtime(metadata
.stat
.mtime
.secs
as u64);
516 header
.set_uid(metadata
.stat
.uid
as u64);
517 header
.set_gid(metadata
.stat
.gid
as u64);
520 async
fn tar_add_file
<'a
, W
, T
>(
521 tar
: &mut proxmox_compression
::tar
::Builder
<W
>,
522 contents
: Option
<Contents
<'a
, T
>>,
526 ) -> Result
<(), Error
>
528 T
: pxar
::decoder
::SeqRead
+ Unpin
+ Send
+ Sync
+ '
static,
529 W
: tokio
::io
::AsyncWrite
+ Unpin
+ Send
+ '
static,
531 let mut header
= tar
::Header
::new_gnu();
532 header
.set_entry_type(tar
::EntryType
::Regular
);
533 header
.set_size(size
);
534 add_metadata_to_header(&mut header
, metadata
);
537 Some(content
) => tar
.add_entry(&mut header
, path
, content
).await
,
538 None
=> tar
.add_entry(&mut header
, path
, tokio
::io
::empty()).await
,
540 .map_err(|err
| format_err
!("could not send file entry: {}", err
))?
;
544 /// Creates a tar file from `path` and writes it into `output`
545 pub async
fn create_tar
<T
, W
, P
>(output
: W
, accessor
: Accessor
<T
>, path
: P
) -> Result
<(), Error
>
547 T
: Clone
+ pxar
::accessor
::ReadAt
+ Unpin
+ Send
+ Sync
+ '
static,
548 W
: tokio
::io
::AsyncWrite
+ Unpin
+ Send
+ '
static,
551 let root
= accessor
.open_root().await?
;
555 .ok_or_else(|| format_err
!("error opening '{:?}'", path
.as_ref()))?
;
557 let mut components
= file
.entry().path().components();
558 components
.next_back(); // discard last
559 let prefix
= components
.as_path();
561 let mut tarencoder
= proxmox_compression
::tar
::Builder
::new(output
);
562 let mut hardlinks
: HashMap
<PathBuf
, PathBuf
> = HashMap
::new();
564 if let Ok(dir
) = file
.enter_directory().await
{
565 let entry
= dir
.lookup_self().await?
;
566 let path
= entry
.path().strip_prefix(prefix
)?
;
568 if path
!= Path
::new("/") {
569 let metadata
= entry
.metadata();
570 let mut header
= tar
::Header
::new_gnu();
571 header
.set_entry_type(tar
::EntryType
::Directory
);
572 add_metadata_to_header(&mut header
, metadata
);
576 .add_entry(&mut header
, path
, tokio
::io
::empty())
578 .map_err(|err
| format_err
!("could not send dir entry: {}", err
))?
;
581 let mut decoder
= dir
.decode_full().await?
;
582 decoder
.enable_goodbye_entries(false);
583 while let Some(entry
) = decoder
.next().await
{
584 let entry
= entry
.map_err(|err
| format_err
!("cannot decode entry: {}", err
))?
;
586 let metadata
= entry
.metadata();
587 let path
= entry
.path().strip_prefix(prefix
)?
;
590 EntryKind
::File { .. }
=> {
591 let size
= decoder
.content_size().unwrap_or(0);
592 tar_add_file(&mut tarencoder
, decoder
.contents(), size
, metadata
, path
).await?
594 EntryKind
::Hardlink(link
) => {
595 if !link
.data
.is_empty() {
599 .ok_or_else(|| format_err
!("error looking up '{:?}'", path
))?
;
600 let realfile
= accessor
.follow_hardlink(&entry
).await?
;
601 let metadata
= realfile
.entry().metadata();
602 let realpath
= Path
::new(link
);
604 log
::debug
!("adding '{}' to tar", path
.display());
606 let stripped_path
= match realpath
.strip_prefix(prefix
) {
609 // outside of our tar archive, add the first occurrence to the tar
610 if let Some(path
) = hardlinks
.get(realpath
) {
613 let size
= decoder
.content_size().unwrap_or(0);
622 hardlinks
.insert(realpath
.to_owned(), path
.to_owned());
627 let mut header
= tar
::Header
::new_gnu();
628 header
.set_entry_type(tar
::EntryType
::Link
);
629 add_metadata_to_header(&mut header
, metadata
);
632 .add_link(&mut header
, path
, stripped_path
)
634 .map_err(|err
| format_err
!("could not send hardlink entry: {}", err
))?
;
637 EntryKind
::Symlink(link
) if !link
.data
.is_empty() => {
638 log
::debug
!("adding '{}' to tar", path
.display());
639 let realpath
= Path
::new(link
);
640 let mut header
= tar
::Header
::new_gnu();
641 header
.set_entry_type(tar
::EntryType
::Symlink
);
642 add_metadata_to_header(&mut header
, metadata
);
645 .add_link(&mut header
, path
, realpath
)
647 .map_err(|err
| format_err
!("could not send symlink entry: {}", err
))?
;
650 log
::debug
!("adding '{}' to tar", path
.display());
651 let mut header
= tar
::Header
::new_gnu();
652 header
.set_entry_type(tar
::EntryType
::Fifo
);
653 add_metadata_to_header(&mut header
, metadata
);
655 header
.set_device_major(0)?
;
656 header
.set_device_minor(0)?
;
659 .add_entry(&mut header
, path
, tokio
::io
::empty())
661 .map_err(|err
| format_err
!("could not send fifo entry: {}", err
))?
;
663 EntryKind
::Directory
=> {
664 log
::debug
!("adding '{}' to tar", path
.display());
665 // we cannot add the root path itself
666 if path
!= Path
::new("/") {
667 let mut header
= tar
::Header
::new_gnu();
668 header
.set_entry_type(tar
::EntryType
::Directory
);
669 add_metadata_to_header(&mut header
, metadata
);
673 .add_entry(&mut header
, path
, tokio
::io
::empty())
675 .map_err(|err
| format_err
!("could not send dir entry: {}", err
))?
;
678 EntryKind
::Device(device
) => {
679 log
::debug
!("adding '{}' to tar", path
.display());
680 let entry_type
= if metadata
.stat
.is_chardev() {
683 tar
::EntryType
::Block
685 let mut header
= tar
::Header
::new_gnu();
686 header
.set_entry_type(entry_type
);
687 header
.set_device_major(device
.major
as u32)?
;
688 header
.set_device_minor(device
.minor
as u32)?
;
689 add_metadata_to_header(&mut header
, metadata
);
692 .add_entry(&mut header
, path
, tokio
::io
::empty())
694 .map_err(|err
| format_err
!("could not send device entry: {}", err
))?
;
696 _
=> {}
// ignore all else
701 tarencoder
.finish().await
.map_err(|err
| {
702 log
::error
!("error during finishing of zip: {}", err
);
708 pub async
fn create_zip
<T
, W
, P
>(output
: W
, accessor
: Accessor
<T
>, path
: P
) -> Result
<(), Error
>
710 T
: Clone
+ pxar
::accessor
::ReadAt
+ Unpin
+ Send
+ Sync
+ '
static,
711 W
: tokio
::io
::AsyncWrite
+ Unpin
+ Send
+ '
static,
714 let root
= accessor
.open_root().await?
;
718 .ok_or_else(|| format_err
!("error opening '{:?}'", path
.as_ref()))?
;
721 let mut components
= file
.entry().path().components();
722 components
.next_back(); // discar last
723 components
.as_path().to_owned()
726 let mut zip
= ZipEncoder
::new(output
);
728 if let Ok(dir
) = file
.enter_directory().await
{
729 let entry
= dir
.lookup_self().await?
;
730 let path
= entry
.path().strip_prefix(&prefix
)?
;
731 if path
!= Path
::new("/") {
732 let metadata
= entry
.metadata();
733 let entry
= ZipEntry
::new(
735 metadata
.stat
.mtime
.secs
,
736 metadata
.stat
.mode
as u16,
739 zip
.add_entry
::<FileContents
<T
>>(entry
, None
).await?
;
742 let mut decoder
= dir
.decode_full().await?
;
743 decoder
.enable_goodbye_entries(false);
744 while let Some(entry
) = decoder
.next().await
{
746 let metadata
= entry
.metadata();
747 let path
= entry
.path().strip_prefix(&prefix
)?
;
750 EntryKind
::File { .. }
=> {
751 log
::debug
!("adding '{}' to zip", path
.display());
752 let entry
= ZipEntry
::new(
754 metadata
.stat
.mtime
.secs
,
755 metadata
.stat
.mode
as u16,
758 zip
.add_entry(entry
, decoder
.contents())
760 .map_err(|err
| format_err
!("could not send file entry: {}", err
))?
;
762 EntryKind
::Hardlink(_
) => {
766 .ok_or_else(|| format_err
!("error looking up '{:?}'", path
))?
;
767 let realfile
= accessor
.follow_hardlink(&entry
).await?
;
768 let metadata
= realfile
.entry().metadata();
769 log
::debug
!("adding '{}' to zip", path
.display());
770 let entry
= ZipEntry
::new(
772 metadata
.stat
.mtime
.secs
,
773 metadata
.stat
.mode
as u16,
776 zip
.add_entry(entry
, decoder
.contents())
778 .map_err(|err
| format_err
!("could not send file entry: {}", err
))?
;
780 EntryKind
::Directory
=> {
781 log
::debug
!("adding '{}' to zip", path
.display());
782 let entry
= ZipEntry
::new(
784 metadata
.stat
.mtime
.secs
,
785 metadata
.stat
.mode
as u16,
788 zip
.add_entry
::<FileContents
<T
>>(entry
, None
).await?
;
790 _
=> {}
// ignore all else
795 zip
.finish().await
.map_err(|err
| {
796 eprintln
!("error during finishing of zip: {}", err
);
801 fn get_extractor
<DEST
>(destination
: DEST
, metadata
: Metadata
) -> Result
<Extractor
, Error
>
808 Some(CreateOptions
::new().perm(Mode
::from_bits_truncate(0o700))),
812 "error creating directory {:?}: {}",
813 destination
.as_ref(),
819 destination
.as_ref(),
820 OFlag
::O_DIRECTORY
| OFlag
::O_CLOEXEC
,
825 "unable to open target directory {:?}: {}",
826 destination
.as_ref(),
831 Ok(Extractor
::new(dir
, metadata
, false, false, Flags
::DEFAULT
))
834 pub async
fn extract_sub_dir
<T
, DEST
, PATH
>(
836 decoder
: Accessor
<T
>,
838 ) -> Result
<(), Error
>
840 T
: Clone
+ pxar
::accessor
::ReadAt
+ Unpin
+ Send
+ Sync
+ '
static,
844 let root
= decoder
.open_root().await?
;
846 let mut extractor
= get_extractor(
848 root
.lookup_self().await?
.entry().metadata().clone(),
854 .ok_or_else(|| format_err
!("error opening '{:?}'", path
.as_ref()))?
;
856 recurse_files_extractor(&mut extractor
, file
).await
859 pub async
fn extract_sub_dir_seq
<S
, DEST
>(
861 mut decoder
: Decoder
<S
>,
862 ) -> Result
<(), Error
>
864 S
: pxar
::decoder
::SeqRead
+ Unpin
+ Send
+ '
static,
867 decoder
.enable_goodbye_entries(true);
868 let root
= match decoder
.next().await
{
869 Some(Ok(root
)) => root
,
870 Some(Err(err
)) => bail
!("error getting root entry from pxar: {}", err
),
871 None
=> bail
!("cannot extract empty archive"),
874 let mut extractor
= get_extractor(destination
, root
.metadata().clone())?
;
876 if let Err(err
) = seq_files_extractor(&mut extractor
, decoder
).await
{
877 log
::error
!("error extracting pxar archive: {}", err
);
884 extractor
: &mut Extractor
,
887 ) -> Result
<(), Error
> {
888 let metadata
= entry
.metadata();
890 EntryKind
::Symlink(link
) => {
891 extractor
.extract_symlink(file_name
, metadata
, link
.as_ref())?
;
893 EntryKind
::Hardlink(link
) => {
894 extractor
.extract_hardlink(file_name
, link
.as_os_str())?
;
896 EntryKind
::Device(dev
) => {
897 if extractor
.contains_flags(Flags
::WITH_DEVICE_NODES
) {
898 extractor
.extract_device(file_name
, metadata
, dev
)?
;
902 if extractor
.contains_flags(Flags
::WITH_FIFOS
) {
903 extractor
.extract_special(file_name
, metadata
, 0)?
;
906 EntryKind
::Socket
=> {
907 if extractor
.contains_flags(Flags
::WITH_SOCKETS
) {
908 extractor
.extract_special(file_name
, metadata
, 0)?
;
911 _
=> bail
!("extract_special used with unsupported entry kind"),
916 fn get_filename(entry
: &Entry
) -> Result
<(OsString
, CString
), Error
> {
917 let file_name_os
= entry
.file_name().to_owned();
919 // safety check: a file entry in an archive must never contain slashes:
920 if file_name_os
.as_bytes().contains(&b'
/'
) {
921 bail
!("archive file entry contains slashes, which is invalid and a security concern");
924 let file_name
= CString
::new(file_name_os
.as_bytes())
925 .map_err(|_
| format_err
!("encountered file name with null-bytes"))?
;
927 Ok((file_name_os
, file_name
))
930 async
fn recurse_files_extractor
<T
>(
931 extractor
: &mut Extractor
,
933 ) -> Result
<(), Error
>
935 T
: Clone
+ pxar
::accessor
::ReadAt
+ Unpin
+ Send
+ Sync
+ '
static,
937 let entry
= file
.entry();
938 let metadata
= entry
.metadata();
939 let (file_name_os
, file_name
) = get_filename(entry
)?
;
941 log
::debug
!("extracting: {}", file
.path().display());
944 EntryKind
::Directory
=> {
946 .enter_directory(file_name_os
.to_owned(), metadata
.clone(), true)
947 .map_err(|err
| format_err
!("error at entry {:?}: {}", file_name_os
, err
))?
;
949 let dir
= file
.enter_directory().await?
;
950 let mut seq_decoder
= dir
.decode_full().await?
;
951 seq_decoder
.enable_goodbye_entries(true);
952 seq_files_extractor(extractor
, seq_decoder
).await?
;
953 extractor
.leave_directory()?
;
955 EntryKind
::File { size, .. }
=> {
961 &mut file
.contents().await
.map_err(|_
| {
962 format_err
!("found regular file entry without contents in archive")
968 EntryKind
::GoodbyeTable
=> {}
// ignore
969 _
=> extract_special(extractor
, entry
, &file_name
)?
,
974 async
fn seq_files_extractor
<T
>(
975 extractor
: &mut Extractor
,
976 mut decoder
: pxar
::decoder
::aio
::Decoder
<T
>,
977 ) -> Result
<(), Error
>
979 T
: pxar
::decoder
::SeqRead
,
981 let mut dir_level
= 0;
983 let entry
= match decoder
.next().await
{
984 Some(entry
) => entry?
,
985 None
=> return Ok(()),
988 let metadata
= entry
.metadata();
989 let (file_name_os
, file_name
) = get_filename(&entry
)?
;
991 if !matches
!(entry
.kind(), EntryKind
::GoodbyeTable
) {
992 log
::debug
!("extracting: {}", entry
.path().display());
995 if let Err(err
) = async
{
997 EntryKind
::Directory
=> {
1000 .enter_directory(file_name_os
.to_owned(), metadata
.clone(), true)
1001 .map_err(|err
| format_err
!("error at entry {:?}: {}", file_name_os
, err
))?
;
1003 EntryKind
::File { size, .. }
=> {
1005 .async_extract_file(
1009 &mut decoder
.contents().ok_or_else(|| {
1010 format_err
!("found regular file entry without contents in archive")
1012 extractor
.overwrite
,
1016 EntryKind
::GoodbyeTable
=> {
1018 extractor
.leave_directory()?
;
1020 _
=> extract_special(extractor
, &entry
, &file_name
)?
,
1022 Ok(()) as Result
<(), Error
>
1026 let display
= entry
.path().display().to_string();
1028 "error extracting {}: {}",
1029 if matches
!(entry
.kind(), EntryKind
::GoodbyeTable
) {
1039 // we've encountered one Goodbye more then Directory, meaning we've left the dir we
1040 // started in - exit early, otherwise the extractor might panic