Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/cli/src/util/tar.rs
3316 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
use crate::util::errors::{wrap, WrappedError};
6
7
use flate2::read::GzDecoder;
8
use std::fs::{self, File};
9
use std::io::{Read, Seek};
10
use std::path::{Path, PathBuf};
11
use tar::Archive;
12
13
use super::errors::wrapdbg;
14
use super::io::ReportCopyProgress;
15
16
fn should_skip_first_segment(file: &fs::File) -> Result<(bool, u64), WrappedError> {
17
// unfortunately, we need to re-read the archive here since you cannot reuse
18
// `.entries()`. But this will generally only look at one or two files, so this
19
// should be acceptably speedy... If not, we could hardcode behavior for
20
// different types of archives.
21
22
let tar = GzDecoder::new(file);
23
let mut archive = Archive::new(tar);
24
let mut entries = archive
25
.entries()
26
.map_err(|e| wrap(e, "error opening archive"))?;
27
28
let first_name = {
29
let file = entries
30
.next()
31
.expect("expected not to have an empty archive")
32
.map_err(|e| wrap(e, "error reading entry file"))?;
33
34
let path = file.path().expect("expected to have path");
35
36
path.iter()
37
.next()
38
.expect("expected to have non-empty name")
39
.to_owned()
40
};
41
42
let mut num_entries = 1;
43
let mut had_different_prefixes = false;
44
for file in entries.flatten() {
45
if !had_different_prefixes {
46
if let Ok(name) = file.path() {
47
if name.iter().next() != Some(&first_name) {
48
had_different_prefixes = true;
49
}
50
}
51
}
52
53
num_entries += 1;
54
}
55
56
Ok((!had_different_prefixes && num_entries > 1, num_entries)) // prefix removal is invalid if there's only a single file
57
}
58
59
pub fn decompress_tarball<T>(
60
mut tar_gz: File,
61
parent_path: &Path,
62
mut reporter: T,
63
) -> Result<(), WrappedError>
64
where
65
T: ReportCopyProgress,
66
{
67
let (skip_first, num_entries) = should_skip_first_segment(&tar_gz)?;
68
let report_progress_every = num_entries / 20;
69
let mut entries_so_far = 0;
70
let mut last_reported_at = 0;
71
72
// reset since skip logic read the tar already:
73
tar_gz
74
.rewind()
75
.map_err(|e| wrap(e, "error resetting seek position"))?;
76
77
let tar = GzDecoder::new(tar_gz);
78
let mut archive = Archive::new(tar);
79
archive
80
.entries()
81
.map_err(|e| wrap(e, "error opening archive"))?
82
.filter_map(|e| e.ok())
83
.try_for_each::<_, Result<_, WrappedError>>(|mut entry| {
84
// approximate progress based on where we are in the archive:
85
entries_so_far += 1;
86
if entries_so_far - last_reported_at > report_progress_every {
87
reporter.report_progress(entries_so_far, num_entries);
88
entries_so_far += 1;
89
last_reported_at = entries_so_far;
90
}
91
92
let entry_path = entry
93
.path()
94
.map_err(|e| wrap(e, "error reading entry path"))?;
95
96
let path = parent_path.join(if skip_first {
97
entry_path.iter().skip(1).collect::<PathBuf>()
98
} else {
99
entry_path.into_owned()
100
});
101
102
if let Some(p) = path.parent() {
103
fs::create_dir_all(p)
104
.map_err(|e| wrap(e, format!("could not create dir for {}", p.display())))?;
105
}
106
107
entry
108
.unpack(&path)
109
.map_err(|e| wrapdbg(e, format!("error unpacking {}", path.display())))?;
110
111
Ok(())
112
})?;
113
114
reporter.report_progress(num_entries, num_entries);
115
116
Ok(())
117
}
118
119
pub fn has_gzip_header(path: &Path) -> std::io::Result<(File, bool)> {
120
let mut file = fs::File::open(path)?;
121
let mut header = [0; 2];
122
let _ = file.read_exact(&mut header);
123
124
file.rewind()?;
125
126
Ok((file, header[0] == 0x1f && header[1] == 0x8b))
127
}
128
129