1use std::borrow::Borrow;
2use std::env::split_paths;
3use std::ffi::CStr;
4use std::ffi::OsStr;
5use std::ffi::OsString;
6use std::io::ErrorKind;
7use std::iter::IntoIterator;
8use std::os::unix::ffi::OsStrExt;
9use std::os::unix::ffi::OsStringExt;
10use std::path::Component;
11use std::path::Path;
12use std::path::PathBuf;
13
14use crate::fs::File;
15use elb::Class;
16use elb::DynamicTag;
17use elb::Elf;
18use elb::Machine;
19use log::trace;
20use log::warn;
21
22use crate::Error;
23
24#[derive(Debug)]
28pub struct DependencyTree {
29 dependencies: Vec<(PathBuf, Vec<PathBuf>)>,
30}
31
32impl DependencyTree {
33 pub const fn new() -> Self {
35 Self {
36 dependencies: Vec::new(),
37 }
38 }
39
40 pub fn contains<P>(&self, path: &P) -> bool
42 where
43 PathBuf: Borrow<P>,
44 P: Ord + ?Sized,
45 {
46 self.dependencies
47 .binary_search_by(|(dependent, _)| dependent.borrow().cmp(path))
48 .is_ok()
49 }
50
51 pub fn get<P>(&self, path: &P) -> Option<&[PathBuf]>
53 where
54 PathBuf: Borrow<P>,
55 P: Ord + ?Sized,
56 {
57 self.dependencies
58 .binary_search_by(|(dependent, _)| dependent.borrow().cmp(path))
59 .ok()
60 .map(|i| self.dependencies[i].1.as_slice())
61 }
62
63 pub fn insert(
67 &mut self,
68 dependent: PathBuf,
69 dependencies: Vec<PathBuf>,
70 ) -> Option<Vec<PathBuf>> {
71 match self
72 .dependencies
73 .binary_search_by(|(x, _)| x.cmp(&dependent))
74 {
75 Ok(i) => Some(std::mem::replace(&mut self.dependencies[i].1, dependencies)),
76 Err(i) => {
77 self.dependencies.insert(i, (dependent, dependencies));
78 None
79 }
80 }
81 }
82
83 pub fn remove<P>(&mut self, path: &P) -> Option<Vec<PathBuf>>
85 where
86 PathBuf: Borrow<P>,
87 P: Ord + ?Sized,
88 {
89 self.dependencies
90 .binary_search_by(|(dependent, _)| dependent.borrow().cmp(path))
91 .ok()
92 .map(|i| self.dependencies.remove(i).1)
93 }
94
95 pub fn len(&self) -> usize {
97 self.dependencies.len()
98 }
99
100 pub fn is_empty(&self) -> bool {
102 self.dependencies.is_empty()
103 }
104}
105
106impl Default for DependencyTree {
107 fn default() -> Self {
108 Self::new()
109 }
110}
111
112impl IntoIterator for DependencyTree {
113 type Item = (PathBuf, Vec<PathBuf>);
114 type IntoIter = std::vec::IntoIter<Self::Item>;
115
116 fn into_iter(self) -> Self::IntoIter {
117 self.dependencies.into_iter()
118 }
119}
120
121#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
123pub enum Libc {
124 #[default]
126 Glibc,
127 Musl,
129}
130
131pub struct LoaderOptions {
133 search_dirs: Vec<PathBuf>,
134 search_dirs_override: Vec<PathBuf>,
135 lib: Option<OsString>,
136 platform: Option<OsString>,
137 page_size: u64,
138 libc: Libc,
139}
140
141impl LoaderOptions {
142 pub fn new() -> Self {
144 Self {
145 search_dirs: Default::default(),
146 search_dirs_override: Default::default(),
147 lib: None,
148 platform: None,
149 page_size: 4096,
150 libc: Default::default(),
151 }
152 }
153
154 #[cfg(feature = "glibc")]
156 pub fn glibc<P: AsRef<Path>>(rootfs_dir: P) -> Result<Self, std::io::Error> {
157 Ok(Self {
158 search_dirs: crate::glibc::get_search_dirs(rootfs_dir)?,
159 search_dirs_override: get_search_dirs_from_env(),
160 libc: Libc::Glibc,
161 ..Default::default()
162 })
163 }
164
165 #[cfg(feature = "musl")]
167 pub fn musl<P: AsRef<Path>>(rootfs_dir: P, arch: &str) -> Result<Self, std::io::Error> {
168 Ok(Self {
169 search_dirs: crate::musl::get_search_dirs(rootfs_dir, arch)?,
170 search_dirs_override: get_search_dirs_from_env(),
171 libc: Libc::Musl,
172 ..Default::default()
173 })
174 }
175
176 pub fn libc(mut self, libc: Libc) -> Self {
183 self.libc = libc;
184 self
185 }
186
187 pub fn search_dirs(mut self, search_dirs: Vec<PathBuf>) -> Self {
194 self.search_dirs = search_dirs;
195 self
196 }
197
198 pub fn search_dirs_override(mut self, search_dirs: Vec<PathBuf>) -> Self {
204 self.search_dirs_override = search_dirs;
205 self
206 }
207
208 pub fn page_size(mut self, page_size: u64) -> Self {
212 assert!(page_size.is_power_of_two());
213 self.page_size = page_size;
214 self
215 }
216
217 pub fn lib(mut self, lib: Option<OsString>) -> Self {
224 self.lib = lib;
225 self
226 }
227
228 pub fn platform(mut self, platform: Option<OsString>) -> Self {
235 self.platform = platform;
236 self
237 }
238
239 pub fn new_loader(self) -> DynamicLoader {
241 DynamicLoader {
242 search_dirs: self.search_dirs,
243 search_dirs_override: self.search_dirs_override,
244 lib: self.lib,
245 platform: self.platform,
246 page_size: self.page_size,
247 libc: self.libc,
248 }
249 }
250}
251
252impl Default for LoaderOptions {
253 fn default() -> Self {
254 Self::new()
255 }
256}
257
258pub struct DynamicLoader {
262 search_dirs: Vec<PathBuf>,
263 search_dirs_override: Vec<PathBuf>,
264 lib: Option<OsString>,
265 platform: Option<OsString>,
266 page_size: u64,
267 libc: Libc,
268}
269
270impl DynamicLoader {
271 pub fn options() -> LoaderOptions {
273 LoaderOptions::new()
274 }
275
276 pub fn resolve_dependencies<P: Into<PathBuf>>(
280 &self,
281 file: P,
282 tree: &mut DependencyTree,
283 ) -> Result<Vec<PathBuf>, Error> {
284 let dependent_file: PathBuf = file.into();
285 if tree.contains(&dependent_file) {
286 return Ok(Default::default());
287 }
288 let mut dependencies: Vec<PathBuf> = Vec::new();
289 let mut file = File::open(&dependent_file)?;
290 let elf = Elf::read(&mut file, self.page_size)?;
291 let names = elf.read_section_names(&mut file)?.unwrap_or_default();
292 let dynstr_table = elf
293 .read_dynamic_string_table(&mut file)?
294 .unwrap_or_default();
295 let Some(dynamic_table) = elf.read_dynamic_table(&mut file)? else {
296 tree.insert(dependent_file, Default::default());
298 return Ok(Default::default());
299 };
300 let interpreter = elf
301 .read_interpreter(&names, &mut file)?
302 .map(|c_str| PathBuf::from(OsString::from_vec(c_str.into_bytes())));
303 let mut search_dirs = Vec::new();
304 let runpath = dynamic_table.get(DynamicTag::Runpath);
305 let rpath = dynamic_table.get(DynamicTag::Rpath);
306 let override_dirs = match self.libc {
307 Libc::Glibc => runpath.is_some(),
308 Libc::Musl => true,
309 };
310 if override_dirs {
311 search_dirs.extend_from_slice(self.search_dirs_override.as_slice());
313 }
314 let mut extend_search_dirs = |path: &CStr| {
315 search_dirs.extend(split_paths(OsStr::from_bytes(path.to_bytes())).map(|dir| {
316 interpolate(
317 &dir,
318 &dependent_file,
319 &elf,
320 self.lib.as_deref(),
321 self.platform.as_deref(),
322 )
323 }));
324 };
325 match self.libc {
326 Libc::Glibc => {
327 runpath
329 .and_then(|string_offset| dynstr_table.get_string(string_offset as usize))
330 .map(&mut extend_search_dirs)
331 .or_else(|| {
332 rpath
339 .and_then(|string_offset| {
340 dynstr_table.get_string(string_offset as usize)
341 })
342 .map(&mut extend_search_dirs)
343 });
344 }
345 Libc::Musl => [rpath, runpath]
346 .into_iter()
347 .flatten()
348 .filter_map(|string_offset| dynstr_table.get_string(string_offset as usize))
349 .for_each(&mut extend_search_dirs),
350 }
351 search_dirs.extend_from_slice(self.search_dirs.as_slice());
353 'outer: for (tag, value) in dynamic_table.iter() {
354 if *tag != DynamicTag::Needed {
355 continue;
356 }
357 let Some(dep_name) = dynstr_table.get_string(*value as usize) else {
358 continue;
359 };
360 trace!("{:?} depends on {:?}", dependent_file, dep_name);
361 for dir in search_dirs.iter() {
362 let path = dir.join(OsStr::from_bytes(dep_name.to_bytes()));
363 let mut file = match File::open(&path) {
364 Ok(file) => file,
365 Err(ref e) if e.kind() == ErrorKind::NotFound => continue,
366 Err(e) => {
367 warn!("Failed to open {path:?}: {e}");
368 continue;
369 }
370 };
371 let dep = match Elf::read_unchecked(&mut file, self.page_size) {
372 Ok(dep) => dep,
373 Err(elb::Error::NotElf) => continue,
374 Err(e) => return Err(e.into()),
375 };
376 if dep.header.byte_order == elf.header.byte_order
377 && dep.header.class == elf.header.class
378 && dep.header.machine == elf.header.machine
379 {
380 trace!("Resolved {:?} as {:?}", dep_name, path);
381 if Some(path.as_path()) != interpreter.as_deref() {
382 dependencies.push(path);
383 }
384 continue 'outer;
385 }
386 }
387 return Err(Error::FailedToResolve(dep_name.into(), dependent_file));
388 }
389 if let Some(interpreter) = interpreter {
390 if !dependencies.contains(&interpreter) {
391 dependencies.push(interpreter);
392 }
393 }
394 tree.insert(dependent_file, dependencies.clone());
395 dependencies.retain(|dep| !tree.contains(dep));
396 Ok(dependencies)
397 }
398}
399
400pub fn get_search_dirs_from_env() -> Vec<PathBuf> {
406 std::env::var_os("LD_LIBRARY_PATH")
407 .map(|path| split_paths(&path).collect())
408 .unwrap_or_default()
409}
410
411fn interpolate(
412 dir: &Path,
413 file: &Path,
414 elf: &Elf,
415 lib: Option<&OsStr>,
416 platform: Option<&OsStr>,
417) -> PathBuf {
418 use Component::*;
419 let mut interpolated = PathBuf::new();
420 for comp in dir.components() {
421 match comp {
422 Normal(comp) if comp == "$ORIGIN" || comp == "${ORIGIN}" => {
423 if let Some(parent) = file.parent() {
424 interpolated.push(parent);
425 } else {
426 interpolated.push(comp);
427 }
428 }
429 Normal(comp) if comp == "$LIB" || comp == "${LIB}" => {
430 let lib = match lib {
431 Some(lib) => lib,
432 None => match elf.header.class {
433 Class::Elf32 => OsStr::new("lib"),
434 Class::Elf64 => OsStr::new("lib64"),
435 },
436 };
437 interpolated.push(lib);
438 }
439 Normal(comp) if comp == "$PLATFORM" || comp == "${PLATFORM}" => {
440 if let Some(platform) = platform {
441 interpolated.push(platform);
442 } else {
443 let platform = match elf.header.machine {
444 Machine::X86_64 => "x86_64",
445 _ => {
446 warn!(
447 "Failed to interpolate $PLATFORM, machine is {:?} ({})",
448 elf.header.machine,
449 elf.header.machine.as_u16()
450 );
451 interpolated.push(comp);
452 continue;
453 }
454 };
455 interpolated.push(platform);
456 }
457 }
458 comp => interpolated.push(comp),
459 }
460 }
461 interpolated
462}