1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
|
use flate2::read::GzDecoder;
use std::fs;
use std::path::Path;
use tar::Archive;
pub fn extract_zip(zip_path: &Path, extract_to: &Path) -> Result<(), String> {
let file = fs::File::open(zip_path)
.map_err(|e| format!("Failed to open zip {}: {}", zip_path.display(), e))?;
let mut archive =
zip::ZipArchive::new(file).map_err(|e| format!("Failed to read zip: {}", e))?;
for i in 0..archive.len() {
let mut file = archive
.by_index(i)
.map_err(|e| format!("Failed to read zip entry: {}", e))?;
let outpath = match file.enclosed_name() {
Some(path) => extract_to.join(path),
None => continue,
};
// Skip META-INF
if outpath.to_string_lossy().contains("META-INF") {
continue;
}
if file.name().ends_with('/') {
fs::create_dir_all(&outpath).map_err(|e| format!("Failed to create dir: {}", e))?;
} else {
if let Some(p) = outpath.parent() {
if !p.exists() {
fs::create_dir_all(p).map_err(|e| format!("Failed to create dir: {}", e))?;
}
}
let mut outfile =
fs::File::create(&outpath).map_err(|e| format!("Failed to create file: {}", e))?;
std::io::copy(&mut file, &mut outfile)
.map_err(|e| format!("Failed to copy file: {}", e))?;
}
}
Ok(())
}
/// Extract a tar.gz archive
///
/// Adoptium's tar.gz archives usually contain a top-level directory, such as `jdk-21.0.5+11-jre/`.
/// This function returns the name of that directory to facilitate locating `bin/java` afterwards.
pub fn extract_tar_gz(archive_path: &Path, extract_to: &Path) -> Result<String, String> {
let file = fs::File::open(archive_path)
.map_err(|e| format!("Failed to open tar.gz {}: {}", archive_path.display(), e))?;
let decoder = GzDecoder::new(file);
let mut archive = Archive::new(decoder);
// Ensure the target directory exists
fs::create_dir_all(extract_to)
.map_err(|e| format!("Failed to create extract directory: {}", e))?;
// Track the top-level directory name
let mut top_level_dir: Option<String> = None;
for entry in archive
.entries()
.map_err(|e| format!("Failed to read tar entries: {}", e))?
{
let mut entry = entry.map_err(|e| format!("Failed to read tar entry: {}", e))?;
let entry_path = entry
.path()
.map_err(|e| format!("Failed to get entry path: {}", e))?
.into_owned();
// Extract the top-level directory name (the first path component)
if top_level_dir.is_none() {
if let Some(first_component) = entry_path.components().next() {
let component_str = first_component.as_os_str().to_string_lossy().to_string();
if !component_str.is_empty() && component_str != "." {
top_level_dir = Some(component_str);
}
}
}
let outpath = extract_to.join(&entry_path);
if entry.header().entry_type().is_dir() {
fs::create_dir_all(&outpath)
.map_err(|e| format!("Failed to create directory {}: {}", outpath.display(), e))?;
} else {
// Ensure parent directory exists
if let Some(parent) = outpath.parent() {
if !parent.exists() {
fs::create_dir_all(parent)
.map_err(|e| format!("Failed to create parent dir: {}", e))?;
}
}
let mut outfile = fs::File::create(&outpath)
.map_err(|e| format!("Failed to create file {}: {}", outpath.display(), e))?;
std::io::copy(&mut entry, &mut outfile)
.map_err(|e| format!("Failed to extract file: {}", e))?;
// Set executable permissions on Unix systems
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
if let Ok(mode) = entry.header().mode() {
let permissions = fs::Permissions::from_mode(mode);
let _ = fs::set_permissions(&outpath, permissions);
}
}
}
}
top_level_dir.ok_or_else(|| "Archive appears to be empty".to_string())
}
|