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 search_dirs: Vec<PathBuf>,
139 search_dirs_override: Vec<PathBuf>,
140 lib: Option<OsString>,
141 platform: Option<OsString>,
142 page_size: u64,
143 libc: Libc,
144}
145
146impl LoaderOptions {
147 pub fn new() -> Self {
149 Self {
150 search_dirs: Default::default(),
151 search_dirs_override: Default::default(),
152 lib: None,
153 platform: None,
154 page_size: 4096,
155 libc: Default::default(),
156 }
157 }
158
159 #[cfg(feature = "glibc")]
161 pub fn glibc<P: AsRef<Path>>(rootfs_dir: P) -> Result<Self, std::io::Error> {
162 Ok(Self {
163 search_dirs: crate::glibc::get_search_dirs(rootfs_dir)?,
164 search_dirs_override: get_search_dirs_from_env(),
165 libc: Libc::Glibc,
166 ..Default::default()
167 })
168 }
169
170 #[cfg(feature = "musl")]
172 pub fn musl<P: AsRef<Path>>(rootfs_dir: P, arch: &str) -> Result<Self, std::io::Error> {
173 Ok(Self {
174 search_dirs: crate::musl::get_search_dirs(rootfs_dir, arch)?,
175 search_dirs_override: get_search_dirs_from_env(),
176 libc: Libc::Musl,
177 ..Default::default()
178 })
179 }
180
181 pub fn libc(mut self, libc: Libc) -> Self {
188 self.libc = libc;
189 self
190 }
191
192 pub fn search_dirs(mut self, search_dirs: Vec<PathBuf>) -> Self {
199 self.search_dirs = search_dirs;
200 self
201 }
202
203 pub fn search_dirs_override(mut self, search_dirs: Vec<PathBuf>) -> Self {
209 self.search_dirs_override = search_dirs;
210 self
211 }
212
213 pub fn page_size(mut self, page_size: u64) -> Self {
217 assert!(page_size.is_power_of_two());
218 self.page_size = page_size;
219 self
220 }
221
222 pub fn lib(mut self, lib: Option<OsString>) -> Self {
229 self.lib = lib;
230 self
231 }
232
233 pub fn platform(mut self, platform: Option<OsString>) -> Self {
240 self.platform = platform;
241 self
242 }
243
244 pub fn new_loader(self) -> DynamicLoader {
246 DynamicLoader {
247 search_dirs: self.search_dirs,
248 search_dirs_override: self.search_dirs_override,
249 lib: self.lib,
250 platform: self.platform,
251 page_size: self.page_size,
252 libc: self.libc,
253 }
254 }
255}
256
257impl Default for LoaderOptions {
258 fn default() -> Self {
259 Self::new()
260 }
261}
262
263pub struct DynamicLoader {
267 search_dirs: Vec<PathBuf>,
268 search_dirs_override: Vec<PathBuf>,
269 lib: Option<OsString>,
270 platform: Option<OsString>,
271 pub(crate) page_size: u64,
272 libc: Libc,
273}
274
275impl DynamicLoader {
276 pub fn options() -> LoaderOptions {
278 LoaderOptions::new()
279 }
280
281 pub fn resolve_dependencies<P: Into<PathBuf>>(
285 &self,
286 file: P,
287 tree: &mut DependencyTree,
288 ) -> Result<Vec<PathBuf>, Error> {
289 let dependent_file: PathBuf = file.into();
290 if tree.contains(&dependent_file) {
291 return Ok(Default::default());
292 }
293 let mut dependencies: Vec<PathBuf> = Vec::new();
294 let mut file = File::open(&dependent_file)?;
295 let elf = Elf::read(&mut file, self.page_size)?;
296 let dynstr_table = elf
297 .read_dynamic_string_table(&mut file)?
298 .unwrap_or_default();
299 let Some(dynamic_table) = elf.read_dynamic_table(&mut file)? else {
300 tree.insert(dependent_file, Default::default());
302 return Ok(Default::default());
303 };
304 let interpreter = elf
305 .read_interpreter(&mut file)?
306 .map(|c_str| PathBuf::from(OsString::from_vec(c_str.into_bytes())));
307 let mut search_dirs = Vec::new();
308 let runpath = dynamic_table.get(DynamicTag::Runpath);
309 let rpath = dynamic_table.get(DynamicTag::Rpath);
310 let override_dirs = match self.libc {
311 Libc::Glibc => runpath.is_some(),
312 Libc::Musl => true,
313 };
314 if override_dirs {
315 search_dirs.extend_from_slice(self.search_dirs_override.as_slice());
317 }
318 let mut extend_search_dirs = |path: &CStr| {
319 search_dirs.extend(split_paths(OsStr::from_bytes(path.to_bytes())).map(|dir| {
320 interpolate(
321 &dir,
322 &dependent_file,
323 &elf,
324 self.lib.as_deref(),
325 self.platform.as_deref(),
326 )
327 }));
328 };
329 match self.libc {
330 Libc::Glibc => {
331 runpath
333 .and_then(|string_offset| dynstr_table.get_string(string_offset as usize))
334 .map(&mut extend_search_dirs)
335 .or_else(|| {
336 rpath
343 .and_then(|string_offset| {
344 dynstr_table.get_string(string_offset as usize)
345 })
346 .map(&mut extend_search_dirs)
347 });
348 }
349 Libc::Musl => [rpath, runpath]
350 .into_iter()
351 .flatten()
352 .filter_map(|string_offset| dynstr_table.get_string(string_offset as usize))
353 .for_each(&mut extend_search_dirs),
354 }
355 search_dirs.extend_from_slice(self.search_dirs.as_slice());
357 'outer: for (tag, value) in dynamic_table.iter() {
358 if *tag != DynamicTag::Needed {
359 continue;
360 }
361 let Some(dep_name) = dynstr_table.get_string(*value as usize) else {
362 continue;
363 };
364 trace!("{:?} depends on {:?}", dependent_file, dep_name);
365 for dir in search_dirs.iter() {
366 let path = dir.join(OsStr::from_bytes(dep_name.to_bytes()));
367 let mut file = match File::open(&path) {
368 Ok(file) => file,
369 Err(ref e) if e.kind() == ErrorKind::NotFound => continue,
370 Err(e) => {
371 warn!("Failed to open {path:?}: {e}");
372 continue;
373 }
374 };
375 let dep = match Elf::read_unchecked(&mut file, self.page_size) {
376 Ok(dep) => dep,
377 Err(elb::Error::NotElf) => continue,
378 Err(e) => return Err(e.into()),
379 };
380 if dep.header.byte_order == elf.header.byte_order
381 && dep.header.class == elf.header.class
382 && dep.header.machine == elf.header.machine
383 {
384 trace!("Resolved {:?} as {:?}", dep_name, path);
385 if Some(path.as_path()) != interpreter.as_deref() {
386 dependencies.push(path);
387 }
388 continue 'outer;
389 }
390 }
391 return Err(Error::FailedToResolve(dep_name.into(), dependent_file));
392 }
393 if let Some(interpreter) = interpreter {
394 if !dependencies.contains(&interpreter) {
395 dependencies.push(interpreter);
396 }
397 }
398 tree.insert(dependent_file, dependencies.clone());
399 dependencies.retain(|dep| !tree.contains(dep));
400 Ok(dependencies)
401 }
402}
403
404pub fn get_search_dirs_from_env() -> Vec<PathBuf> {
410 std::env::var_os("LD_LIBRARY_PATH")
411 .map(|path| split_paths(&path).collect())
412 .unwrap_or_default()
413}
414
415fn interpolate(
416 dir: &Path,
417 file: &Path,
418 elf: &Elf,
419 lib: Option<&OsStr>,
420 platform: Option<&OsStr>,
421) -> PathBuf {
422 use Component::*;
423 let mut interpolated = PathBuf::new();
424 for comp in dir.components() {
425 match comp {
426 Normal(comp) if comp == "$ORIGIN" || comp == "${ORIGIN}" => {
427 if let Some(parent) = file.parent() {
428 interpolated.push(parent);
429 } else {
430 interpolated.push(comp);
431 }
432 }
433 Normal(comp) if comp == "$LIB" || comp == "${LIB}" => {
434 let lib = match lib {
435 Some(lib) => lib,
436 None => match elf.header.class {
437 Class::Elf32 => OsStr::new("lib"),
438 Class::Elf64 => OsStr::new("lib64"),
439 },
440 };
441 interpolated.push(lib);
442 }
443 Normal(comp) if comp == "$PLATFORM" || comp == "${PLATFORM}" => {
444 if let Some(platform) = platform {
445 interpolated.push(platform);
446 } else {
447 let platform = match elf.header.machine {
448 Machine::X86_64 => "x86_64",
449 _ => {
450 warn!(
451 "Failed to interpolate $PLATFORM, machine is {:?} ({})",
452 elf.header.machine,
453 elf.header.machine.as_u16()
454 );
455 interpolated.push(comp);
456 continue;
457 }
458 };
459 interpolated.push(platform);
460 }
461 }
462 comp => interpolated.push(comp),
463 }
464 }
465 interpolated
466}