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