Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/cli/src/util/zipper.rs
3314 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 super::errors::{wrap, WrappedError};
6
use super::io::ReportCopyProgress;
7
use std::fs::{self, File};
8
use std::io;
9
use std::path::Path;
10
use std::path::PathBuf;
11
use zip::read::ZipFile;
12
use zip::{self, ZipArchive};
13
14
// Borrowed and modified from https://github.com/zip-rs/zip/blob/master/examples/extract.rs
15
16
/// Returns whether all files in the archive start with the same path segment.
17
/// If so, it's an indication we should skip that segment when extracting.
18
fn should_skip_first_segment(archive: &mut ZipArchive<File>) -> bool {
19
let first_name = {
20
let file = archive
21
.by_index_raw(0)
22
.expect("expected not to have an empty archive");
23
24
let path = file
25
.enclosed_name()
26
.expect("expected to have path")
27
.iter()
28
.next()
29
.expect("expected to have non-empty name");
30
31
path.to_owned()
32
};
33
34
for i in 1..archive.len() {
35
if let Ok(file) = archive.by_index_raw(i) {
36
if let Some(name) = file.enclosed_name() {
37
if name.iter().next() != Some(&first_name) {
38
return false;
39
}
40
}
41
}
42
}
43
44
archive.len() > 1 // prefix removal is invalid if there's only a single file
45
}
46
47
pub fn unzip_file<T>(file: File, parent_path: &Path, mut reporter: T) -> Result<(), WrappedError>
48
where
49
T: ReportCopyProgress,
50
{
51
let mut archive =
52
zip::ZipArchive::new(file).map_err(|e| wrap(e, "failed to open zip archive"))?;
53
54
let skip_segments_no = usize::from(should_skip_first_segment(&mut archive));
55
let report_progress_every = archive.len() / 20;
56
57
for i in 0..archive.len() {
58
if i % report_progress_every == 0 {
59
reporter.report_progress(i as u64, archive.len() as u64);
60
}
61
let mut file = archive
62
.by_index(i)
63
.map_err(|e| wrap(e, format!("could not open zip entry {i}")))?;
64
65
let outpath: PathBuf = match file.enclosed_name() {
66
Some(path) => {
67
let mut full_path = PathBuf::from(parent_path);
68
full_path.push(PathBuf::from_iter(path.iter().skip(skip_segments_no)));
69
full_path
70
}
71
None => continue,
72
};
73
74
if file.is_dir() || file.name().ends_with('/') {
75
fs::create_dir_all(&outpath)
76
.map_err(|e| wrap(e, format!("could not create dir for {}", outpath.display())))?;
77
apply_permissions(&file, &outpath)?;
78
continue;
79
}
80
81
if let Some(p) = outpath.parent() {
82
fs::create_dir_all(p)
83
.map_err(|e| wrap(e, format!("could not create dir for {}", outpath.display())))?;
84
}
85
86
#[cfg(unix)]
87
{
88
use libc::S_IFLNK;
89
use std::io::Read;
90
use std::os::unix::ffi::OsStringExt;
91
92
#[cfg(target_os = "macos")]
93
const S_IFLINK_32: u32 = S_IFLNK as u32;
94
95
#[cfg(target_os = "linux")]
96
const S_IFLINK_32: u32 = S_IFLNK;
97
98
if matches!(file.unix_mode(), Some(mode) if mode & S_IFLINK_32 == S_IFLINK_32) {
99
let mut link_to = Vec::new();
100
file.read_to_end(&mut link_to).map_err(|e| {
101
wrap(
102
e,
103
format!("could not read symlink linkpath {}", outpath.display()),
104
)
105
})?;
106
107
let link_path = PathBuf::from(std::ffi::OsString::from_vec(link_to));
108
std::os::unix::fs::symlink(link_path, &outpath).map_err(|e| {
109
wrap(e, format!("could not create symlink {}", outpath.display()))
110
})?;
111
continue;
112
}
113
}
114
115
let mut outfile = fs::File::create(&outpath).map_err(|e| {
116
wrap(
117
e,
118
format!(
119
"unable to open file to write {} (from {:?})",
120
outpath.display(),
121
file.enclosed_name().map(|p| p.to_string_lossy()),
122
),
123
)
124
})?;
125
126
io::copy(&mut file, &mut outfile)
127
.map_err(|e| wrap(e, format!("error copying file {}", outpath.display())))?;
128
129
apply_permissions(&file, &outpath)?;
130
}
131
132
reporter.report_progress(archive.len() as u64, archive.len() as u64);
133
134
Ok(())
135
}
136
137
#[cfg(unix)]
138
fn apply_permissions(file: &ZipFile, outpath: &Path) -> Result<(), WrappedError> {
139
use std::os::unix::fs::PermissionsExt;
140
141
if let Some(mode) = file.unix_mode() {
142
fs::set_permissions(outpath, fs::Permissions::from_mode(mode)).map_err(|e| {
143
wrap(
144
e,
145
format!("error setting permissions on {}", outpath.display()),
146
)
147
})?;
148
}
149
150
Ok(())
151
}
152
153
#[cfg(windows)]
154
fn apply_permissions(_file: &ZipFile, _outpath: &Path) -> Result<(), WrappedError> {
155
Ok(())
156
}
157
158