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 pub fn iter(&self) -> std::slice::Iter<'_, (PathBuf, Vec<PathBuf>)> {
107 self.dependencies.iter()
108 }
109}
110
111impl Default for DependencyTree {
112 fn default() -> Self {
113 Self::new()
114 }
115}
116
117impl IntoIterator for DependencyTree {
118 type Item = (PathBuf, Vec<PathBuf>);
119 type IntoIter = std::vec::IntoIter<Self::Item>;
120
121 fn into_iter(self) -> Self::IntoIter {
122 self.dependencies.into_iter()
123 }
124}
125
126#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
128pub enum Libc {
129 #[default]
131 Glibc,
132 Musl,
134}
135
136pub struct LoaderOptions {
138 root: PathBuf,
139 search_dirs: Vec<PathBuf>,
140 search_dirs_override: Vec<PathBuf>,
141 lib: Option<OsString>,
142 platform: Option<OsString>,
143 page_size: u64,
144 libc: Libc,
145}
146
147impl LoaderOptions {
148 pub fn new() -> Self {
150 Self {
151 root: "/".into(),
152 search_dirs: Default::default(),
153 search_dirs_override: Default::default(),
154 lib: None,
155 platform: None,
156 page_size: 4096,
157 libc: Default::default(),
158 }
159 }
160
161 #[cfg(feature = "glibc")]
163 pub fn glibc<P: Into<PathBuf>>(rootfs_dir: P) -> Result<Self, std::io::Error> {
164 let root: PathBuf = rootfs_dir.into();
165 Ok(Self {
166 search_dirs: crate::glibc::get_search_dirs(root.as_path())?,
167 search_dirs_override: get_search_dirs_from_env(),
168 libc: Libc::Glibc,
169 root,
170 ..Default::default()
171 })
172 }
173
174 #[cfg(feature = "musl")]
176 pub fn musl<P: Into<PathBuf>>(rootfs_dir: P, arch: &str) -> Result<Self, std::io::Error> {
177 let root: PathBuf = rootfs_dir.into();
178 Ok(Self {
179 search_dirs: crate::musl::get_search_dirs(root.as_path(), arch)?,
180 search_dirs_override: get_search_dirs_from_env(),
181 libc: Libc::Musl,
182 root,
183 ..Default::default()
184 })
185 }
186
187 pub fn root<P: Into<PathBuf>>(mut self, root: P) -> Self {
191 self.root = root.into();
192 self
193 }
194
195 pub fn libc(mut self, libc: Libc) -> Self {
202 self.libc = libc;
203 self
204 }
205
206 pub fn search_dirs(mut self, search_dirs: Vec<PathBuf>) -> Self {
213 self.search_dirs = search_dirs;
214 self
215 }
216
217 pub fn search_dirs_override(mut self, search_dirs: Vec<PathBuf>) -> Self {
223 self.search_dirs_override = search_dirs;
224 self
225 }
226
227 pub fn page_size(mut self, page_size: u64) -> Self {
231 assert!(page_size.is_power_of_two());
232 self.page_size = page_size;
233 self
234 }
235
236 pub fn lib(mut self, lib: Option<OsString>) -> Self {
243 self.lib = lib;
244 self
245 }
246
247 pub fn platform(mut self, platform: Option<OsString>) -> Self {
254 self.platform = platform;
255 self
256 }
257
258 pub fn new_loader(self) -> DynamicLoader {
260 DynamicLoader {
261 root: self.root,
262 search_dirs: self.search_dirs,
263 search_dirs_override: self.search_dirs_override,
264 lib: self.lib,
265 platform: self.platform,
266 page_size: self.page_size,
267 libc: self.libc,
268 }
269 }
270}
271
272impl Default for LoaderOptions {
273 fn default() -> Self {
274 Self::new()
275 }
276}
277
278pub struct DynamicLoader {
282 root: PathBuf,
283 search_dirs: Vec<PathBuf>,
284 search_dirs_override: Vec<PathBuf>,
285 lib: Option<OsString>,
286 platform: Option<OsString>,
287 pub(crate) page_size: u64,
288 libc: Libc,
289}
290
291impl DynamicLoader {
292 pub fn options() -> LoaderOptions {
294 LoaderOptions::new()
295 }
296
297 pub fn resolve_dependencies<P: Into<PathBuf>>(
301 &self,
302 file: P,
303 tree: &mut DependencyTree,
304 ) -> Result<Vec<PathBuf>, Error> {
305 let dependent_file: PathBuf = file.into();
306 if tree.contains(&dependent_file) {
307 return Ok(Default::default());
308 }
309 let dependent_file = if dependent_file.strip_prefix(&self.root).is_err() {
310 let relative = dependent_file
311 .strip_prefix("/")
312 .unwrap_or(dependent_file.as_path());
313 self.root.join(relative)
314 } else {
315 dependent_file
316 };
317 let mut dependencies: Vec<PathBuf> = Vec::new();
318 let mut file = File::open(&dependent_file)?;
319 let elf = Elf::read_unchecked(&mut file, self.page_size)?;
320 let dynstr_table = elf
321 .read_dynamic_string_table(&mut file)?
322 .unwrap_or_default();
323 let Some(dynamic_table) = elf.read_dynamic_table(&mut file)? else {
324 tree.insert(dependent_file, Default::default());
326 return Ok(Default::default());
327 };
328 let interpreter = elf
329 .read_interpreter(&mut file)?
330 .map(|interpreter| PathBuf::from(OsString::from_vec(interpreter.into_bytes())));
331 let mut search_dirs = Vec::new();
332 let runpath = dynamic_table.get(DynamicTag::Runpath);
333 let rpath = dynamic_table.get(DynamicTag::Rpath);
334 let override_dirs = match self.libc {
335 Libc::Glibc => runpath.is_some(),
336 Libc::Musl => true,
337 };
338 if override_dirs {
339 search_dirs.extend_from_slice(self.search_dirs_override.as_slice());
341 }
342 let mut extend_search_dirs = |path: &CStr| {
343 search_dirs.extend(split_paths(OsStr::from_bytes(path.to_bytes())).map(|dir| {
344 let path = interpolate(
345 &dir,
346 &dependent_file,
347 &elf,
348 self.lib.as_deref(),
349 self.platform.as_deref(),
350 );
351 if !path.starts_with(&self.root) {
353 match path.strip_prefix("/") {
354 Ok(relative) => self.root.join(relative),
355 Err(_) => path,
356 }
357 } else {
358 path
359 }
360 }));
361 };
362 match self.libc {
363 Libc::Glibc => {
364 runpath
366 .and_then(|string_offset| dynstr_table.get_string(string_offset as usize))
367 .map(&mut extend_search_dirs)
368 .or_else(|| {
369 rpath
376 .and_then(|string_offset| {
377 dynstr_table.get_string(string_offset as usize)
378 })
379 .map(&mut extend_search_dirs)
380 });
381 }
382 Libc::Musl => [rpath, runpath]
383 .into_iter()
384 .flatten()
385 .filter_map(|string_offset| dynstr_table.get_string(string_offset as usize))
386 .for_each(&mut extend_search_dirs),
387 }
388 search_dirs.extend_from_slice(self.search_dirs.as_slice());
390 trace!("Search directories for {dependent_file:?}: {search_dirs:?}");
391 'outer: for (tag, value) in dynamic_table.iter() {
392 if *tag != DynamicTag::Needed {
393 continue;
394 }
395 let Some(dep_name) = dynstr_table.get_string(*value as usize) else {
396 continue;
397 };
398 trace!("{:?} depends on {:?}", dependent_file, dep_name);
399 for dir in search_dirs.iter() {
400 let path = dir.join(OsStr::from_bytes(dep_name.to_bytes()));
401 let mut file = match File::open(&path) {
402 Ok(file) => file,
403 Err(ref e) if e.kind() == ErrorKind::NotFound => continue,
404 Err(e) => {
405 warn!("Failed to open {path:?}: {e}");
406 continue;
407 }
408 };
409 let dep = match Elf::read_unchecked(&mut file, self.page_size) {
410 Ok(dep) => dep,
411 Err(elb::Error::NotElf) => continue,
412 Err(e) => return Err(e.into()),
413 };
414 if dep.header.byte_order == elf.header.byte_order
415 && dep.header.class == elf.header.class
416 && dep.header.machine == elf.header.machine
417 {
418 trace!("Resolved {:?} as {:?}", dep_name, path);
419 if Some(path.as_path()) != interpreter.as_deref() {
420 dependencies.push(path);
421 }
422 continue 'outer;
423 }
424 }
425 return Err(Error::FailedToResolve(dep_name.into(), dependent_file));
426 }
427 if let Some(interpreter) = interpreter {
428 if !dependencies.contains(&interpreter) {
429 dependencies.push(interpreter);
430 }
431 }
432 tree.insert(dependent_file, dependencies.clone());
433 dependencies.retain(|dep| !tree.contains(dep));
434 Ok(dependencies)
435 }
436}
437
438pub fn get_search_dirs_from_env() -> Vec<PathBuf> {
444 std::env::var_os("LD_LIBRARY_PATH")
445 .map(|path| split_paths(&path).collect())
446 .unwrap_or_default()
447}
448
449fn interpolate(
450 dir: &Path,
451 file: &Path,
452 elf: &Elf,
453 lib: Option<&OsStr>,
454 platform: Option<&OsStr>,
455) -> PathBuf {
456 use Component::*;
457 let mut interpolated = PathBuf::new();
458 for comp in dir.components() {
459 match comp {
460 Normal(comp) if comp == "$ORIGIN" || comp == "${ORIGIN}" => {
461 if let Some(parent) = file.parent() {
462 interpolated.push(parent);
463 } else {
464 interpolated.push(comp);
465 }
466 }
467 Normal(comp) if comp == "$LIB" || comp == "${LIB}" => {
468 let lib = match lib {
469 Some(lib) => lib,
470 None => match elf.header.class {
471 Class::Elf32 => OsStr::new("lib"),
472 Class::Elf64 => OsStr::new("lib64"),
473 },
474 };
475 interpolated.push(lib);
476 }
477 Normal(comp) if comp == "$PLATFORM" || comp == "${PLATFORM}" => {
478 if let Some(platform) = platform {
479 interpolated.push(platform);
480 } else {
481 let platform = match elf.header.machine {
482 Machine::X86_64 => "x86_64",
483 _ => {
484 warn!(
485 "Failed to interpolate $PLATFORM, machine is {:?} ({})",
486 elf.header.machine,
487 elf.header.machine.as_u16()
488 );
489 interpolated.push(comp);
490 continue;
491 }
492 };
493 interpolated.push(platform);
494 }
495 }
496 comp => interpolated.push(comp),
497 }
498 }
499 interpolated
500}