55use crate :: util:: errors:: { wrap, WrappedError } ;
66
77use flate2:: read:: GzDecoder ;
8- use std:: fs:: File ;
8+ use std:: fs;
9+ use std:: io:: { Seek , SeekFrom } ;
910use std:: path:: { Path , PathBuf } ;
1011use tar:: Archive ;
1112
1213use super :: io:: ReportCopyProgress ;
1314
15+ fn should_skip_first_segment ( file : & fs:: File ) -> Result < bool , WrappedError > {
16+ // unfortunately, we need to re-read the archive here since you cannot reuse
17+ // `.entries()`. But this will generally only look at one or two files, so this
18+ // should be acceptably speedy... If not, we could hardcode behavior for
19+ // different types of archives.
20+
21+ let tar = GzDecoder :: new ( file) ;
22+ let mut archive = Archive :: new ( tar) ;
23+ let mut entries = archive
24+ . entries ( )
25+ . map_err ( |e| wrap ( e, "error opening archive" ) ) ?;
26+
27+ let first_name = {
28+ let file = entries
29+ . next ( )
30+ . expect ( "expected not to have an empty archive" )
31+ . map_err ( |e| wrap ( e, "error reading entry file" ) ) ?;
32+
33+ let path = file. path ( ) . expect ( "expected to have path" ) ;
34+
35+ path. iter ( )
36+ . next ( )
37+ . expect ( "expected to have non-empty name" )
38+ . to_owned ( )
39+ } ;
40+
41+ let mut had_multiple = false ;
42+ for file in entries {
43+ if let Ok ( file) = file {
44+ had_multiple = true ;
45+ if let Ok ( name) = file. path ( ) {
46+ if name. iter ( ) . next ( ) != Some ( & first_name) {
47+ return Ok ( false ) ;
48+ }
49+ }
50+ }
51+ }
52+
53+ Ok ( had_multiple) // prefix removal is invalid if there's only a single file
54+ }
55+
1456pub fn decompress_tarball < T > (
1557 path : & Path ,
1658 parent_path : & Path ,
@@ -19,12 +61,15 @@ pub fn decompress_tarball<T>(
1961where
2062 T : ReportCopyProgress ,
2163{
22- let tar_gz = File :: open ( path) . map_err ( |e| {
23- wrap (
24- Box :: new ( e) ,
25- format ! ( "error opening file {}" , path. display( ) ) ,
26- )
27- } ) ?;
64+ let mut tar_gz = fs:: File :: open ( path)
65+ . map_err ( |e| wrap ( e, format ! ( "error opening file {}" , path. display( ) ) ) ) ?;
66+ let skip_first = should_skip_first_segment ( & tar_gz) ?;
67+
68+ // reset since skip logic read the tar already:
69+ tar_gz
70+ . seek ( SeekFrom :: Start ( 0 ) )
71+ . map_err ( |e| wrap ( e, "error resetting seek position" ) ) ?;
72+
2873 let tar = GzDecoder :: new ( tar_gz) ;
2974 let mut archive = Archive :: new ( tar) ;
3075
3782 . path ( )
3883 . map_err ( |e| wrap ( e, "error reading entry path" ) ) ?;
3984
40- let path = parent_path. join ( entry_path. iter ( ) . skip ( 1 ) . collect :: < PathBuf > ( ) ) ;
85+ let path = parent_path. join ( if skip_first {
86+ entry_path. iter ( ) . skip ( 1 ) . collect :: < PathBuf > ( )
87+ } else {
88+ entry_path. into_owned ( )
89+ } ) ;
90+
91+ if let Some ( p) = path. parent ( ) {
92+ fs:: create_dir_all ( & p)
93+ . map_err ( |e| wrap ( e, format ! ( "could not create dir for {}" , p. display( ) ) ) ) ?;
94+ }
95+
4196 entry
4297 . unpack ( & path)
4398 . map_err ( |e| wrap ( e, format ! ( "error unpacking {}" , path. display( ) ) ) ) ?;
0 commit comments