1use std::fs;
21use std::io::Read;
22use std::path::{Path, PathBuf};
23
24use flate2::read::GzDecoder;
25use tar::Archive;
26use thiserror::Error;
27
28#[derive(Debug, Error)]
29pub enum ExtractError {
30 #[error("{name}: I/O error: {source}")]
31 Io {
32 name: String,
33 #[source]
34 source: std::io::Error,
35 },
36
37 #[error("{name}: archive `{archive}` contains no top-level entry named `{member}`")]
38 BinaryMemberNotFound {
39 name: String,
40 archive: PathBuf,
41 member: String,
42 },
43}
44
45pub fn install_extension_binary(
48 archive_path: &Path,
49 bin_dir: &Path,
50 bin_name: &str,
51) -> Result<PathBuf, ExtractError> {
52 let name = bin_name.to_string();
53 fs::create_dir_all(bin_dir).map_err(io(&name))?;
54
55 let bytes = read_member(archive_path, bin_name).map_err(io(&name))?.ok_or_else(|| {
56 ExtractError::BinaryMemberNotFound {
57 name: name.clone(),
58 archive: archive_path.to_path_buf(),
59 member: bin_name.to_string(),
60 }
61 })?;
62
63 let final_path = bin_dir.join(bin_name);
64 let tmp_path = bin_dir.join(format!(".{bin_name}.tmp"));
65 let _ = fs::remove_file(&tmp_path);
67
68 fs::write(&tmp_path, &bytes).map_err(io(&name))?;
69 set_executable_mode(&tmp_path).map_err(io(&name))?;
70 fs::rename(&tmp_path, &final_path).map_err(io(&name))?;
71
72 Ok(final_path)
73}
74
75pub fn install_lib_tree(
83 archive_path: &Path,
84 lib_dir: &Path,
85 lib_name: &str,
86) -> Result<PathBuf, ExtractError> {
87 let name = lib_name.to_string();
88 fs::create_dir_all(lib_dir).map_err(io(&name))?;
89
90 let target = lib_dir.join(lib_name);
91 let staging = lib_dir.join(format!(".{lib_name}.new"));
92
93 if staging.exists() {
95 fs::remove_dir_all(&staging).map_err(io(&name))?;
96 }
97 fs::create_dir_all(&staging).map_err(io(&name))?;
98
99 let f = fs::File::open(archive_path).map_err(io(&name))?;
100 let mut archive = Archive::new(GzDecoder::new(f));
101 archive.unpack(&staging).map_err(io(&name))?;
102
103 if target.exists() {
104 fs::remove_dir_all(&target).map_err(io(&name))?;
105 }
106 fs::rename(&staging, &target).map_err(io(&name))?;
107
108 Ok(target)
109}
110
111fn read_member(archive_path: &Path, member: &str) -> std::io::Result<Option<Vec<u8>>> {
112 let target_basename = Path::new(member);
113 let f = fs::File::open(archive_path)?;
114 let mut archive = Archive::new(GzDecoder::new(f));
115 for entry in archive.entries()? {
116 let mut entry = entry?;
117 let path = entry.path()?.into_owned();
118 if path.file_name() == Some(target_basename.as_os_str()) {
119 let mut buf = Vec::new();
120 entry.read_to_end(&mut buf)?;
121 return Ok(Some(buf));
122 }
123 }
124 Ok(None)
125}
126
127fn io(name: &str) -> impl Fn(std::io::Error) -> ExtractError + '_ {
128 let owned = name.to_string();
129 move |source| ExtractError::Io {
130 name: owned.clone(),
131 source,
132 }
133}
134
135#[cfg(unix)]
136fn set_executable_mode(path: &Path) -> std::io::Result<()> {
137 use std::os::unix::fs::PermissionsExt;
138 fs::set_permissions(path, fs::Permissions::from_mode(0o755))
139}
140
141#[cfg(not(unix))]
142fn set_executable_mode(_path: &Path) -> std::io::Result<()> {
143 Ok(())
144}