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 pbs_runtime
::block_in_place
;
22 use pbs_datastore
::catalog
::{self, DirEntryAttribute}
;
23 use pbs_tools
::ops
::ControlFlow
;
25 use crate::pxar
::Flags
;
26 use crate::pxar
::fuse
::{Accessor, FileEntry}
;
28 type CatalogReader
= pbs_datastore
::catalog
::CatalogReader
<std
::fs
::File
>;
30 const MAX_SYMLINK_COUNT
: usize = 40;
32 static mut SHELL
: Option
<usize> = None
;
34 /// This list defines all the shell commands and their properties
35 /// using the api schema
36 pub fn catalog_shell_cli() -> CommandLineInterface
{
37 CommandLineInterface
::Nested(
39 .insert("pwd", CliCommand
::new(&API_METHOD_PWD_COMMAND
))
42 CliCommand
::new(&API_METHOD_CD_COMMAND
)
44 .completion_cb("path", complete_path
),
48 CliCommand
::new(&API_METHOD_LS_COMMAND
)
50 .completion_cb("path", complete_path
),
54 CliCommand
::new(&API_METHOD_STAT_COMMAND
)
56 .completion_cb("path", complete_path
),
60 CliCommand
::new(&API_METHOD_SELECT_COMMAND
)
62 .completion_cb("path", complete_path
),
66 CliCommand
::new(&API_METHOD_DESELECT_COMMAND
)
68 .completion_cb("path", complete_path
),
72 CliCommand
::new(&API_METHOD_CLEAR_SELECTED_COMMAND
),
76 CliCommand
::new(&API_METHOD_LIST_SELECTED_COMMAND
),
80 CliCommand
::new(&API_METHOD_RESTORE_SELECTED_COMMAND
)
81 .arg_param(&["target"])
82 .completion_cb("target", pbs_tools
::fs
::complete_file_name
),
86 CliCommand
::new(&API_METHOD_RESTORE_COMMAND
)
87 .arg_param(&["target"])
88 .completion_cb("target", pbs_tools
::fs
::complete_file_name
),
92 CliCommand
::new(&API_METHOD_FIND_COMMAND
).arg_param(&["pattern"]),
96 CliCommand
::new(&API_METHOD_EXIT
),
102 fn complete_path(complete_me
: &str, _map
: &HashMap
<String
, String
>) -> Vec
<String
> {
103 let shell
: &mut Shell
= unsafe { std::mem::transmute(SHELL.unwrap()) }
;
104 match shell
.complete_path(complete_me
) {
107 eprintln
!("error during completion: {}", err
);
113 // just an empty wrapper so that it is displayed in help/docs, we check
114 // in the readloop for 'exit' again break
115 #[api(input: { properties: {} })]
117 async
fn exit() -> Result
<(), Error
> {
121 #[api(input: { properties: {} })]
122 /// List the current working directory.
123 async
fn pwd_command() -> Result
<(), Error
> {
124 Shell
::with(move |shell
| shell
.pwd()).await
133 description
: "target path."
138 /// Change the current working directory to the new directory
139 async
fn cd_command(path
: Option
<String
>) -> Result
<(), Error
> {
140 let path
= path
.as_ref().map(Path
::new
);
141 Shell
::with(move |shell
| shell
.cd(path
)).await
150 description
: "target path."
155 /// List the content of working directory or given path.
156 async
fn ls_command(path
: Option
<String
>) -> Result
<(), Error
> {
157 let path
= path
.as_ref().map(Path
::new
);
158 Shell
::with(move |shell
| shell
.ls(path
)).await
166 description
: "target path."
171 /// Read the metadata for a given directory entry.
173 /// This is expensive because the data has to be read from the pxar archive, which means reading
174 /// over the network.
175 async
fn stat_command(path
: String
) -> Result
<(), Error
> {
176 Shell
::with(move |shell
| shell
.stat(PathBuf
::from(path
))).await
184 description
: "target path."
189 /// Select an entry for restore.
191 /// This will return an error if the entry is already present in the list or
192 /// if an invalid path was provided.
193 async
fn select_command(path
: String
) -> Result
<(), Error
> {
194 Shell
::with(move |shell
| shell
.select(PathBuf
::from(path
))).await
202 description
: "path to entry to remove from list."
207 /// Deselect an entry for restore.
209 /// This will return an error if the entry was not found in the list of entries
210 /// selected for restore.
211 async
fn deselect_command(path
: String
) -> Result
<(), Error
> {
212 Shell
::with(move |shell
| shell
.deselect(PathBuf
::from(path
))).await
215 #[api( input: { properties: { } })]
216 /// Clear the list of files selected for restore.
217 async
fn clear_selected_command() -> Result
<(), Error
> {
218 Shell
::with(move |shell
| shell
.deselect_all()).await
226 description
: "List match patterns instead of the matching files.",
233 /// List entries currently selected for restore.
234 async
fn list_selected_command(patterns
: bool
) -> Result
<(), Error
> {
235 Shell
::with(move |shell
| shell
.list_selected(patterns
)).await
243 description
: "Match pattern for matching files in the catalog."
249 description
: "Add matching filenames to list for restore."
254 /// Find entries in the catalog matching the given match pattern.
255 async
fn find_command(pattern
: String
, select
: bool
) -> Result
<(), Error
> {
256 Shell
::with(move |shell
| shell
.find(pattern
, select
)).await
264 description
: "target path for restore on local filesystem."
269 /// Restore the selected entries to the given target path.
271 /// Target must not exist on the clients filesystem.
272 async
fn restore_selected_command(target
: String
) -> Result
<(), Error
> {
273 Shell
::with(move |shell
| shell
.restore_selected(PathBuf
::from(target
))).await
281 description
: "target path for restore on local filesystem."
286 description
: "match pattern to limit files for restore."
291 /// Restore the sub-archive given by the current working directory to target.
293 /// By further providing a pattern, the restore can be limited to a narrower
294 /// subset of this sub-archive.
295 /// If pattern is not present or empty, the full archive is restored to target.
296 async
fn restore_command(target
: String
, pattern
: Option
<String
>) -> Result
<(), Error
> {
297 Shell
::with(move |shell
| shell
.restore(PathBuf
::from(target
), pattern
)).await
300 /// TODO: Should we use this to fix `step()`? Make path resolution behave more like described in
301 /// the path_resolution(7) man page.
303 /// The `Path` type's component iterator does not tell us anything about trailing slashes or
304 /// trailing `Component::CurDir` entries. Since we only support regular paths we'll roll our own
306 enum PathComponent
<'a
> {
314 struct PathComponentIter
<'a
> {
316 state
: u8, // 0=beginning, 1=ongoing, 2=trailing, 3=finished (fused)
319 impl std
::iter
::FusedIterator
for PathComponentIter
<'_
> {}
321 impl<'a
> Iterator
for PathComponentIter
<'a
> {
322 type Item
= PathComponent
<'a
>;
324 fn next(&mut self) -> Option
<Self::Item
> {
325 if self.path
.is_empty() {
331 if self.path
[0] == b'
/'
{
333 self.path
= &self.path
[1..];
334 return Some(PathComponent
::Root
);
339 let had_slashes
= self.path
[0] == b'
/'
;
340 while self.path
.get(0).copied() == Some(b'
/'
) {
341 self.path
= &self.path
[1..];
344 Some(match self.path
{
345 [] if had_slashes
=> PathComponent
::TrailingSlash
,
347 [b'
.'
] | [b'
.'
, b'
/'
, ..] => {
348 self.path
= &self.path
[1..];
349 PathComponent
::CurDir
351 [b'
.'
, b'
.'
] | [b'
.'
, b'
.'
, b'
/'
, ..] => {
352 self.path
= &self.path
[2..];
353 PathComponent
::ParentDir
359 .position(|&b
| b
== b'
/'
)
360 .unwrap_or(self.path
.len());
361 let (out
, rest
) = self.path
.split_at(end
);
363 PathComponent
::Normal(OsStr
::from_bytes(out
))
370 /// Readline instance handling input and callbacks
371 rl
: rustyline
::Editor
<CliHelper
>,
373 /// Interactive prompt.
376 /// Calalog reader instance to navigate
377 catalog
: CatalogReader
,
379 /// List of selected paths for restore
380 selected
: HashMap
<OsString
, MatchEntry
>,
382 /// pxar accessor instance for the current pxar archive
385 /// The current position in the archive.
386 position
: Vec
<PathStackEntry
>,
390 struct PathStackEntry
{
391 /// This is always available. We mainly navigate through the catalog.
392 catalog
: catalog
::DirEntry
,
394 /// Whenever we need something from the actual archive we fill this out. This is cached along
396 pxar
: Option
<FileEntry
>,
399 impl PathStackEntry
{
400 fn new(dir_entry
: catalog
::DirEntry
) -> Self {
409 /// Create a new shell for the given catalog and pxar archive.
411 mut catalog
: CatalogReader
,
414 ) -> Result
<Self, Error
> {
415 let cli_helper
= CliHelper
::new(catalog_shell_cli());
416 let mut rl
= rustyline
::Editor
::<CliHelper
>::new();
417 rl
.set_helper(Some(cli_helper
));
419 let catalog_root
= catalog
.root()?
;
420 let archive_root
= catalog
421 .lookup(&catalog_root
, archive_name
.as_bytes())?
422 .ok_or_else(|| format_err
!("archive not found in catalog"))?
;
423 let position
= vec
![PathStackEntry
::new(archive_root
)];
425 let mut this
= Self {
427 prompt
: String
::new(),
429 selected
: HashMap
::new(),
433 this
.update_prompt();
437 async
fn with
<'a
, Fut
, R
, F
>(call
: F
) -> Result
<R
, Error
>
439 F
: FnOnce(&'a
mut Shell
) -> Fut
,
440 Fut
: Future
<Output
= Result
<R
, Error
>>,
445 let shell
: &mut Shell
= unsafe { std::mem::transmute(SHELL.unwrap()) }
;
446 call(&mut *shell
).await
449 pub async
fn shell(mut self) -> Result
<(), Error
> {
450 let this
= &mut self;
452 SHELL
= Some(this
as *mut Shell
as usize);
454 while let Ok(line
) = this
.rl
.readline(&this
.prompt
) {
458 let helper
= this
.rl
.helper().unwrap();
459 let args
= match cli
::shellword_split(&line
) {
462 println
!("Error: {}", err
);
468 cli
::handle_command_future(helper
.cmd_def(), "", args
, cli
::CliEnvironment
::new())
470 this
.rl
.add_history_entry(line
);
471 this
.update_prompt();
476 fn update_prompt(&mut self) {
477 self.prompt
= "pxar:".to_string();
478 if self.position
.len() <= 1 {
479 self.prompt
.push('
/'
);
481 for p
in self.position
.iter().skip(1) {
482 if !p
.catalog
.name
.starts_with(b
"/") {
483 self.prompt
.push('
/'
);
485 match std
::str::from_utf8(&p
.catalog
.name
) {
486 Ok(entry
) => self.prompt
.push_str(entry
),
487 Err(_
) => self.prompt
.push_str("<non-utf8-dir>"),
491 self.prompt
.push_str(" > ");
494 async
fn pwd(&mut self) -> Result
<(), Error
> {
495 let stack
= Self::lookup(
503 let path
= Self::format_path_stack(&stack
);
504 println
!("{:?}", path
);
508 fn new_path_stack(&self) -> Vec
<PathStackEntry
> {
509 self.position
[..1].to_vec()
512 async
fn resolve_symlink(
513 stack
: &mut Vec
<PathStackEntry
>,
514 catalog
: &mut CatalogReader
,
516 follow_symlinks
: &mut Option
<usize>,
517 ) -> Result
<(), Error
> {
518 if let Some(ref mut symlink_count
) = follow_symlinks
{
520 if *symlink_count
> MAX_SYMLINK_COUNT
{
521 bail
!("too many levels of symbolic links");
524 let file
= Self::walk_pxar_archive(accessor
, &mut stack
[..]).await?
;
526 let path
= match file
.entry().kind() {
527 EntryKind
::Symlink(symlink
) => Path
::new(symlink
.as_os_str()),
528 _
=> bail
!("symlink in the catalog was not a symlink in the archive"),
532 Self::lookup(&stack
, &mut *catalog
, accessor
, Some(path
), follow_symlinks
).await?
;
538 bail
!("target is a symlink");
542 /// Walk a path and add it to the path stack.
544 /// If the symlink count is used, symlinks will be followed, until we hit the cap and error
547 stack
: &mut Vec
<PathStackEntry
>,
548 catalog
: &mut CatalogReader
,
550 component
: std
::path
::Component
<'_
>,
551 follow_symlinks
: &mut Option
<usize>,
552 ) -> Result
<(), Error
> {
553 use std
::path
::Component
;
555 Component
::Prefix(_
) => bail
!("invalid path component (prefix)"),
556 Component
::RootDir
=> stack
.truncate(1),
557 Component
::CurDir
=> {
558 if stack
.last().unwrap().catalog
.is_symlink() {
559 Self::resolve_symlink(stack
, catalog
, accessor
, follow_symlinks
).await?
;
562 Component
::ParentDir
=> drop(stack
.pop()),
563 Component
::Normal(entry
) => {
564 if stack
.last().unwrap().catalog
.is_symlink() {
565 Self::resolve_symlink(stack
, catalog
, accessor
, follow_symlinks
).await?
;
567 match catalog
.lookup(&stack
.last().unwrap().catalog
, entry
.as_bytes())?
{
568 Some(dir
) => stack
.push(PathStackEntry
::new(dir
)),
569 None
=> bail
!("no such file or directory: {:?}", entry
),
578 stack
: &mut Vec
<PathStackEntry
>,
579 catalog
: &mut CatalogReader
,
580 component
: std
::path
::Component
<'_
>,
581 ) -> Result
<(), Error
> {
582 use std
::path
::Component
;
584 Component
::Prefix(_
) => bail
!("invalid path component (prefix)"),
585 Component
::RootDir
=> stack
.truncate(1),
586 Component
::CurDir
=> {
587 if stack
.last().unwrap().catalog
.is_symlink() {
588 bail
!("target is a symlink");
591 Component
::ParentDir
=> drop(stack
.pop()),
592 Component
::Normal(entry
) => {
593 if stack
.last().unwrap().catalog
.is_symlink() {
594 bail
!("target is a symlink");
596 match catalog
.lookup(&stack
.last().unwrap().catalog
, entry
.as_bytes())?
{
597 Some(dir
) => stack
.push(PathStackEntry
::new(dir
)),
598 None
=> bail
!("no such file or directory: {:?}", entry
),
606 /// The pxar accessor is required to resolve symbolic links
607 async
fn walk_catalog(
608 stack
: &mut Vec
<PathStackEntry
>,
609 catalog
: &mut CatalogReader
,
612 follow_symlinks
: &mut Option
<usize>,
613 ) -> Result
<(), Error
> {
614 for c
in path
.components() {
615 Self::step(stack
, catalog
, accessor
, c
, follow_symlinks
).await?
;
620 /// Non-async version cannot follow symlinks.
621 fn walk_catalog_nofollow(
622 stack
: &mut Vec
<PathStackEntry
>,
623 catalog
: &mut CatalogReader
,
625 ) -> Result
<(), Error
> {
626 for c
in path
.components() {
627 Self::step_nofollow(stack
, catalog
, c
)?
;
632 /// This assumes that there are no more symlinks in the path stack.
633 async
fn walk_pxar_archive(
635 mut stack
: &mut [PathStackEntry
],
636 ) -> Result
<FileEntry
, Error
> {
637 if stack
[0].pxar
.is_none() {
638 stack
[0].pxar
= Some(accessor
.open_root().await?
.lookup_self().await?
);
641 // Now walk the directory stack:
643 while at
< stack
.len() {
644 if stack
[at
].pxar
.is_some() {
649 let parent
= stack
[at
- 1].pxar
.as_ref().unwrap();
650 let dir
= parent
.enter_directory().await?
;
651 let name
= Path
::new(OsStr
::from_bytes(&stack
[at
].catalog
.name
));
652 stack
[at
].pxar
= Some(
655 .ok_or_else(|| format_err
!("no such entry in pxar file: {:?}", name
))?
,
661 Ok(stack
.last().unwrap().pxar
.clone().unwrap())
664 fn complete_path(&mut self, input
: &str) -> Result
<Vec
<String
>, Error
> {
666 let (parent
, base
, part
) = match input
.rfind('
/'
) {
668 let (base
, part
) = input
.split_at(ind
+ 1);
669 let path
= PathBuf
::from(base
);
670 if path
.is_absolute() {
671 tmp_stack
= self.new_path_stack();
673 tmp_stack
= self.position
.clone();
675 Self::walk_catalog_nofollow(&mut tmp_stack
, &mut self.catalog
, &path
)?
;
676 (&tmp_stack
.last().unwrap().catalog
, base
, part
)
678 None
=> (&self.position
.last().unwrap().catalog
, "", input
),
681 let entries
= self.catalog
.read_dir(parent
)?
;
683 let mut out
= Vec
::new();
684 for entry
in entries
{
685 let mut name
= base
.to_string();
686 if entry
.name
.starts_with(part
.as_bytes()) {
687 name
.push_str(std
::str::from_utf8(&entry
.name
)?
);
688 if entry
.is_directory() {
698 // Break async recursion here: lookup -> walk_catalog -> step -> lookup
699 fn lookup
<'future
, 's
, 'c
, 'a
, 'p
, 'y
>(
700 stack
: &'s
[PathStackEntry
],
701 catalog
: &'c
mut CatalogReader
,
702 accessor
: &'a Accessor
,
703 path
: Option
<&'p Path
>,
704 follow_symlinks
: &'y
mut Option
<usize>,
705 ) -> Pin
<Box
<dyn Future
<Output
= Result
<Vec
<PathStackEntry
>, Error
>> + Send
+ 'future
>>
713 Box
::pin(async
move {
715 None
=> stack
.to_vec(),
717 let mut stack
= if path
.is_absolute() {
722 Self::walk_catalog(&mut stack
, catalog
, accessor
, path
, follow_symlinks
)
730 async
fn ls(&mut self, path
: Option
<&Path
>) -> Result
<(), Error
> {
731 let stack
= Self::lookup(
740 let last
= stack
.last().unwrap();
741 if last
.catalog
.is_directory() {
742 let items
= self.catalog
.read_dir(&stack
.last().unwrap().catalog
)?
;
743 let mut out
= std
::io
::stdout();
746 out
.write_all(&item
.name
)?
;
747 out
.write_all(b
"\n")?
;
750 let mut out
= std
::io
::stdout();
751 out
.write_all(&last
.catalog
.name
)?
;
752 out
.write_all(b
"\n")?
;
757 async
fn stat(&mut self, path
: PathBuf
) -> Result
<(), Error
> {
758 let mut stack
= Self::lookup(
767 let file
= Self::walk_pxar_archive(&self.accessor
, &mut stack
).await?
;
769 .write_all(crate::pxar
::format_multi_line_entry(file
.entry()).as_bytes())?
;
773 async
fn cd(&mut self, path
: Option
<&Path
>) -> Result
<(), Error
> {
776 let new_position
= Self::lookup(
784 if !new_position
.last().unwrap().catalog
.is_directory() {
785 bail
!("not a directory");
787 self.position
= new_position
;
789 None
=> self.position
.truncate(1),
791 self.update_prompt();
795 /// This stack must have been canonicalized already!
796 fn format_path_stack(stack
: &[PathStackEntry
]) -> OsString
{
797 if stack
.len() <= 1 {
798 return OsString
::from("/");
801 let mut out
= OsString
::new();
802 for c
in stack
.iter().skip(1) {
804 out
.push(OsStr
::from_bytes(&c
.catalog
.name
));
810 async
fn select(&mut self, path
: PathBuf
) -> Result
<(), Error
> {
811 let stack
= Self::lookup(
820 let path
= Self::format_path_stack(&stack
);
821 let entry
= MatchEntry
::include(MatchPattern
::Literal(path
.as_bytes().to_vec()));
822 if self.selected
.insert(path
.clone(), entry
).is_some() {
823 println
!("path already selected: {:?}", path
);
825 println
!("added path: {:?}", path
);
831 async
fn deselect(&mut self, path
: PathBuf
) -> Result
<(), Error
> {
832 let stack
= Self::lookup(
841 let path
= Self::format_path_stack(&stack
);
843 if self.selected
.remove(&path
).is_some() {
844 println
!("removed path from selection: {:?}", path
);
846 println
!("path not selected: {:?}", path
);
852 async
fn deselect_all(&mut self) -> Result
<(), Error
> {
853 self.selected
.clear();
854 println
!("cleared selection");
858 async
fn list_selected(&mut self, patterns
: bool
) -> Result
<(), Error
> {
860 self.list_selected_patterns().await
862 self.list_matching_files().await
866 async
fn list_selected_patterns(&self) -> Result
<(), Error
> {
867 for entry
in self.selected
.keys() {
868 println
!("{:?}", entry
);
873 fn build_match_list(&self) -> Vec
<MatchEntry
> {
874 let mut list
= Vec
::with_capacity(self.selected
.len());
875 for entry
in self.selected
.values() {
876 list
.push(entry
.clone());
881 async
fn list_matching_files(&mut self) -> Result
<(), Error
> {
882 let matches
= self.build_match_list();
885 &self.position
[0].catalog
,
888 &mut |path
: &[u8]| -> Result
<(), Error
> {
889 let mut out
= std
::io
::stdout();
890 out
.write_all(path
)?
;
891 out
.write_all(b
"\n")?
;
899 async
fn find(&mut self, pattern
: String
, select
: bool
) -> Result
<(), Error
> {
900 let pattern_os
= OsString
::from(pattern
.clone());
902 MatchEntry
::parse_pattern(pattern
, PatternFlag
::PATH_NAME
, MatchType
::Include
)?
;
904 let mut found_some
= false;
906 &self.position
[0].catalog
,
909 &mut |path
: &[u8]| -> Result
<(), Error
> {
911 let mut out
= std
::io
::stdout();
912 out
.write_all(path
)?
;
913 out
.write_all(b
"\n")?
;
918 if found_some
&& select
{
919 self.selected
.insert(pattern_os
, pattern_entry
);
925 async
fn restore_selected(&mut self, destination
: PathBuf
) -> Result
<(), Error
> {
926 if self.selected
.is_empty() {
927 bail
!("no entries selected");
930 let match_list
= self.build_match_list();
932 self.restore_with_match_list(destination
, &match_list
).await
937 destination
: PathBuf
,
938 pattern
: Option
<String
>,
939 ) -> Result
<(), Error
> {
941 let match_list
: &[MatchEntry
] = match pattern
{
944 tmp
= [MatchEntry
::parse_pattern(
946 PatternFlag
::PATH_NAME
,
953 self.restore_with_match_list(destination
, match_list
).await
956 async
fn restore_with_match_list(
958 destination
: PathBuf
,
959 match_list
: &[MatchEntry
],
960 ) -> Result
<(), Error
> {
964 Some(CreateOptions
::new().perm(Mode
::from_bits_truncate(0o700))),
966 .map_err(|err
| format_err
!("error creating directory {:?}: {}", destination
, err
))?
;
968 let rootdir
= Dir
::open(
970 OFlag
::O_DIRECTORY
| OFlag
::O_CLOEXEC
,
974 format_err
!("unable to open target directory {:?}: {}", destination
, err
,)
977 let mut dir_stack
= self.new_path_stack();
978 Self::walk_pxar_archive(&self.accessor
, &mut dir_stack
).await?
;
979 let root_meta
= dir_stack
990 crate::pxar
::extract
::Extractor
::new(rootdir
, root_meta
, true, Flags
::DEFAULT
);
992 let mut extractor
= ExtractorState
::new(
1000 extractor
.extract().await
1004 struct ExtractorState
<'a
> {
1007 path_len_stack
: Vec
<usize>,
1009 dir_stack
: Vec
<PathStackEntry
>,
1012 matches_stack
: Vec
<bool
>,
1014 read_dir
: <Vec
<catalog
::DirEntry
> as IntoIterator
>::IntoIter
,
1015 read_dir_stack
: Vec
<<Vec
<catalog
::DirEntry
> as IntoIterator
>::IntoIter
>,
1017 extractor
: crate::pxar
::extract
::Extractor
,
1019 catalog
: &'a
mut CatalogReader
,
1020 match_list
: &'a
[MatchEntry
],
1021 accessor
: &'a Accessor
,
1024 impl<'a
> ExtractorState
<'a
> {
1026 catalog
: &'a
mut CatalogReader
,
1027 dir_stack
: Vec
<PathStackEntry
>,
1028 extractor
: crate::pxar
::extract
::Extractor
,
1029 match_list
: &'a
[MatchEntry
],
1030 accessor
: &'a Accessor
,
1031 ) -> Result
<Self, Error
> {
1032 let read_dir
= catalog
1033 .read_dir(&dir_stack
.last().unwrap().catalog
)?
1038 path_len_stack
: Vec
::new(),
1042 matches
: match_list
.is_empty(),
1043 matches_stack
: Vec
::new(),
1046 read_dir_stack
: Vec
::new(),
1056 pub async
fn extract(&mut self) -> Result
<(), Error
> {
1058 let entry
= match self.read_dir
.next() {
1059 Some(entry
) => entry
,
1060 None
=> match self.handle_end_of_directory()?
{
1061 ControlFlow
::Break(()) => break, // done with root directory
1062 ControlFlow
::Continue(()) => continue,
1066 self.path
.truncate(self.path_len
);
1067 if !entry
.name
.starts_with(b
"/") {
1068 self.path
.reserve(entry
.name
.len() + 1);
1069 self.path
.push(b'
/'
);
1071 self.path
.extend(&entry
.name
);
1073 self.extractor
.set_path(OsString
::from_vec(self.path
.clone()));
1074 self.handle_entry(entry
).await?
;
1080 fn handle_end_of_directory(&mut self) -> Result
<ControlFlow
<()>, Error
> {
1081 // go up a directory:
1082 self.read_dir
= match self.read_dir_stack
.pop() {
1084 None
=> return Ok(ControlFlow
::Break(())), // out of root directory
1090 .ok_or_else(|| format_err
!("internal iterator error (matches_stack)"))?
;
1094 .ok_or_else(|| format_err
!("internal iterator error (dir_stack)"))?
;
1096 self.path_len
= self
1099 .ok_or_else(|| format_err
!("internal iterator error (path_len_stack)"))?
;
1101 self.extractor
.leave_directory()?
;
1103 Ok(ControlFlow
::CONTINUE
)
1106 async
fn handle_new_directory(
1108 entry
: catalog
::DirEntry
,
1109 match_result
: Option
<MatchType
>,
1110 ) -> Result
<(), Error
> {
1111 // enter a new directory:
1112 self.read_dir_stack
.push(mem
::replace(
1114 self.catalog
.read_dir(&entry
)?
.into_iter(),
1116 self.matches_stack
.push(self.matches
);
1117 self.dir_stack
.push(PathStackEntry
::new(entry
));
1118 self.path_len_stack
.push(self.path_len
);
1119 self.path_len
= self.path
.len();
1121 Shell
::walk_pxar_archive(&self.accessor
, &mut self.dir_stack
).await?
;
1122 let dir_pxar
= self.dir_stack
.last().unwrap().pxar
.as_ref().unwrap();
1123 let dir_meta
= dir_pxar
.entry().metadata().clone();
1124 let create
= self.matches
&& match_result
!= Some(MatchType
::Exclude
);
1125 self.extractor
.enter_directory(dir_pxar
.file_name().to_os_string(), dir_meta
, create
)?
;
1130 pub async
fn handle_entry(&mut self, entry
: catalog
::DirEntry
) -> Result
<(), Error
> {
1131 let match_result
= self.match_list
.matches(&self.path
, entry
.get_file_mode());
1132 let did_match
= match match_result
{
1133 Some(MatchType
::Include
) => true,
1134 Some(MatchType
::Exclude
) => false,
1135 None
=> self.matches
,
1138 match (did_match
, &entry
.attr
) {
1139 (_
, DirEntryAttribute
::Directory { .. }
) => {
1140 self.handle_new_directory(entry
, match_result
).await?
;
1142 (true, DirEntryAttribute
::File { .. }
) => {
1143 self.dir_stack
.push(PathStackEntry
::new(entry
));
1144 let file
= Shell
::walk_pxar_archive(&self.accessor
, &mut self.dir_stack
).await?
;
1145 self.extract_file(file
).await?
;
1146 self.dir_stack
.pop();
1148 (true, DirEntryAttribute
::Symlink
)
1149 | (true, DirEntryAttribute
::BlockDevice
)
1150 | (true, DirEntryAttribute
::CharDevice
)
1151 | (true, DirEntryAttribute
::Fifo
)
1152 | (true, DirEntryAttribute
::Socket
)
1153 | (true, DirEntryAttribute
::Hardlink
) => {
1154 let attr
= entry
.attr
.clone();
1155 self.dir_stack
.push(PathStackEntry
::new(entry
));
1156 let file
= Shell
::walk_pxar_archive(&self.accessor
, &mut self.dir_stack
).await?
;
1157 self.extract_special(file
, attr
).await?
;
1158 self.dir_stack
.pop();
1160 (false, _
) => (), // skip
1166 fn path(&self) -> &OsStr
{
1167 OsStr
::from_bytes(&self.path
)
1170 async
fn extract_file(&mut self, entry
: FileEntry
) -> Result
<(), Error
> {
1171 match entry
.kind() {
1172 pxar
::EntryKind
::File { size, .. }
=> {
1173 let file_name
= CString
::new(entry
.file_name().as_bytes())?
;
1174 let mut contents
= entry
.contents().await?
;
1175 self.extractor
.async_extract_file(
1185 "catalog file {:?} not a regular file in the archive",
1192 async
fn extract_special(
1195 catalog_attr
: DirEntryAttribute
,
1196 ) -> Result
<(), Error
> {
1197 let file_name
= CString
::new(entry
.file_name().as_bytes())?
;
1198 match (catalog_attr
, entry
.kind()) {
1199 (DirEntryAttribute
::Symlink
, pxar
::EntryKind
::Symlink(symlink
)) => {
1200 block_in_place(|| self.extractor
.extract_symlink(
1203 symlink
.as_os_str(),
1206 (DirEntryAttribute
::Symlink
, _
) => {
1208 "catalog symlink {:?} not a symlink in the archive",
1213 (DirEntryAttribute
::Hardlink
, pxar
::EntryKind
::Hardlink(hardlink
)) => {
1214 block_in_place(|| self.extractor
.extract_hardlink(&file_name
, hardlink
.as_os_str()))
1216 (DirEntryAttribute
::Hardlink
, _
) => {
1218 "catalog hardlink {:?} not a hardlink in the archive",
1223 (ref attr
, pxar
::EntryKind
::Device(device
)) => {
1224 self.extract_device(attr
.clone(), &file_name
, device
, entry
.metadata())
1227 (DirEntryAttribute
::Fifo
, pxar
::EntryKind
::Fifo
) => {
1228 block_in_place(|| self.extractor
.extract_special(&file_name
, entry
.metadata(), 0))
1230 (DirEntryAttribute
::Fifo
, _
) => {
1231 bail
!("catalog fifo {:?} not a fifo in the archive", self.path());
1234 (DirEntryAttribute
::Socket
, pxar
::EntryKind
::Socket
) => {
1235 block_in_place(|| self.extractor
.extract_special(&file_name
, entry
.metadata(), 0))
1237 (DirEntryAttribute
::Socket
, _
) => {
1239 "catalog socket {:?} not a socket in the archive",
1244 attr
=> bail
!("unhandled file type {:?} for {:?}", attr
, self.path()),
1250 attr
: DirEntryAttribute
,
1252 device
: &pxar
::format
::Device
,
1253 metadata
: &Metadata
,
1254 ) -> Result
<(), Error
> {
1256 DirEntryAttribute
::BlockDevice
=> {
1257 if !metadata
.stat
.is_blockdev() {
1259 "catalog block device {:?} is not a block device in the archive",
1264 DirEntryAttribute
::CharDevice
=> {
1265 if !metadata
.stat
.is_chardev() {
1267 "catalog character device {:?} is not a character device in the archive",
1274 "unexpected file type for {:?} in the catalog, \
1275 which is a device special file in the archive",
1280 block_in_place(|| self.extractor
.extract_special(file_name
, metadata
, device
.to_dev_t()))