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
,
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
,
86 if let Some(on_error
) = options
.on_error
{
87 extractor
.on_error(on_error
);
90 let mut match_stack
= Vec
::new();
91 let mut err_path_stack
= vec
![OsString
::from("/")];
92 let mut current_match
= options
.extract_match_default
;
93 while let Some(entry
) = decoder
.next() {
94 let entry
= entry
.map_err(|err
| format_err
!("error reading pxar archive: {}", err
))?
;
96 let file_name_os
= entry
.file_name();
98 // safety check: a file entry in an archive must never contain slashes:
99 if file_name_os
.as_bytes().contains(&b'
/'
) {
100 bail
!("archive file entry contains slashes, which is invalid and a security concern");
103 let file_name
= CString
::new(file_name_os
.as_bytes())
104 .map_err(|_
| format_err
!("encountered file name with null-bytes"))?
;
106 let metadata
= entry
.metadata();
108 extractor
.set_path(entry
.path().as_os_str().to_owned());
110 let match_result
= options
.match_list
.matches(
111 entry
.path().as_os_str().as_bytes(),
112 Some(metadata
.file_type() as u32),
115 let did_match
= match match_result
{
116 Some(MatchType
::Include
) => true,
117 Some(MatchType
::Exclude
) => false,
118 None
=> current_match
,
120 match (did_match
, entry
.kind()) {
121 (_
, EntryKind
::Directory
) => {
122 callback(entry
.path());
124 let create
= current_match
&& match_result
!= Some(MatchType
::Exclude
);
126 .enter_directory(file_name_os
.to_owned(), metadata
.clone(), create
)
127 .map_err(|err
| format_err
!("error at entry {:?}: {}", file_name_os
, err
))?
;
129 // We're starting a new directory, push our old matching state and replace it with
131 match_stack
.push(current_match
);
132 current_match
= did_match
;
134 // When we hit the goodbye table we'll try to apply metadata to the directory, but
135 // the Goodbye entry will not contain the path, so push it to our path stack for
137 err_path_stack
.push(extractor
.clone_path());
141 (_
, EntryKind
::GoodbyeTable
) => {
144 extractor
.set_path(err_path_stack
.pop().ok_or_else(|| {
146 "error at entry {:?}: unexpected end of directory",
153 .map_err(|err
| format_err
!("error at entry {:?}: {}", file_name_os
, err
))?
;
155 // We left a directory, also get back our previous matching state. This is in sync
156 // with `dir_stack` so this should never be empty except for the final goodbye
157 // table, in which case we get back to the default of `true`.
158 current_match
= match_stack
.pop().unwrap_or(true);
162 (true, EntryKind
::Symlink(link
)) => {
163 callback(entry
.path());
164 extractor
.extract_symlink(&file_name
, metadata
, link
.as_ref())
166 (true, EntryKind
::Hardlink(link
)) => {
167 callback(entry
.path());
168 extractor
.extract_hardlink(&file_name
, link
.as_os_str())
170 (true, EntryKind
::Device(dev
)) => {
171 if extractor
.contains_flags(Flags
::WITH_DEVICE_NODES
) {
172 callback(entry
.path());
173 extractor
.extract_device(&file_name
, metadata
, dev
)
178 (true, EntryKind
::Fifo
) => {
179 if extractor
.contains_flags(Flags
::WITH_FIFOS
) {
180 callback(entry
.path());
181 extractor
.extract_special(&file_name
, metadata
, 0)
186 (true, EntryKind
::Socket
) => {
187 if extractor
.contains_flags(Flags
::WITH_SOCKETS
) {
188 callback(entry
.path());
189 extractor
.extract_special(&file_name
, metadata
, 0)
194 (true, EntryKind
::File { size, .. }
) => extractor
.extract_file(
198 &mut decoder
.contents().ok_or_else(|| {
199 format_err
!("found regular file entry without contents in archive")
202 (false, _
) => Ok(()), // skip this
204 .map_err(|err
| format_err
!("error at entry {:?}: {}", file_name_os
, err
))?
;
207 if !extractor
.dir_stack
.is_empty() {
208 bail
!("unexpected eof while decoding pxar archive");
214 /// Common state for file extraction.
215 pub struct Extractor
{
216 feature_flags
: Flags
,
217 allow_existing_dirs
: bool
,
218 dir_stack
: PxarDirStack
,
220 /// For better error output we need to track the current path in the Extractor state.
221 current_path
: Arc
<Mutex
<OsString
>>,
223 /// Error callback. Includes `current_path` in the reformatted error, should return `Ok` to
224 /// continue extracting or the passed error as `Err` to bail out.
225 on_error
: ErrorHandler
,
229 /// Create a new extractor state for a target directory.
233 allow_existing_dirs
: bool
,
234 feature_flags
: Flags
,
237 dir_stack
: PxarDirStack
::new(root_dir
, metadata
),
240 current_path
: Arc
::new(Mutex
::new(OsString
::new())),
241 on_error
: Box
::new(Err
),
245 /// We call this on errors. The error will be reformatted to include `current_path`. The
246 /// callback should decide whether this error was fatal (simply return it) to bail out early,
247 /// or log/remember/accumulate errors somewhere and return `Ok(())` in its place to continue
249 pub fn on_error(&mut self, mut on_error
: Box
<dyn FnMut(Error
) -> Result
<(), Error
> + Send
>) {
250 let path
= Arc
::clone(&self.current_path
);
251 self.on_error
= Box
::new(move |err
: Error
| -> Result
<(), Error
> {
252 on_error(format_err
!("error at {:?}: {}", path
.lock().unwrap(), err
))
256 pub fn set_path(&mut self, path
: OsString
) {
257 *self.current_path
.lock().unwrap() = path
;
260 pub fn clone_path(&self) -> OsString
{
261 self.current_path
.lock().unwrap().clone()
264 /// When encountering a directory during extraction, this is used to keep track of it. If
265 /// `create` is true it is immediately created and its metadata will be updated once we leave
266 /// it. If `create` is false it will only be created if it is going to have any actual content.
267 pub fn enter_directory(
272 ) -> Result
<(), Error
> {
273 self.dir_stack
.push(file_name
, metadata
)?
;
276 self.dir_stack
.create_last_dir(self.allow_existing_dirs
)?
;
282 /// When done with a directory we can apply its metadata if it has been created.
283 pub fn leave_directory(&mut self) -> Result
<(), Error
> {
284 let path_info
= self.dir_stack
.path().to_owned();
289 .map_err(|err
| format_err
!("unexpected end of directory entry: {}", err
))?
290 .ok_or_else(|| format_err
!("broken pxar archive (directory stack underrun)"))?
;
292 if let Some(fd
) = dir
.try_as_borrowed_fd() {
300 .map_err(|err
| format_err
!("failed to apply directory metadata: {}", err
))?
;
306 fn contains_flags(&self, flag
: Flags
) -> bool
{
307 self.feature_flags
.contains(flag
)
310 fn parent_fd(&mut self) -> Result
<RawFd
, Error
> {
312 .last_dir_fd(self.allow_existing_dirs
)
313 .map(|d
| d
.as_raw_fd())
314 .map_err(|err
| format_err
!("failed to get parent directory file descriptor: {}", err
))
317 pub fn extract_symlink(
322 ) -> Result
<(), Error
> {
323 let parent
= self.parent_fd()?
;
324 nix
::unistd
::symlinkat(link
, Some(parent
), file_name
)?
;
330 self.dir_stack
.path(),
335 pub fn extract_hardlink(&mut self, file_name
: &CStr
, link
: &OsStr
) -> Result
<(), Error
> {
336 crate::pxar
::tools
::assert_relative_path(link
)?
;
338 let parent
= self.parent_fd()?
;
339 let root
= self.dir_stack
.root_dir_fd()?
;
340 let target
= CString
::new(link
.as_bytes())?
;
342 Some(root
.as_raw_fd()),
346 nix
::unistd
::LinkatFlags
::NoSymlinkFollow
,
352 pub fn extract_device(
357 ) -> Result
<(), Error
> {
358 self.extract_special(file_name
, metadata
, device
.to_dev_t())
361 pub fn extract_special(
366 ) -> Result
<(), Error
> {
367 let mode
= metadata
.stat
.mode
;
368 let mode
= u32::try_from(mode
).map_err(|_
| {
370 "device node's mode contains illegal bits: 0x{:x} (0o{:o})",
375 let parent
= self.parent_fd()?
;
376 unsafe { c_result!(libc::mknodat(parent, file_name.as_ptr(), mode, device)) }
377 .map_err(|err
| format_err
!("failed to create device node: {}", err
))?
;
384 self.dir_stack
.path(),
394 contents
: &mut dyn io
::Read
,
395 ) -> Result
<(), Error
> {
396 let parent
= self.parent_fd()?
;
397 let mut file
= unsafe {
398 std
::fs
::File
::from_raw_fd(
402 OFlag
::O_CREAT
| OFlag
::O_EXCL
| OFlag
::O_WRONLY
| OFlag
::O_CLOEXEC
,
403 Mode
::from_bits(0o600).unwrap(),
405 .map_err(|err
| format_err
!("failed to create file {:?}: {}", file_name
, err
))?
,
409 metadata
::apply_initial_flags(
415 .map_err(|err
| format_err
!("failed to apply initial flags: {}", err
))?
;
417 let result
= sparse_copy(&mut *contents
, &mut file
)
418 .map_err(|err
| format_err
!("failed to copy file contents: {}", err
))?
;
420 if size
!= result
.written
{
422 "extracted {} bytes of a file of {} bytes",
428 if result
.seeked_last
{
429 while match nix
::unistd
::ftruncate(file
.as_raw_fd(), size
as i64) {
431 Err(errno
) if errno
== nix
::errno
::Errno
::EINTR
=> true,
432 Err(err
) => bail
!("error setting file size: {}", err
),
440 self.dir_stack
.path(),
445 pub async
fn async_extract_file
<T
: tokio
::io
::AsyncRead
+ Unpin
>(
451 ) -> Result
<(), Error
> {
452 let parent
= self.parent_fd()?
;
453 let mut file
= tokio
::fs
::File
::from_std(unsafe {
454 std
::fs
::File
::from_raw_fd(
458 OFlag
::O_CREAT
| OFlag
::O_EXCL
| OFlag
::O_WRONLY
| OFlag
::O_CLOEXEC
,
459 Mode
::from_bits(0o600).unwrap(),
461 .map_err(|err
| format_err
!("failed to create file {:?}: {}", file_name
, err
))?
,
465 metadata
::apply_initial_flags(
471 .map_err(|err
| format_err
!("failed to apply initial flags: {}", err
))?
;
473 let result
= sparse_copy_async(&mut *contents
, &mut file
)
475 .map_err(|err
| format_err
!("failed to copy file contents: {}", err
))?
;
477 if size
!= result
.written
{
479 "extracted {} bytes of a file of {} bytes",
485 if result
.seeked_last
{
486 while match nix
::unistd
::ftruncate(file
.as_raw_fd(), size
as i64) {
488 Err(errno
) if errno
== nix
::errno
::Errno
::EINTR
=> true,
489 Err(err
) => bail
!("error setting file size: {}", err
),
497 self.dir_stack
.path(),
503 fn add_metadata_to_header(header
: &mut tar
::Header
, metadata
: &Metadata
) {
504 header
.set_mode(metadata
.stat
.mode
as u32);
505 header
.set_mtime(metadata
.stat
.mtime
.secs
as u64);
506 header
.set_uid(metadata
.stat
.uid
as u64);
507 header
.set_gid(metadata
.stat
.gid
as u64);
510 async
fn tar_add_file
<'a
, W
, T
>(
511 tar
: &mut proxmox_compression
::tar
::Builder
<W
>,
512 contents
: Option
<Contents
<'a
, T
>>,
516 ) -> Result
<(), Error
>
518 T
: pxar
::decoder
::SeqRead
+ Unpin
+ Send
+ Sync
+ '
static,
519 W
: tokio
::io
::AsyncWrite
+ Unpin
+ Send
+ '
static,
521 let mut header
= tar
::Header
::new_gnu();
522 header
.set_entry_type(tar
::EntryType
::Regular
);
523 header
.set_size(size
);
524 add_metadata_to_header(&mut header
, metadata
);
527 Some(content
) => tar
.add_entry(&mut header
, path
, content
).await
,
528 None
=> tar
.add_entry(&mut header
, path
, tokio
::io
::empty()).await
,
530 .map_err(|err
| format_err
!("could not send file entry: {}", err
))?
;
534 /// Creates a tar file from `path` and writes it into `output`
535 pub async
fn create_tar
<T
, W
, P
>(output
: W
, accessor
: Accessor
<T
>, path
: P
) -> Result
<(), Error
>
537 T
: Clone
+ pxar
::accessor
::ReadAt
+ Unpin
+ Send
+ Sync
+ '
static,
538 W
: tokio
::io
::AsyncWrite
+ Unpin
+ Send
+ '
static,
541 let root
= accessor
.open_root().await?
;
545 .ok_or_else(|| format_err
!("error opening '{:?}'", path
.as_ref()))?
;
547 let mut components
= file
.entry().path().components();
548 components
.next_back(); // discard last
549 let prefix
= components
.as_path();
551 let mut tarencoder
= proxmox_compression
::tar
::Builder
::new(output
);
552 let mut hardlinks
: HashMap
<PathBuf
, PathBuf
> = HashMap
::new();
554 if let Ok(dir
) = file
.enter_directory().await
{
555 let entry
= dir
.lookup_self().await?
;
556 let path
= entry
.path().strip_prefix(prefix
)?
;
558 if path
!= Path
::new("/") {
559 let metadata
= entry
.metadata();
560 let mut header
= tar
::Header
::new_gnu();
561 header
.set_entry_type(tar
::EntryType
::Directory
);
562 add_metadata_to_header(&mut header
, metadata
);
566 .add_entry(&mut header
, path
, tokio
::io
::empty())
568 .map_err(|err
| format_err
!("could not send dir entry: {}", err
))?
;
571 let mut decoder
= dir
.decode_full().await?
;
572 decoder
.enable_goodbye_entries(false);
573 while let Some(entry
) = decoder
.next().await
{
574 let entry
= entry
.map_err(|err
| format_err
!("cannot decode entry: {}", err
))?
;
576 let metadata
= entry
.metadata();
577 let path
= entry
.path().strip_prefix(prefix
)?
;
580 EntryKind
::File { .. }
=> {
581 let size
= decoder
.content_size().unwrap_or(0);
582 tar_add_file(&mut tarencoder
, decoder
.contents(), size
, metadata
, path
).await?
584 EntryKind
::Hardlink(link
) => {
585 if !link
.data
.is_empty() {
589 .ok_or_else(|| format_err
!("error looking up '{:?}'", path
))?
;
590 let realfile
= accessor
.follow_hardlink(&entry
).await?
;
591 let metadata
= realfile
.entry().metadata();
592 let realpath
= Path
::new(link
);
594 log
::debug
!("adding '{}' to tar", path
.display());
596 let stripped_path
= match realpath
.strip_prefix(prefix
) {
599 // outside of our tar archive, add the first occurrence to the tar
600 if let Some(path
) = hardlinks
.get(realpath
) {
603 let size
= decoder
.content_size().unwrap_or(0);
612 hardlinks
.insert(realpath
.to_owned(), path
.to_owned());
617 let mut header
= tar
::Header
::new_gnu();
618 header
.set_entry_type(tar
::EntryType
::Link
);
619 add_metadata_to_header(&mut header
, metadata
);
622 .add_link(&mut header
, path
, stripped_path
)
624 .map_err(|err
| format_err
!("could not send hardlink entry: {}", err
))?
;
627 EntryKind
::Symlink(link
) if !link
.data
.is_empty() => {
628 log
::debug
!("adding '{}' to tar", path
.display());
629 let realpath
= Path
::new(link
);
630 let mut header
= tar
::Header
::new_gnu();
631 header
.set_entry_type(tar
::EntryType
::Symlink
);
632 add_metadata_to_header(&mut header
, metadata
);
635 .add_link(&mut header
, path
, realpath
)
637 .map_err(|err
| format_err
!("could not send symlink entry: {}", err
))?
;
640 log
::debug
!("adding '{}' to tar", path
.display());
641 let mut header
= tar
::Header
::new_gnu();
642 header
.set_entry_type(tar
::EntryType
::Fifo
);
643 add_metadata_to_header(&mut header
, metadata
);
645 header
.set_device_major(0)?
;
646 header
.set_device_minor(0)?
;
649 .add_entry(&mut header
, path
, tokio
::io
::empty())
651 .map_err(|err
| format_err
!("could not send fifo entry: {}", err
))?
;
653 EntryKind
::Directory
=> {
654 log
::debug
!("adding '{}' to tar", path
.display());
655 // we cannot add the root path itself
656 if path
!= Path
::new("/") {
657 let mut header
= tar
::Header
::new_gnu();
658 header
.set_entry_type(tar
::EntryType
::Directory
);
659 add_metadata_to_header(&mut header
, metadata
);
663 .add_entry(&mut header
, path
, tokio
::io
::empty())
665 .map_err(|err
| format_err
!("could not send dir entry: {}", err
))?
;
668 EntryKind
::Device(device
) => {
669 log
::debug
!("adding '{}' to tar", path
.display());
670 let entry_type
= if metadata
.stat
.is_chardev() {
673 tar
::EntryType
::Block
675 let mut header
= tar
::Header
::new_gnu();
676 header
.set_entry_type(entry_type
);
677 header
.set_device_major(device
.major
as u32)?
;
678 header
.set_device_minor(device
.minor
as u32)?
;
679 add_metadata_to_header(&mut header
, metadata
);
682 .add_entry(&mut header
, path
, tokio
::io
::empty())
684 .map_err(|err
| format_err
!("could not send device entry: {}", err
))?
;
686 _
=> {}
// ignore all else
691 tarencoder
.finish().await
.map_err(|err
| {
692 log
::error
!("error during finishing of zip: {}", err
);
698 pub async
fn create_zip
<T
, W
, P
>(output
: W
, accessor
: Accessor
<T
>, path
: P
) -> Result
<(), Error
>
700 T
: Clone
+ pxar
::accessor
::ReadAt
+ Unpin
+ Send
+ Sync
+ '
static,
701 W
: tokio
::io
::AsyncWrite
+ Unpin
+ Send
+ '
static,
704 let root
= accessor
.open_root().await?
;
708 .ok_or_else(|| format_err
!("error opening '{:?}'", path
.as_ref()))?
;
711 let mut components
= file
.entry().path().components();
712 components
.next_back(); // discar last
713 components
.as_path().to_owned()
716 let mut zip
= ZipEncoder
::new(output
);
718 if let Ok(dir
) = file
.enter_directory().await
{
719 let entry
= dir
.lookup_self().await?
;
720 let path
= entry
.path().strip_prefix(&prefix
)?
;
721 if path
!= Path
::new("/") {
722 let metadata
= entry
.metadata();
723 let entry
= ZipEntry
::new(
725 metadata
.stat
.mtime
.secs
,
726 metadata
.stat
.mode
as u16,
729 zip
.add_entry
::<FileContents
<T
>>(entry
, None
).await?
;
732 let mut decoder
= dir
.decode_full().await?
;
733 decoder
.enable_goodbye_entries(false);
734 while let Some(entry
) = decoder
.next().await
{
736 let metadata
= entry
.metadata();
737 let path
= entry
.path().strip_prefix(&prefix
)?
;
740 EntryKind
::File { .. }
=> {
741 log
::debug
!("adding '{}' to zip", path
.display());
742 let entry
= ZipEntry
::new(
744 metadata
.stat
.mtime
.secs
,
745 metadata
.stat
.mode
as u16,
748 zip
.add_entry(entry
, decoder
.contents())
750 .map_err(|err
| format_err
!("could not send file entry: {}", err
))?
;
752 EntryKind
::Hardlink(_
) => {
756 .ok_or_else(|| format_err
!("error looking up '{:?}'", path
))?
;
757 let realfile
= accessor
.follow_hardlink(&entry
).await?
;
758 let metadata
= realfile
.entry().metadata();
759 log
::debug
!("adding '{}' to zip", path
.display());
760 let entry
= ZipEntry
::new(
762 metadata
.stat
.mtime
.secs
,
763 metadata
.stat
.mode
as u16,
766 zip
.add_entry(entry
, decoder
.contents())
768 .map_err(|err
| format_err
!("could not send file entry: {}", err
))?
;
770 EntryKind
::Directory
=> {
771 log
::debug
!("adding '{}' to zip", path
.display());
772 let entry
= ZipEntry
::new(
774 metadata
.stat
.mtime
.secs
,
775 metadata
.stat
.mode
as u16,
778 zip
.add_entry
::<FileContents
<T
>>(entry
, None
).await?
;
780 _
=> {}
// ignore all else
785 zip
.finish().await
.map_err(|err
| {
786 eprintln
!("error during finishing of zip: {}", err
);
791 fn get_extractor
<DEST
>(destination
: DEST
, metadata
: Metadata
) -> Result
<Extractor
, Error
>
798 Some(CreateOptions
::new().perm(Mode
::from_bits_truncate(0o700))),
802 "error creating directory {:?}: {}",
803 destination
.as_ref(),
809 destination
.as_ref(),
810 OFlag
::O_DIRECTORY
| OFlag
::O_CLOEXEC
,
815 "unable to open target directory {:?}: {}",
816 destination
.as_ref(),
821 Ok(Extractor
::new(dir
, metadata
, false, Flags
::DEFAULT
))
824 pub async
fn extract_sub_dir
<T
, DEST
, PATH
>(
826 decoder
: Accessor
<T
>,
828 ) -> Result
<(), Error
>
830 T
: Clone
+ pxar
::accessor
::ReadAt
+ Unpin
+ Send
+ Sync
+ '
static,
834 let root
= decoder
.open_root().await?
;
836 let mut extractor
= get_extractor(
838 root
.lookup_self().await?
.entry().metadata().clone(),
844 .ok_or_else(|| format_err
!("error opening '{:?}'", path
.as_ref()))?
;
846 recurse_files_extractor(&mut extractor
, file
).await
849 pub async
fn extract_sub_dir_seq
<S
, DEST
>(
851 mut decoder
: Decoder
<S
>,
852 ) -> Result
<(), Error
>
854 S
: pxar
::decoder
::SeqRead
+ Unpin
+ Send
+ '
static,
857 decoder
.enable_goodbye_entries(true);
858 let root
= match decoder
.next().await
{
859 Some(Ok(root
)) => root
,
860 Some(Err(err
)) => bail
!("error getting root entry from pxar: {}", err
),
861 None
=> bail
!("cannot extract empty archive"),
864 let mut extractor
= get_extractor(destination
, root
.metadata().clone())?
;
866 if let Err(err
) = seq_files_extractor(&mut extractor
, decoder
).await
{
867 log
::error
!("error extracting pxar archive: {}", err
);
874 extractor
: &mut Extractor
,
877 ) -> Result
<(), Error
> {
878 let metadata
= entry
.metadata();
880 EntryKind
::Symlink(link
) => {
881 extractor
.extract_symlink(file_name
, metadata
, link
.as_ref())?
;
883 EntryKind
::Hardlink(link
) => {
884 extractor
.extract_hardlink(file_name
, link
.as_os_str())?
;
886 EntryKind
::Device(dev
) => {
887 if extractor
.contains_flags(Flags
::WITH_DEVICE_NODES
) {
888 extractor
.extract_device(file_name
, metadata
, dev
)?
;
892 if extractor
.contains_flags(Flags
::WITH_FIFOS
) {
893 extractor
.extract_special(file_name
, metadata
, 0)?
;
896 EntryKind
::Socket
=> {
897 if extractor
.contains_flags(Flags
::WITH_SOCKETS
) {
898 extractor
.extract_special(file_name
, metadata
, 0)?
;
901 _
=> bail
!("extract_special used with unsupported entry kind"),
906 fn get_filename(entry
: &Entry
) -> Result
<(OsString
, CString
), Error
> {
907 let file_name_os
= entry
.file_name().to_owned();
909 // safety check: a file entry in an archive must never contain slashes:
910 if file_name_os
.as_bytes().contains(&b'
/'
) {
911 bail
!("archive file entry contains slashes, which is invalid and a security concern");
914 let file_name
= CString
::new(file_name_os
.as_bytes())
915 .map_err(|_
| format_err
!("encountered file name with null-bytes"))?
;
917 Ok((file_name_os
, file_name
))
920 async
fn recurse_files_extractor
<T
>(
921 extractor
: &mut Extractor
,
923 ) -> Result
<(), Error
>
925 T
: Clone
+ pxar
::accessor
::ReadAt
+ Unpin
+ Send
+ Sync
+ '
static,
927 let entry
= file
.entry();
928 let metadata
= entry
.metadata();
929 let (file_name_os
, file_name
) = get_filename(entry
)?
;
931 log
::debug
!("extracting: {}", file
.path().display());
934 EntryKind
::Directory
=> {
936 .enter_directory(file_name_os
.to_owned(), metadata
.clone(), true)
937 .map_err(|err
| format_err
!("error at entry {:?}: {}", file_name_os
, err
))?
;
939 let dir
= file
.enter_directory().await?
;
940 let mut seq_decoder
= dir
.decode_full().await?
;
941 seq_decoder
.enable_goodbye_entries(true);
942 seq_files_extractor(extractor
, seq_decoder
).await?
;
943 extractor
.leave_directory()?
;
945 EntryKind
::File { size, .. }
=> {
951 &mut file
.contents().await
.map_err(|_
| {
952 format_err
!("found regular file entry without contents in archive")
957 EntryKind
::GoodbyeTable
=> {}
// ignore
958 _
=> extract_special(extractor
, entry
, &file_name
)?
,
963 async
fn seq_files_extractor
<T
>(
964 extractor
: &mut Extractor
,
965 mut decoder
: pxar
::decoder
::aio
::Decoder
<T
>,
966 ) -> Result
<(), Error
>
968 T
: pxar
::decoder
::SeqRead
,
970 let mut dir_level
= 0;
972 let entry
= match decoder
.next().await
{
973 Some(entry
) => entry?
,
974 None
=> return Ok(()),
977 let metadata
= entry
.metadata();
978 let (file_name_os
, file_name
) = get_filename(&entry
)?
;
980 if !matches
!(entry
.kind(), EntryKind
::GoodbyeTable
) {
981 log
::debug
!("extracting: {}", entry
.path().display());
984 if let Err(err
) = async
{
986 EntryKind
::Directory
=> {
989 .enter_directory(file_name_os
.to_owned(), metadata
.clone(), true)
990 .map_err(|err
| format_err
!("error at entry {:?}: {}", file_name_os
, err
))?
;
992 EntryKind
::File { size, .. }
=> {
998 &mut decoder
.contents().ok_or_else(|| {
999 format_err
!("found regular file entry without contents in archive")
1004 EntryKind
::GoodbyeTable
=> {
1006 extractor
.leave_directory()?
;
1008 _
=> extract_special(extractor
, &entry
, &file_name
)?
,
1010 Ok(()) as Result
<(), Error
>
1014 let display
= entry
.path().display().to_string();
1016 "error extracting {}: {}",
1017 if matches
!(entry
.kind(), EntryKind
::GoodbyeTable
) {
1027 // we've encountered one Goodbye more then Directory, meaning we've left the dir we
1028 // started in - exit early, otherwise the extractor might panic