1 use std
::collections
::HashMap
;
2 use std
::ffi
::{CStr, CString, OsStr, OsString}
;
3 use std
::future
::Future
;
6 use std
::os
::unix
::ffi
::{OsStrExt, OsStringExt}
;
7 use std
::path
::{Path, PathBuf}
;
10 use anyhow
::{bail, format_err, Error}
;
12 use nix
::fcntl
::OFlag
;
13 use nix
::sys
::stat
::Mode
;
15 use pathpatterns
::{MatchEntry, MatchList, MatchPattern, MatchType, PatternFlag}
;
16 use proxmox
::api
::api
;
17 use proxmox
::api
::cli
::{self, CliCommand, CliCommandMap, CliHelper, CommandLineInterface}
;
18 use proxmox
::tools
::fs
::{create_path, CreateOptions}
;
19 use pxar
::{EntryKind, Metadata}
;
21 use crate::backup
::catalog
::{self, DirEntryAttribute}
;
22 use crate::pxar
::fuse
::{Accessor, FileEntry}
;
23 use crate::pxar
::Flags
;
24 use pbs_runtime
::block_in_place
;
25 use crate::tools
::ControlFlow
;
27 type CatalogReader
= crate::backup
::CatalogReader
<std
::fs
::File
>;
29 const MAX_SYMLINK_COUNT
: usize = 40;
31 static mut SHELL
: Option
<usize> = None
;
33 /// This list defines all the shell commands and their properties
34 /// using the api schema
35 pub fn catalog_shell_cli() -> CommandLineInterface
{
36 CommandLineInterface
::Nested(
38 .insert("pwd", CliCommand
::new(&API_METHOD_PWD_COMMAND
))
41 CliCommand
::new(&API_METHOD_CD_COMMAND
)
43 .completion_cb("path", complete_path
),
47 CliCommand
::new(&API_METHOD_LS_COMMAND
)
49 .completion_cb("path", complete_path
),
53 CliCommand
::new(&API_METHOD_STAT_COMMAND
)
55 .completion_cb("path", complete_path
),
59 CliCommand
::new(&API_METHOD_SELECT_COMMAND
)
61 .completion_cb("path", complete_path
),
65 CliCommand
::new(&API_METHOD_DESELECT_COMMAND
)
67 .completion_cb("path", complete_path
),
71 CliCommand
::new(&API_METHOD_CLEAR_SELECTED_COMMAND
),
75 CliCommand
::new(&API_METHOD_LIST_SELECTED_COMMAND
),
79 CliCommand
::new(&API_METHOD_RESTORE_SELECTED_COMMAND
)
80 .arg_param(&["target"])
81 .completion_cb("target", crate::tools
::complete_file_name
),
85 CliCommand
::new(&API_METHOD_RESTORE_COMMAND
)
86 .arg_param(&["target"])
87 .completion_cb("target", crate::tools
::complete_file_name
),
91 CliCommand
::new(&API_METHOD_FIND_COMMAND
).arg_param(&["pattern"]),
95 CliCommand
::new(&API_METHOD_EXIT
),
101 fn complete_path(complete_me
: &str, _map
: &HashMap
<String
, String
>) -> Vec
<String
> {
102 let shell
: &mut Shell
= unsafe { std::mem::transmute(SHELL.unwrap()) }
;
103 match shell
.complete_path(complete_me
) {
106 eprintln
!("error during completion: {}", err
);
112 // just an empty wrapper so that it is displayed in help/docs, we check
113 // in the readloop for 'exit' again break
114 #[api(input: { properties: {} })]
116 async
fn exit() -> Result
<(), Error
> {
120 #[api(input: { properties: {} })]
121 /// List the current working directory.
122 async
fn pwd_command() -> Result
<(), Error
> {
123 Shell
::with(move |shell
| shell
.pwd()).await
132 description
: "target path."
137 /// Change the current working directory to the new directory
138 async
fn cd_command(path
: Option
<String
>) -> Result
<(), Error
> {
139 let path
= path
.as_ref().map(Path
::new
);
140 Shell
::with(move |shell
| shell
.cd(path
)).await
149 description
: "target path."
154 /// List the content of working directory or given path.
155 async
fn ls_command(path
: Option
<String
>) -> Result
<(), Error
> {
156 let path
= path
.as_ref().map(Path
::new
);
157 Shell
::with(move |shell
| shell
.ls(path
)).await
165 description
: "target path."
170 /// Read the metadata for a given directory entry.
172 /// This is expensive because the data has to be read from the pxar archive, which means reading
173 /// over the network.
174 async
fn stat_command(path
: String
) -> Result
<(), Error
> {
175 Shell
::with(move |shell
| shell
.stat(PathBuf
::from(path
))).await
183 description
: "target path."
188 /// Select an entry for restore.
190 /// This will return an error if the entry is already present in the list or
191 /// if an invalid path was provided.
192 async
fn select_command(path
: String
) -> Result
<(), Error
> {
193 Shell
::with(move |shell
| shell
.select(PathBuf
::from(path
))).await
201 description
: "path to entry to remove from list."
206 /// Deselect an entry for restore.
208 /// This will return an error if the entry was not found in the list of entries
209 /// selected for restore.
210 async
fn deselect_command(path
: String
) -> Result
<(), Error
> {
211 Shell
::with(move |shell
| shell
.deselect(PathBuf
::from(path
))).await
214 #[api( input: { properties: { } })]
215 /// Clear the list of files selected for restore.
216 async
fn clear_selected_command() -> Result
<(), Error
> {
217 Shell
::with(move |shell
| shell
.deselect_all()).await
225 description
: "List match patterns instead of the matching files.",
232 /// List entries currently selected for restore.
233 async
fn list_selected_command(patterns
: bool
) -> Result
<(), Error
> {
234 Shell
::with(move |shell
| shell
.list_selected(patterns
)).await
242 description
: "Match pattern for matching files in the catalog."
248 description
: "Add matching filenames to list for restore."
253 /// Find entries in the catalog matching the given match pattern.
254 async
fn find_command(pattern
: String
, select
: bool
) -> Result
<(), Error
> {
255 Shell
::with(move |shell
| shell
.find(pattern
, select
)).await
263 description
: "target path for restore on local filesystem."
268 /// Restore the selected entries to the given target path.
270 /// Target must not exist on the clients filesystem.
271 async
fn restore_selected_command(target
: String
) -> Result
<(), Error
> {
272 Shell
::with(move |shell
| shell
.restore_selected(PathBuf
::from(target
))).await
280 description
: "target path for restore on local filesystem."
285 description
: "match pattern to limit files for restore."
290 /// Restore the sub-archive given by the current working directory to target.
292 /// By further providing a pattern, the restore can be limited to a narrower
293 /// subset of this sub-archive.
294 /// If pattern is not present or empty, the full archive is restored to target.
295 async
fn restore_command(target
: String
, pattern
: Option
<String
>) -> Result
<(), Error
> {
296 Shell
::with(move |shell
| shell
.restore(PathBuf
::from(target
), pattern
)).await
299 /// TODO: Should we use this to fix `step()`? Make path resolution behave more like described in
300 /// the path_resolution(7) man page.
302 /// The `Path` type's component iterator does not tell us anything about trailing slashes or
303 /// trailing `Component::CurDir` entries. Since we only support regular paths we'll roll our own
305 enum PathComponent
<'a
> {
313 struct PathComponentIter
<'a
> {
315 state
: u8, // 0=beginning, 1=ongoing, 2=trailing, 3=finished (fused)
318 impl std
::iter
::FusedIterator
for PathComponentIter
<'_
> {}
320 impl<'a
> Iterator
for PathComponentIter
<'a
> {
321 type Item
= PathComponent
<'a
>;
323 fn next(&mut self) -> Option
<Self::Item
> {
324 if self.path
.is_empty() {
330 if self.path
[0] == b'
/'
{
332 self.path
= &self.path
[1..];
333 return Some(PathComponent
::Root
);
338 let had_slashes
= self.path
[0] == b'
/'
;
339 while self.path
.get(0).copied() == Some(b'
/'
) {
340 self.path
= &self.path
[1..];
343 Some(match self.path
{
344 [] if had_slashes
=> PathComponent
::TrailingSlash
,
346 [b'
.'
] | [b'
.'
, b'
/'
, ..] => {
347 self.path
= &self.path
[1..];
348 PathComponent
::CurDir
350 [b'
.'
, b'
.'
] | [b'
.'
, b'
.'
, b'
/'
, ..] => {
351 self.path
= &self.path
[2..];
352 PathComponent
::ParentDir
358 .position(|&b
| b
== b'
/'
)
359 .unwrap_or(self.path
.len());
360 let (out
, rest
) = self.path
.split_at(end
);
362 PathComponent
::Normal(OsStr
::from_bytes(out
))
369 /// Readline instance handling input and callbacks
370 rl
: rustyline
::Editor
<CliHelper
>,
372 /// Interactive prompt.
375 /// Calalog reader instance to navigate
376 catalog
: CatalogReader
,
378 /// List of selected paths for restore
379 selected
: HashMap
<OsString
, MatchEntry
>,
381 /// pxar accessor instance for the current pxar archive
384 /// The current position in the archive.
385 position
: Vec
<PathStackEntry
>,
389 struct PathStackEntry
{
390 /// This is always available. We mainly navigate through the catalog.
391 catalog
: catalog
::DirEntry
,
393 /// Whenever we need something from the actual archive we fill this out. This is cached along
395 pxar
: Option
<FileEntry
>,
398 impl PathStackEntry
{
399 fn new(dir_entry
: catalog
::DirEntry
) -> Self {
408 /// Create a new shell for the given catalog and pxar archive.
410 mut catalog
: CatalogReader
,
413 ) -> Result
<Self, Error
> {
414 let cli_helper
= CliHelper
::new(catalog_shell_cli());
415 let mut rl
= rustyline
::Editor
::<CliHelper
>::new();
416 rl
.set_helper(Some(cli_helper
));
418 let catalog_root
= catalog
.root()?
;
419 let archive_root
= catalog
420 .lookup(&catalog_root
, archive_name
.as_bytes())?
421 .ok_or_else(|| format_err
!("archive not found in catalog"))?
;
422 let position
= vec
![PathStackEntry
::new(archive_root
)];
424 let mut this
= Self {
426 prompt
: String
::new(),
428 selected
: HashMap
::new(),
432 this
.update_prompt();
436 async
fn with
<'a
, Fut
, R
, F
>(call
: F
) -> Result
<R
, Error
>
438 F
: FnOnce(&'a
mut Shell
) -> Fut
,
439 Fut
: Future
<Output
= Result
<R
, Error
>>,
444 let shell
: &mut Shell
= unsafe { std::mem::transmute(SHELL.unwrap()) }
;
445 call(&mut *shell
).await
448 pub async
fn shell(mut self) -> Result
<(), Error
> {
449 let this
= &mut self;
451 SHELL
= Some(this
as *mut Shell
as usize);
453 while let Ok(line
) = this
.rl
.readline(&this
.prompt
) {
457 let helper
= this
.rl
.helper().unwrap();
458 let args
= match cli
::shellword_split(&line
) {
461 println
!("Error: {}", err
);
467 cli
::handle_command_future(helper
.cmd_def(), "", args
, cli
::CliEnvironment
::new())
469 this
.rl
.add_history_entry(line
);
470 this
.update_prompt();
475 fn update_prompt(&mut self) {
476 self.prompt
= "pxar:".to_string();
477 if self.position
.len() <= 1 {
478 self.prompt
.push('
/'
);
480 for p
in self.position
.iter().skip(1) {
481 if !p
.catalog
.name
.starts_with(b
"/") {
482 self.prompt
.push('
/'
);
484 match std
::str::from_utf8(&p
.catalog
.name
) {
485 Ok(entry
) => self.prompt
.push_str(entry
),
486 Err(_
) => self.prompt
.push_str("<non-utf8-dir>"),
490 self.prompt
.push_str(" > ");
493 async
fn pwd(&mut self) -> Result
<(), Error
> {
494 let stack
= Self::lookup(
502 let path
= Self::format_path_stack(&stack
);
503 println
!("{:?}", path
);
507 fn new_path_stack(&self) -> Vec
<PathStackEntry
> {
508 self.position
[..1].to_vec()
511 async
fn resolve_symlink(
512 stack
: &mut Vec
<PathStackEntry
>,
513 catalog
: &mut CatalogReader
,
515 follow_symlinks
: &mut Option
<usize>,
516 ) -> Result
<(), Error
> {
517 if let Some(ref mut symlink_count
) = follow_symlinks
{
519 if *symlink_count
> MAX_SYMLINK_COUNT
{
520 bail
!("too many levels of symbolic links");
523 let file
= Self::walk_pxar_archive(accessor
, &mut stack
[..]).await?
;
525 let path
= match file
.entry().kind() {
526 EntryKind
::Symlink(symlink
) => Path
::new(symlink
.as_os_str()),
527 _
=> bail
!("symlink in the catalog was not a symlink in the archive"),
531 Self::lookup(&stack
, &mut *catalog
, accessor
, Some(path
), follow_symlinks
).await?
;
537 bail
!("target is a symlink");
541 /// Walk a path and add it to the path stack.
543 /// If the symlink count is used, symlinks will be followed, until we hit the cap and error
546 stack
: &mut Vec
<PathStackEntry
>,
547 catalog
: &mut CatalogReader
,
549 component
: std
::path
::Component
<'_
>,
550 follow_symlinks
: &mut Option
<usize>,
551 ) -> Result
<(), Error
> {
552 use std
::path
::Component
;
554 Component
::Prefix(_
) => bail
!("invalid path component (prefix)"),
555 Component
::RootDir
=> stack
.truncate(1),
556 Component
::CurDir
=> {
557 if stack
.last().unwrap().catalog
.is_symlink() {
558 Self::resolve_symlink(stack
, catalog
, accessor
, follow_symlinks
).await?
;
561 Component
::ParentDir
=> drop(stack
.pop()),
562 Component
::Normal(entry
) => {
563 if stack
.last().unwrap().catalog
.is_symlink() {
564 Self::resolve_symlink(stack
, catalog
, accessor
, follow_symlinks
).await?
;
566 match catalog
.lookup(&stack
.last().unwrap().catalog
, entry
.as_bytes())?
{
567 Some(dir
) => stack
.push(PathStackEntry
::new(dir
)),
568 None
=> bail
!("no such file or directory: {:?}", entry
),
577 stack
: &mut Vec
<PathStackEntry
>,
578 catalog
: &mut CatalogReader
,
579 component
: std
::path
::Component
<'_
>,
580 ) -> Result
<(), Error
> {
581 use std
::path
::Component
;
583 Component
::Prefix(_
) => bail
!("invalid path component (prefix)"),
584 Component
::RootDir
=> stack
.truncate(1),
585 Component
::CurDir
=> {
586 if stack
.last().unwrap().catalog
.is_symlink() {
587 bail
!("target is a symlink");
590 Component
::ParentDir
=> drop(stack
.pop()),
591 Component
::Normal(entry
) => {
592 if stack
.last().unwrap().catalog
.is_symlink() {
593 bail
!("target is a symlink");
595 match catalog
.lookup(&stack
.last().unwrap().catalog
, entry
.as_bytes())?
{
596 Some(dir
) => stack
.push(PathStackEntry
::new(dir
)),
597 None
=> bail
!("no such file or directory: {:?}", entry
),
605 /// The pxar accessor is required to resolve symbolic links
606 async
fn walk_catalog(
607 stack
: &mut Vec
<PathStackEntry
>,
608 catalog
: &mut CatalogReader
,
611 follow_symlinks
: &mut Option
<usize>,
612 ) -> Result
<(), Error
> {
613 for c
in path
.components() {
614 Self::step(stack
, catalog
, accessor
, c
, follow_symlinks
).await?
;
619 /// Non-async version cannot follow symlinks.
620 fn walk_catalog_nofollow(
621 stack
: &mut Vec
<PathStackEntry
>,
622 catalog
: &mut CatalogReader
,
624 ) -> Result
<(), Error
> {
625 for c
in path
.components() {
626 Self::step_nofollow(stack
, catalog
, c
)?
;
631 /// This assumes that there are no more symlinks in the path stack.
632 async
fn walk_pxar_archive(
634 mut stack
: &mut [PathStackEntry
],
635 ) -> Result
<FileEntry
, Error
> {
636 if stack
[0].pxar
.is_none() {
637 stack
[0].pxar
= Some(accessor
.open_root().await?
.lookup_self().await?
);
640 // Now walk the directory stack:
642 while at
< stack
.len() {
643 if stack
[at
].pxar
.is_some() {
648 let parent
= stack
[at
- 1].pxar
.as_ref().unwrap();
649 let dir
= parent
.enter_directory().await?
;
650 let name
= Path
::new(OsStr
::from_bytes(&stack
[at
].catalog
.name
));
651 stack
[at
].pxar
= Some(
654 .ok_or_else(|| format_err
!("no such entry in pxar file: {:?}", name
))?
,
660 Ok(stack
.last().unwrap().pxar
.clone().unwrap())
663 fn complete_path(&mut self, input
: &str) -> Result
<Vec
<String
>, Error
> {
665 let (parent
, base
, part
) = match input
.rfind('
/'
) {
667 let (base
, part
) = input
.split_at(ind
+ 1);
668 let path
= PathBuf
::from(base
);
669 if path
.is_absolute() {
670 tmp_stack
= self.new_path_stack();
672 tmp_stack
= self.position
.clone();
674 Self::walk_catalog_nofollow(&mut tmp_stack
, &mut self.catalog
, &path
)?
;
675 (&tmp_stack
.last().unwrap().catalog
, base
, part
)
677 None
=> (&self.position
.last().unwrap().catalog
, "", input
),
680 let entries
= self.catalog
.read_dir(parent
)?
;
682 let mut out
= Vec
::new();
683 for entry
in entries
{
684 let mut name
= base
.to_string();
685 if entry
.name
.starts_with(part
.as_bytes()) {
686 name
.push_str(std
::str::from_utf8(&entry
.name
)?
);
687 if entry
.is_directory() {
697 // Break async recursion here: lookup -> walk_catalog -> step -> lookup
698 fn lookup
<'future
, 's
, 'c
, 'a
, 'p
, 'y
>(
699 stack
: &'s
[PathStackEntry
],
700 catalog
: &'c
mut CatalogReader
,
701 accessor
: &'a Accessor
,
702 path
: Option
<&'p Path
>,
703 follow_symlinks
: &'y
mut Option
<usize>,
704 ) -> Pin
<Box
<dyn Future
<Output
= Result
<Vec
<PathStackEntry
>, Error
>> + Send
+ 'future
>>
712 Box
::pin(async
move {
714 None
=> stack
.to_vec(),
716 let mut stack
= if path
.is_absolute() {
721 Self::walk_catalog(&mut stack
, catalog
, accessor
, path
, follow_symlinks
)
729 async
fn ls(&mut self, path
: Option
<&Path
>) -> Result
<(), Error
> {
730 let stack
= Self::lookup(
739 let last
= stack
.last().unwrap();
740 if last
.catalog
.is_directory() {
741 let items
= self.catalog
.read_dir(&stack
.last().unwrap().catalog
)?
;
742 let mut out
= std
::io
::stdout();
745 out
.write_all(&item
.name
)?
;
746 out
.write_all(b
"\n")?
;
749 let mut out
= std
::io
::stdout();
750 out
.write_all(&last
.catalog
.name
)?
;
751 out
.write_all(b
"\n")?
;
756 async
fn stat(&mut self, path
: PathBuf
) -> Result
<(), Error
> {
757 let mut stack
= Self::lookup(
766 let file
= Self::walk_pxar_archive(&self.accessor
, &mut stack
).await?
;
768 .write_all(crate::pxar
::format_multi_line_entry(file
.entry()).as_bytes())?
;
772 async
fn cd(&mut self, path
: Option
<&Path
>) -> Result
<(), Error
> {
775 let new_position
= Self::lookup(
783 if !new_position
.last().unwrap().catalog
.is_directory() {
784 bail
!("not a directory");
786 self.position
= new_position
;
788 None
=> self.position
.truncate(1),
790 self.update_prompt();
794 /// This stack must have been canonicalized already!
795 fn format_path_stack(stack
: &[PathStackEntry
]) -> OsString
{
796 if stack
.len() <= 1 {
797 return OsString
::from("/");
800 let mut out
= OsString
::new();
801 for c
in stack
.iter().skip(1) {
803 out
.push(OsStr
::from_bytes(&c
.catalog
.name
));
809 async
fn select(&mut self, path
: PathBuf
) -> Result
<(), Error
> {
810 let stack
= Self::lookup(
819 let path
= Self::format_path_stack(&stack
);
820 let entry
= MatchEntry
::include(MatchPattern
::Literal(path
.as_bytes().to_vec()));
821 if self.selected
.insert(path
.clone(), entry
).is_some() {
822 println
!("path already selected: {:?}", path
);
824 println
!("added path: {:?}", path
);
830 async
fn deselect(&mut self, path
: PathBuf
) -> Result
<(), Error
> {
831 let stack
= Self::lookup(
840 let path
= Self::format_path_stack(&stack
);
842 if self.selected
.remove(&path
).is_some() {
843 println
!("removed path from selection: {:?}", path
);
845 println
!("path not selected: {:?}", path
);
851 async
fn deselect_all(&mut self) -> Result
<(), Error
> {
852 self.selected
.clear();
853 println
!("cleared selection");
857 async
fn list_selected(&mut self, patterns
: bool
) -> Result
<(), Error
> {
859 self.list_selected_patterns().await
861 self.list_matching_files().await
865 async
fn list_selected_patterns(&self) -> Result
<(), Error
> {
866 for entry
in self.selected
.keys() {
867 println
!("{:?}", entry
);
872 fn build_match_list(&self) -> Vec
<MatchEntry
> {
873 let mut list
= Vec
::with_capacity(self.selected
.len());
874 for entry
in self.selected
.values() {
875 list
.push(entry
.clone());
880 async
fn list_matching_files(&mut self) -> Result
<(), Error
> {
881 let matches
= self.build_match_list();
884 &self.position
[0].catalog
,
887 &mut |path
: &[u8]| -> Result
<(), Error
> {
888 let mut out
= std
::io
::stdout();
889 out
.write_all(path
)?
;
890 out
.write_all(b
"\n")?
;
898 async
fn find(&mut self, pattern
: String
, select
: bool
) -> Result
<(), Error
> {
899 let pattern_os
= OsString
::from(pattern
.clone());
901 MatchEntry
::parse_pattern(pattern
, PatternFlag
::PATH_NAME
, MatchType
::Include
)?
;
903 let mut found_some
= false;
905 &self.position
[0].catalog
,
908 &mut |path
: &[u8]| -> Result
<(), Error
> {
910 let mut out
= std
::io
::stdout();
911 out
.write_all(path
)?
;
912 out
.write_all(b
"\n")?
;
917 if found_some
&& select
{
918 self.selected
.insert(pattern_os
, pattern_entry
);
924 async
fn restore_selected(&mut self, destination
: PathBuf
) -> Result
<(), Error
> {
925 if self.selected
.is_empty() {
926 bail
!("no entries selected");
929 let match_list
= self.build_match_list();
931 self.restore_with_match_list(destination
, &match_list
).await
936 destination
: PathBuf
,
937 pattern
: Option
<String
>,
938 ) -> Result
<(), Error
> {
940 let match_list
: &[MatchEntry
] = match pattern
{
943 tmp
= [MatchEntry
::parse_pattern(
945 PatternFlag
::PATH_NAME
,
952 self.restore_with_match_list(destination
, match_list
).await
955 async
fn restore_with_match_list(
957 destination
: PathBuf
,
958 match_list
: &[MatchEntry
],
959 ) -> Result
<(), Error
> {
963 Some(CreateOptions
::new().perm(Mode
::from_bits_truncate(0o700))),
965 .map_err(|err
| format_err
!("error creating directory {:?}: {}", destination
, err
))?
;
967 let rootdir
= Dir
::open(
969 OFlag
::O_DIRECTORY
| OFlag
::O_CLOEXEC
,
973 format_err
!("unable to open target directory {:?}: {}", destination
, err
,)
976 let mut dir_stack
= self.new_path_stack();
977 Self::walk_pxar_archive(&self.accessor
, &mut dir_stack
).await?
;
978 let root_meta
= dir_stack
988 let extractor
= crate::pxar
::extract
::Extractor
::new(rootdir
, root_meta
, true, Flags
::DEFAULT
);
990 let mut extractor
= ExtractorState
::new(
998 extractor
.extract().await
1002 struct ExtractorState
<'a
> {
1005 path_len_stack
: Vec
<usize>,
1007 dir_stack
: Vec
<PathStackEntry
>,
1010 matches_stack
: Vec
<bool
>,
1012 read_dir
: <Vec
<catalog
::DirEntry
> as IntoIterator
>::IntoIter
,
1013 read_dir_stack
: Vec
<<Vec
<catalog
::DirEntry
> as IntoIterator
>::IntoIter
>,
1015 extractor
: crate::pxar
::extract
::Extractor
,
1017 catalog
: &'a
mut CatalogReader
,
1018 match_list
: &'a
[MatchEntry
],
1019 accessor
: &'a Accessor
,
1022 impl<'a
> ExtractorState
<'a
> {
1024 catalog
: &'a
mut CatalogReader
,
1025 dir_stack
: Vec
<PathStackEntry
>,
1026 extractor
: crate::pxar
::extract
::Extractor
,
1027 match_list
: &'a
[MatchEntry
],
1028 accessor
: &'a Accessor
,
1029 ) -> Result
<Self, Error
> {
1030 let read_dir
= catalog
1031 .read_dir(&dir_stack
.last().unwrap().catalog
)?
1036 path_len_stack
: Vec
::new(),
1040 matches
: match_list
.is_empty(),
1041 matches_stack
: Vec
::new(),
1044 read_dir_stack
: Vec
::new(),
1054 pub async
fn extract(&mut self) -> Result
<(), Error
> {
1056 let entry
= match self.read_dir
.next() {
1057 Some(entry
) => entry
,
1058 None
=> match self.handle_end_of_directory()?
{
1059 ControlFlow
::Break(()) => break, // done with root directory
1060 ControlFlow
::Continue(()) => continue,
1064 self.path
.truncate(self.path_len
);
1065 if !entry
.name
.starts_with(b
"/") {
1066 self.path
.reserve(entry
.name
.len() + 1);
1067 self.path
.push(b'
/'
);
1069 self.path
.extend(&entry
.name
);
1071 self.extractor
.set_path(OsString
::from_vec(self.path
.clone()));
1072 self.handle_entry(entry
).await?
;
1078 fn handle_end_of_directory(&mut self) -> Result
<ControlFlow
<()>, Error
> {
1079 // go up a directory:
1080 self.read_dir
= match self.read_dir_stack
.pop() {
1082 None
=> return Ok(ControlFlow
::Break(())), // out of root directory
1088 .ok_or_else(|| format_err
!("internal iterator error (matches_stack)"))?
;
1092 .ok_or_else(|| format_err
!("internal iterator error (dir_stack)"))?
;
1094 self.path_len
= self
1097 .ok_or_else(|| format_err
!("internal iterator error (path_len_stack)"))?
;
1099 self.extractor
.leave_directory()?
;
1101 Ok(ControlFlow
::CONTINUE
)
1104 async
fn handle_new_directory(
1106 entry
: catalog
::DirEntry
,
1107 match_result
: Option
<MatchType
>,
1108 ) -> Result
<(), Error
> {
1109 // enter a new directory:
1110 self.read_dir_stack
.push(mem
::replace(
1112 self.catalog
.read_dir(&entry
)?
.into_iter(),
1114 self.matches_stack
.push(self.matches
);
1115 self.dir_stack
.push(PathStackEntry
::new(entry
));
1116 self.path_len_stack
.push(self.path_len
);
1117 self.path_len
= self.path
.len();
1119 Shell
::walk_pxar_archive(&self.accessor
, &mut self.dir_stack
).await?
;
1120 let dir_pxar
= self.dir_stack
.last().unwrap().pxar
.as_ref().unwrap();
1121 let dir_meta
= dir_pxar
.entry().metadata().clone();
1122 let create
= self.matches
&& match_result
!= Some(MatchType
::Exclude
);
1123 self.extractor
.enter_directory(dir_pxar
.file_name().to_os_string(), dir_meta
, create
)?
;
1128 pub async
fn handle_entry(&mut self, entry
: catalog
::DirEntry
) -> Result
<(), Error
> {
1129 let match_result
= self.match_list
.matches(&self.path
, entry
.get_file_mode());
1130 let did_match
= match match_result
{
1131 Some(MatchType
::Include
) => true,
1132 Some(MatchType
::Exclude
) => false,
1133 None
=> self.matches
,
1136 match (did_match
, &entry
.attr
) {
1137 (_
, DirEntryAttribute
::Directory { .. }
) => {
1138 self.handle_new_directory(entry
, match_result
).await?
;
1140 (true, DirEntryAttribute
::File { .. }
) => {
1141 self.dir_stack
.push(PathStackEntry
::new(entry
));
1142 let file
= Shell
::walk_pxar_archive(&self.accessor
, &mut self.dir_stack
).await?
;
1143 self.extract_file(file
).await?
;
1144 self.dir_stack
.pop();
1146 (true, DirEntryAttribute
::Symlink
)
1147 | (true, DirEntryAttribute
::BlockDevice
)
1148 | (true, DirEntryAttribute
::CharDevice
)
1149 | (true, DirEntryAttribute
::Fifo
)
1150 | (true, DirEntryAttribute
::Socket
)
1151 | (true, DirEntryAttribute
::Hardlink
) => {
1152 let attr
= entry
.attr
.clone();
1153 self.dir_stack
.push(PathStackEntry
::new(entry
));
1154 let file
= Shell
::walk_pxar_archive(&self.accessor
, &mut self.dir_stack
).await?
;
1155 self.extract_special(file
, attr
).await?
;
1156 self.dir_stack
.pop();
1158 (false, _
) => (), // skip
1164 fn path(&self) -> &OsStr
{
1165 OsStr
::from_bytes(&self.path
)
1168 async
fn extract_file(&mut self, entry
: FileEntry
) -> Result
<(), Error
> {
1169 match entry
.kind() {
1170 pxar
::EntryKind
::File { size, .. }
=> {
1171 let file_name
= CString
::new(entry
.file_name().as_bytes())?
;
1172 let mut contents
= entry
.contents().await?
;
1173 self.extractor
.async_extract_file(
1183 "catalog file {:?} not a regular file in the archive",
1190 async
fn extract_special(
1193 catalog_attr
: DirEntryAttribute
,
1194 ) -> Result
<(), Error
> {
1195 let file_name
= CString
::new(entry
.file_name().as_bytes())?
;
1196 match (catalog_attr
, entry
.kind()) {
1197 (DirEntryAttribute
::Symlink
, pxar
::EntryKind
::Symlink(symlink
)) => {
1198 block_in_place(|| self.extractor
.extract_symlink(
1201 symlink
.as_os_str(),
1204 (DirEntryAttribute
::Symlink
, _
) => {
1206 "catalog symlink {:?} not a symlink in the archive",
1211 (DirEntryAttribute
::Hardlink
, pxar
::EntryKind
::Hardlink(hardlink
)) => {
1212 block_in_place(|| self.extractor
.extract_hardlink(&file_name
, hardlink
.as_os_str()))
1214 (DirEntryAttribute
::Hardlink
, _
) => {
1216 "catalog hardlink {:?} not a hardlink in the archive",
1221 (ref attr
, pxar
::EntryKind
::Device(device
)) => {
1222 self.extract_device(attr
.clone(), &file_name
, device
, entry
.metadata())
1225 (DirEntryAttribute
::Fifo
, pxar
::EntryKind
::Fifo
) => {
1226 block_in_place(|| self.extractor
.extract_special(&file_name
, entry
.metadata(), 0))
1228 (DirEntryAttribute
::Fifo
, _
) => {
1229 bail
!("catalog fifo {:?} not a fifo in the archive", self.path());
1232 (DirEntryAttribute
::Socket
, pxar
::EntryKind
::Socket
) => {
1233 block_in_place(|| self.extractor
.extract_special(&file_name
, entry
.metadata(), 0))
1235 (DirEntryAttribute
::Socket
, _
) => {
1237 "catalog socket {:?} not a socket in the archive",
1242 attr
=> bail
!("unhandled file type {:?} for {:?}", attr
, self.path()),
1248 attr
: DirEntryAttribute
,
1250 device
: &pxar
::format
::Device
,
1251 metadata
: &Metadata
,
1252 ) -> Result
<(), Error
> {
1254 DirEntryAttribute
::BlockDevice
=> {
1255 if !metadata
.stat
.is_blockdev() {
1257 "catalog block device {:?} is not a block device in the archive",
1262 DirEntryAttribute
::CharDevice
=> {
1263 if !metadata
.stat
.is_chardev() {
1265 "catalog character device {:?} is not a character device in the archive",
1272 "unexpected file type for {:?} in the catalog, \
1273 which is a device special file in the archive",
1278 block_in_place(|| self.extractor
.extract_special(file_name
, metadata
, device
.to_dev_t()))