hyperdb_bootstrap/
extract.rs1use std::fs::{self, File};
16use std::io;
17use std::path::{Path, PathBuf};
18
19use crate::Error;
20
21pub fn extract_hyperd(zip_path: &Path, dest_dir: &Path) -> Result<Vec<PathBuf>, Error> {
31 let file = File::open(zip_path)
32 .map_err(|source| Error::io(format!("opening zip {}", zip_path.display()), source))?;
33 let mut archive = zip::ZipArchive::new(file).map_err(Error::Zip)?;
34
35 fs::create_dir_all(dest_dir)
36 .map_err(|source| Error::io(format!("creating {}", dest_dir.display()), source))?;
37
38 let mut extracted = Vec::new();
39 let mut found_hyperd = false;
40
41 for i in 0..archive.len() {
42 let mut entry = archive.by_index(i).map_err(Error::Zip)?;
43 let Some(enclosed) = entry.enclosed_name() else {
44 continue;
45 };
46 let Some(rel) = strip_lib_hyper_prefix(&enclosed) else {
47 continue;
48 };
49 if rel.as_os_str().is_empty() {
50 continue;
51 }
52
53 let out_path = dest_dir.join(&rel);
54 if entry.is_dir() {
55 fs::create_dir_all(&out_path)
56 .map_err(|source| Error::io(format!("creating {}", out_path.display()), source))?;
57 continue;
58 }
59 if let Some(parent) = out_path.parent() {
60 fs::create_dir_all(parent)
61 .map_err(|source| Error::io(format!("creating {}", parent.display()), source))?;
62 }
63 let mut out = File::create(&out_path)
64 .map_err(|source| Error::io(format!("creating {}", out_path.display()), source))?;
65 io::copy(&mut entry, &mut out)
66 .map_err(|source| Error::io(format!("writing {}", out_path.display()), source))?;
67
68 #[cfg(unix)]
69 {
70 use std::os::unix::fs::PermissionsExt;
71 if let Some(mode) = entry.unix_mode() {
72 let _ = fs::set_permissions(&out_path, fs::Permissions::from_mode(mode));
73 }
74 }
75
76 if rel
77 .file_name()
78 .is_some_and(|n| n == "hyperd" || n == "hyperd.exe")
79 {
80 found_hyperd = true;
81 }
82 extracted.push(rel);
83 }
84
85 if !found_hyperd {
86 return Err(Error::HyperdNotInArchive);
87 }
88 Ok(extracted)
89}
90
91fn strip_lib_hyper_prefix(path: &Path) -> Option<PathBuf> {
97 let mut comps = path.components();
98 let first = comps.next()?;
102 let (a, b) = if first.as_os_str() == "lib" || first.as_os_str() == "bin" {
103 (first, comps.next()?)
104 } else {
105 (comps.next()?, comps.next()?)
106 };
107 if (a.as_os_str() == "lib" || a.as_os_str() == "bin") && b.as_os_str() == "hyper" {
108 Some(comps.as_path().to_path_buf())
109 } else {
110 None
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[test]
119 fn strip_prefix_matches_lib_hyper() {
120 assert_eq!(
122 strip_lib_hyper_prefix(Path::new("lib/hyper/hyperd")),
123 Some(PathBuf::from("hyperd"))
124 );
125 assert_eq!(
126 strip_lib_hyper_prefix(Path::new("lib/hyper/sub/file")),
127 Some(PathBuf::from("sub/file"))
128 );
129 assert_eq!(
131 strip_lib_hyper_prefix(Path::new("tableauhyperapi-java-x/lib/hyper/hyperd")),
132 Some(PathBuf::from("hyperd"))
133 );
134 assert_eq!(
135 strip_lib_hyper_prefix(Path::new("tableauhyperapi-java-x/lib/hyper/sub/a.so")),
136 Some(PathBuf::from("sub/a.so"))
137 );
138 assert_eq!(
140 strip_lib_hyper_prefix(Path::new("tableauhyperapi-java-x/bin/hyper/hyperd.exe")),
141 Some(PathBuf::from("hyperd.exe"))
142 );
143 assert_eq!(
144 strip_lib_hyper_prefix(Path::new("tableauhyperapi-java-x/bin/hyper/sub/extra.dll")),
145 Some(PathBuf::from("sub/extra.dll"))
146 );
147 assert_eq!(
149 strip_lib_hyper_prefix(Path::new("tableauhyperapi-java-x/include/foo.hpp")),
150 None
151 );
152 assert_eq!(
153 strip_lib_hyper_prefix(Path::new("tableauhyperapi-java-x/bin/tableauhyperapi.dll")),
154 None
155 );
156 assert_eq!(strip_lib_hyper_prefix(Path::new("other/file")), None);
157 }
158
159 #[test]
160 fn extract_fixture_zip() -> Result<(), Box<dyn std::error::Error>> {
161 use std::io::Write;
162 let tmp = tempfile::tempdir()?;
163 let zip_path = tmp.path().join("fixture.zip");
164 {
165 let file = File::create(&zip_path)?;
166 let mut zw = zip::ZipWriter::new(file);
167 let opts = zip::write::SimpleFileOptions::default();
168 zw.start_file("tableauhyperapi-java-fake/lib/hyper/hyperd", opts)?;
170 zw.write_all(b"fake hyperd")?;
171 zw.start_file("tableauhyperapi-java-fake/lib/hyper/sub/extra.txt", opts)?;
172 zw.write_all(b"extra")?;
173 zw.start_file("tableauhyperapi-java-fake/include/ignored.hpp", opts)?;
174 zw.write_all(b"nope")?;
175 zw.finish()?;
176 }
177 let out = tmp.path().join("out");
178 let files = extract_hyperd(&zip_path, &out)?;
179 assert!(files.iter().any(|p| p == Path::new("hyperd")));
180 assert!(files.iter().any(|p| p == Path::new("sub/extra.txt")));
181 assert!(out.join("hyperd").exists());
182 assert!(!out.join("ignored.hpp").exists());
183 Ok(())
184 }
185}