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