1use crate::{
2 loader::{builtin, create_lazy_scope, deal_unknown, Dylib, ElfLibrary},
3 register::{register, DylibState, MANAGER},
4 OpenFlags, Result,
5};
6use alloc::{borrow::ToOwned, sync::Arc, vec::Vec};
7use elf_loader::RelocatedDylib;
8
9impl ElfLibrary {
10 #[cfg(feature = "std")]
21 #[inline]
22 pub fn dlopen(path: impl AsRef<std::ffi::OsStr>, flags: OpenFlags) -> Result<Dylib> {
23 dlopen_impl(path.as_ref().to_str().unwrap(), flags, || {
24 ElfLibrary::from_file(path.as_ref(), flags)
25 })
26 }
27
28 #[inline]
31 pub fn dlopen_from_binary(
32 bytes: &[u8],
33 path: impl AsRef<str>,
34 flags: OpenFlags,
35 ) -> Result<Dylib> {
36 dlopen_impl(path.as_ref(), flags, || {
37 ElfLibrary::from_binary(bytes, path.as_ref(), flags)
38 })
39 }
40}
41
42struct Recycler {
43 is_recycler: bool,
44 old_all_len: usize,
45 old_global_len: usize,
46}
47
48impl Drop for Recycler {
49 fn drop(&mut self) {
50 if self.is_recycler {
51 log::debug!("Destroying newly added dynamic libraries");
52 let mut lock = MANAGER.write();
53 lock.all.truncate(self.old_all_len);
54 lock.global.truncate(self.old_global_len);
55 }
56 }
57}
58
59fn dlopen_impl(
60 path: &str,
61 mut flags: OpenFlags,
62 f: impl Fn() -> Result<ElfLibrary>,
63) -> Result<Dylib> {
64 let shortname = path.split('/').last().unwrap();
65 log::info!("dlopen: Try to open [{}] with [{:?}] ", path, flags);
66 let reader = MANAGER.read();
67 let mut new_libs = Vec::new();
69 let core = if let Some(lib) = reader.all.get(shortname) {
71 if lib.deps().is_some()
72 && !flags
73 .difference(lib.flags())
74 .contains(OpenFlags::RTLD_GLOBAL)
75 {
76 return Ok(lib.get_dylib());
77 }
78 lib.relocated_dylib()
79 } else {
80 let lib = f()?;
81 let core = lib.dylib.core_component().clone();
82 new_libs.push(Some(lib));
83 unsafe { RelocatedDylib::from_core_component(core) }
84 };
85
86 drop(reader);
87
88 if flags.contains(OpenFlags::CUSTOM_NOT_REGISTER) {
89 log::warn!("dlopen ignores the open flag CUSTOM_NOT_REGISTER");
90 flags.remove(OpenFlags::CUSTOM_NOT_REGISTER);
91 }
92
93 let mut recycler = Recycler {
94 is_recycler: true,
95 old_all_len: usize::MAX,
96 old_global_len: usize::MAX,
97 };
98
99 let mut dep_libs = Vec::new();
101 let mut cur_pos = 0;
102 dep_libs.push(core);
103 let mut lock = MANAGER.write();
104 recycler.old_all_len = lock.all.len();
105 recycler.old_global_len = lock.global.len();
106
107 #[cfg(feature = "std")]
108 let mut cur_newlib_pos = 0;
109 while cur_pos < dep_libs.len() {
111 let lib_names: &[&str] = unsafe { core::mem::transmute(dep_libs[cur_pos].needed_libs()) };
112 #[cfg(feature = "std")]
113 let mut cur_rpath = None;
114 for lib_name in lib_names {
115 if let Some(lib) = lock.all.get_mut(*lib_name) {
116 if !lib.state.is_used() {
117 lib.state.set_used();
118 dep_libs.push(lib.relocated_dylib());
119 log::debug!("Use an existing dylib: [{}]", lib.shortname());
120 if flags
121 .difference(lib.flags())
122 .contains(OpenFlags::RTLD_GLOBAL)
123 {
124 let shortname = lib.shortname().to_owned();
125 log::debug!(
126 "Trying to update a library. Name: [{}] Old flags:[{:?}] New flags:[{:?}]",
127 shortname,
128 lib.flags(),
129 flags
130 );
131 lib.set_flags(flags);
132 let core = lib.relocated_dylib();
133 lock.global.insert(shortname, core);
134 }
135 }
136 continue;
137 }
138
139 #[cfg(feature = "std")]
140 {
141 let rpath = if let Some(rpath) = &cur_rpath {
142 rpath
143 } else {
144 let parent_lib = new_libs[cur_newlib_pos].as_ref().unwrap();
145 cur_rpath = Some(
146 parent_lib
147 .dylib
148 .rpath()
149 .map(|rpath| imp::fixup_rpath(parent_lib.name(), rpath))
150 .unwrap_or(Box::new([])),
151 );
152 cur_newlib_pos += 1;
153 unsafe { cur_rpath.as_ref().unwrap_unchecked() }
154 };
155
156 imp::find_library(rpath, lib_name, |file, file_path| {
157 let new_lib =
158 ElfLibrary::from_open_file(file, file_path.to_str().unwrap(), flags)?;
159 let inner = new_lib.dylib.core_component().clone();
160 register(
161 unsafe { RelocatedDylib::from_core_component(inner.clone()) },
162 flags,
163 None,
164 &mut lock,
165 *DylibState::default()
166 .set_used()
167 .set_new_idx(new_libs.len() as _),
168 );
169 dep_libs.push(unsafe { RelocatedDylib::from_core_component(inner) });
170 new_libs.push(Some(new_lib));
171 Ok(())
172 })?;
173 }
174
175 #[cfg(not(feature = "std"))]
176 return Err(crate::find_lib_error(alloc::format!(
177 "can not find file: {}",
178 lib_name
179 )));
180 }
181 cur_pos += 1;
182 }
183
184 #[derive(Clone, Copy)]
185 struct Item {
186 idx: usize,
187 next: usize,
188 }
189 let mut stack = Vec::new();
191 stack.push(Item { idx: 0, next: 0 });
192
193 'start: while let Some(mut item) = stack.pop() {
194 let names = new_libs[item.idx].as_ref().unwrap().needed_libs();
195 for name in names.iter().skip(item.next) {
196 let lib = lock.all.get_mut(*name).unwrap();
197 lib.state.set_unused();
198 let Some(idx) = lib.state.get_new_idx() else {
199 continue;
200 };
201 lib.state.set_relocated();
202 item.next += 1;
203 stack.push(item);
204 stack.push(Item {
205 idx: idx as usize,
206 next: 0,
207 });
208 continue 'start;
209 }
210 let iter = lock.global.values().chain(dep_libs.iter());
211 let reloc = |lib: ElfLibrary| {
212 log::debug!("Relocating dylib [{}]", lib.name());
213 let lazy_scope = create_lazy_scope(&dep_libs, lib.dylib.is_lazy());
214 lib.dylib.relocate(
215 iter,
216 &|name| builtin::BUILTIN.get(name).copied(),
217 deal_unknown,
218 lazy_scope,
219 )
220 };
221 reloc(core::mem::take(&mut new_libs[item.idx]).unwrap())?;
222 }
223
224 let deps = Arc::new(dep_libs.into_boxed_slice());
225 recycler.is_recycler = false;
226 let core = deps[0].clone();
227
228 let res = Dylib {
229 inner: core.clone(),
230 flags,
231 deps: Some(deps.clone()),
232 };
233 register(
235 core,
236 flags,
237 Some(deps),
238 &mut lock,
239 *DylibState::default().set_relocated(),
240 );
241 Ok(res)
242}
243
244#[cfg(feature = "std")]
245pub mod imp {
246 use crate::{find_lib_error, Result};
247 use core::str::FromStr;
248 use dynamic_loader_cache::{Cache as LdCache, Result as LdResult};
249 use spin::Lazy;
250 use std::path::PathBuf;
251
252 static LD_LIBRARY_PATH: Lazy<Box<[PathBuf]>> = Lazy::new(|| {
253 let library_path = std::env::var("LD_LIBRARY_PATH").unwrap_or(String::new());
254 deal_path(&library_path)
255 });
256 static DEFAULT_PATH: spin::Lazy<Box<[PathBuf]>> = Lazy::new(|| unsafe {
257 vec![
258 PathBuf::from_str("/lib").unwrap_unchecked(),
259 PathBuf::from_str("/usr/lib").unwrap_unchecked(),
260 ]
261 .into_boxed_slice()
262 });
263 static LD_CACHE: Lazy<Box<[PathBuf]>> = Lazy::new(|| {
264 build_ld_cache().unwrap_or_else(|err| {
265 log::warn!("Build ld cache failed: {}", err);
266 Box::new([])
267 })
268 });
269
270 #[inline]
271 fn build_ld_cache() -> LdResult<Box<[PathBuf]>> {
272 use std::collections::HashSet;
273
274 let cache = LdCache::load()?;
275 let unique_ld_foders = cache
276 .iter()?
277 .filter_map(LdResult::ok)
278 .map(|entry| {
279 entry.full_path.parent().unwrap().to_owned()
281 })
282 .collect::<HashSet<_>>();
283 Ok(Vec::from_iter(unique_ld_foders).into_boxed_slice())
284 }
285
286 #[inline]
287 pub(crate) fn fixup_rpath(lib_path: &str, rpath: &str) -> Box<[PathBuf]> {
288 if !rpath.contains('$') {
289 return deal_path(rpath);
290 }
291 for s in rpath.split('$').skip(1) {
292 if !s.starts_with("ORIGIN") && !s.starts_with("{ORIGIN}") {
293 log::warn!("DT_RUNPATH format is incorrect: [{}]", rpath);
294 return Box::new([]);
295 }
296 }
297 let dir = if let Some((path, _)) = lib_path.rsplit_once('/') {
298 path
299 } else {
300 "."
301 };
302 deal_path(&rpath.to_string().replace("$ORIGIN", dir))
303 }
304
305 #[inline]
306 fn deal_path(s: &str) -> Box<[PathBuf]> {
307 s.split(":")
308 .map(|str| std::path::PathBuf::try_from(str).unwrap())
309 .collect()
310 }
311
312 #[inline]
313 pub(crate) fn find_library(
314 cur_rpath: &Box<[PathBuf]>,
315 lib_name: &str,
316 mut f: impl FnMut(std::fs::File, &std::path::PathBuf) -> Result<()>,
317 ) -> Result<()> {
318 let search_paths = LD_LIBRARY_PATH
320 .iter()
321 .chain(cur_rpath.iter())
322 .chain(LD_CACHE.iter())
323 .chain(DEFAULT_PATH.iter());
324
325 for path in search_paths {
326 let file_path = path.join(lib_name);
327 log::trace!("Try to open dependency shared object: [{:?}]", file_path);
328 if let Ok(file) = std::fs::File::open(&file_path) {
329 match f(file, &file_path) {
330 Ok(_) => return Ok(()),
331 Err(err) => {
332 log::debug!("Cannot load dylib: [{:?}] reason: [{:?}]", file_path, err)
333 }
334 }
335 }
336 }
337 Err(find_lib_error(format!("can not find file: {}", lib_name)))
338 }
339}