1use std::collections::HashMap;
2use std::collections::VecDeque;
3use std::ffi::CString;
4use std::ffi::OsStr;
5use std::fs::Permissions;
6use std::os::unix::ffi::OsStrExt;
7use std::os::unix::fs::PermissionsExt;
8use std::path::Path;
9use std::path::PathBuf;
10
11use elb::DynamicTag;
12use elb::Elf;
13use elb::ElfPatcher;
14
15use crate::fs;
16use crate::fs::os::unix::fs::symlink;
17use crate::DependencyTree;
18use crate::DynamicLoader;
19use crate::Error;
20
21pub struct ElfRelocator {
23 loader: DynamicLoader,
24}
25
26impl ElfRelocator {
27 pub fn new(loader: DynamicLoader) -> Self {
29 Self { loader }
30 }
31
32 pub fn relocate<P1: Into<PathBuf>, P2: AsRef<Path>>(
39 &self,
40 file: P1,
41 directory: P2,
42 ) -> Result<PathBuf, Error> {
43 let file = file.into();
44 let directory = directory.as_ref();
45 let mut tree = DependencyTree::new();
46 let mut queue = VecDeque::new();
47 queue.push_back(file.clone());
48 while let Some(file) = queue.pop_front() {
49 let dependencies = self.loader.resolve_dependencies(&file, &mut tree)?;
50 queue.extend(dependencies);
51 }
52 let mut hashes = HashMap::with_capacity(tree.len());
53 for (dependent, _dependencies) in tree.iter() {
54 let (hash, new_path) = relocate_file(dependent, directory)?;
55 patch_file(&new_path, directory, &hash, self.loader.page_size)?;
56 hashes.insert(dependent.clone(), hash);
58 }
59 for (dependent, dependencies) in tree.iter() {
60 let hash = hashes.get(dependent).expect("Inserted above");
61 let dir = directory.join(hash.as_str());
62 for dep in dependencies.iter() {
63 let file_name = dep.file_name().expect("File name exists");
64 let dep_hash = hashes.get(dep).expect("Inserted above");
65 let source = {
66 let mut path = PathBuf::new();
67 path.push("..");
68 path.push(dep_hash.as_str());
69 path.push(file_name);
70 path
71 };
72 let target = dir.join(file_name);
73 let _ = std::fs::remove_file(&target);
74 symlink(&source, &target)?;
75 }
76 }
77 let mut new_path = PathBuf::new();
78 new_path.push(directory);
79 new_path.push(hashes.get(&file).expect("Inserted above").as_str());
80 new_path.push(file.file_name().expect("File name exists"));
81 Ok(new_path)
82 }
83}
84
85fn relocate_file(file: &Path, dir: &Path) -> Result<(Hash, PathBuf), Error> {
86 let hash = {
87 let mut file = fs::File::open(file)?;
88 let mut hasher = Blake2bHasher::new();
89 std::io::copy(&mut file, &mut hasher)?;
90 let hash = hasher.finalize();
91 let mut encoded_hash: HashArray = [0_u8; base32_fs::encoded_len(32)];
92 base32_fs::encode(&hash[..], &mut &mut encoded_hash[..]);
93 Hash(encoded_hash)
94 };
95 let mut new_path = PathBuf::new();
96 new_path.push(dir);
97 new_path.push(hash.as_str());
98 fs::create_dir_all(&new_path)?;
99 new_path.push(file.file_name().expect("File name exists"));
100 let _ = std::fs::remove_file(&new_path);
101 fs::copy(file, &new_path)?;
102 Ok((hash, new_path))
103}
104
105fn patch_file(file: &Path, directory: &Path, hash: &Hash, page_size: u64) -> Result<(), Error> {
106 let dir = file.parent().expect("Parent directory exists");
107 let dir_bytes = dir.as_os_str().as_bytes();
108 let file_name = file.file_name().expect("File name exists");
109 let Some(file_kind) = get_file_kind(file, page_size)? else {
110 return Ok(());
112 };
113 let mode = match file_kind {
114 FileKind::Executable | FileKind::Static => 0o755,
115 FileKind::Library => 0o644,
116 };
117 fs::set_permissions(file, Permissions::from_mode(mode))?;
118 if matches!(file_kind, FileKind::Executable | FileKind::Static) {
119 let bin = directory.join("bin");
120 fs::create_dir_all(&bin)?;
121 let source = {
122 let mut path = PathBuf::new();
123 path.push("..");
124 path.push(hash.as_str());
125 path.push(file_name);
126 path
127 };
128 let target = bin.join(file_name);
129 let _ = std::fs::remove_file(&target);
130 symlink(&source, &target)?;
131 }
132 if file_kind == FileKind::Static {
133 return Ok(());
135 }
136 let mut file = fs::OpenOptions::new().read(true).write(true).open(file)?;
137 let elf = Elf::read(&mut file, page_size)?;
138 let mut patcher = ElfPatcher::new(elf, file);
139 if let Some(old_interpreter) = patcher.read_interpreter()? {
140 let interpreter = {
141 let old_interpreter = Path::new(OsStr::from_bytes(old_interpreter.to_bytes()));
142 let file_name_bytes = old_interpreter
143 .file_name()
144 .expect("File name exists")
145 .as_bytes();
146 let mut bytes = Vec::with_capacity(dir_bytes.len() + file_name_bytes.len() + 2);
147 bytes.extend_from_slice(dir_bytes);
148 bytes.extend_from_slice(std::path::MAIN_SEPARATOR_STR.as_bytes());
149 bytes.extend_from_slice(file_name_bytes);
150 bytes.push(0_u8);
151 unsafe { CString::from_vec_with_nul_unchecked(bytes) }
154 };
155 patcher.set_interpreter(interpreter.as_c_str())?;
156 }
157 let runpath = {
158 let mut bytes = Vec::with_capacity(dir_bytes.len() + 1);
159 bytes.extend_from_slice(dir_bytes);
160 bytes.push(0_u8);
161 unsafe { CString::from_vec_with_nul_unchecked(bytes) }
164 };
165 patcher.set_dynamic_tag(DynamicTag::Runpath, runpath.as_c_str())?;
166 patcher.finish()?;
167 Ok(())
168}
169
170fn get_file_kind(file: &Path, page_size: u64) -> Result<Option<FileKind>, Error> {
171 let mut file = fs::File::open(file)?;
172 let elf = Elf::read(&mut file, page_size)?;
173 if elf.read_interpreter(&mut file)?.is_some() {
174 return Ok(Some(FileKind::Executable));
175 }
176 let Some(dynamic_table) = elf.read_dynamic_table(&mut file)? else {
178 return Ok(None);
179 };
180 Ok(match dynamic_table.get(DynamicTag::Needed) {
181 Some(..) => Some(FileKind::Library),
182 None => Some(FileKind::Static),
183 })
184}
185
186#[derive(Clone, Copy, PartialEq, Eq, Debug)]
187enum FileKind {
188 Executable,
189 Library,
190 Static,
191}
192
193struct Blake2bHasher {
194 state: blake2b_simd::State,
195}
196
197impl Blake2bHasher {
198 fn new() -> Self {
199 Self {
200 state: blake2b_simd::Params::new().hash_length(32).to_state(),
201 }
202 }
203
204 fn finalize(self) -> [u8; 32] {
205 self.state
206 .finalize()
207 .as_bytes()
208 .try_into()
209 .expect("64 > 32")
210 }
211}
212
213impl std::io::Write for Blake2bHasher {
214 fn write(&mut self, data: &[u8]) -> std::io::Result<usize> {
215 self.state.update(data);
216 Ok(data.len())
217 }
218
219 fn flush(&mut self) -> std::io::Result<()> {
220 Ok(())
221 }
222}
223
224type HashArray = [u8; base32_fs::encoded_len(32)];
225
226struct Hash(HashArray);
227
228impl Hash {
229 fn as_str(&self) -> &str {
230 unsafe { std::str::from_utf8_unchecked(&self.0[..]) }
233 }
234}