dynamic_loader_cache/
lib.rs

1// Copyright 2024-2025 Koutheir Attouchi.
2// See the "LICENSE.txt" file at the top-level directory of this distribution.
3//
4// Licensed under the MIT license. This file may not be copied, modified,
5// or distributed except according to those terms.
6
7#![doc = include_str!("../README.md")]
8#![doc(html_root_url = "https://docs.rs/dynamic-loader-cache/0.2.3")]
9#![warn(
10    unsafe_op_in_unsafe_fn,
11    missing_docs,
12    keyword_idents,
13    macro_use_extern_crate,
14    missing_debug_implementations,
15    non_ascii_idents,
16    trivial_casts,
17    trivial_numeric_casts,
18    unstable_features,
19    unused_extern_crates,
20    unused_import_braces,
21    unused_labels,
22    variant_size_differences,
23    unused_qualifications,
24    clippy::must_use_candidate,
25    clippy::default_numeric_fallback,
26    clippy::single_char_lifetime_names,
27    clippy::alloc_instead_of_core,
28    clippy::std_instead_of_core,
29    clippy::std_instead_of_alloc
30)]
31// Activate these lints to clean up the code and hopefully detect some issues.
32/*
33#![warn(clippy::all, clippy::pedantic, clippy::restriction)]
34#![allow(
35    clippy::doc_markdown,
36    clippy::exhaustive_structs,
37    clippy::missing_inline_in_public_items,
38    clippy::implicit_return,
39    clippy::missing_docs_in_private_items,
40    clippy::missing_errors_doc,
41    clippy::question_mark_used,
42    clippy::unnecessary_wraps,
43    clippy::single_call_fn,
44    clippy::undocumented_unsafe_blocks,
45    clippy::shadow_reuse,
46    clippy::shadow_unrelated,
47    clippy::separated_literal_suffix,
48    clippy::expect_used,
49    clippy::unused_self,
50    clippy::mod_module_files,
51    clippy::pub_use,
52    clippy::module_name_repetitions,
53    clippy::indexing_slicing,
54    clippy::absolute_paths,
55    clippy::min_ident_chars,
56    clippy::impl_trait_in_params,
57    clippy::arbitrary_source_item_ordering,
58    clippy::missing_trait_methods,
59    clippy::integer_division_remainder_used,
60    clippy::big_endian_bytes,
61    clippy::little_endian_bytes,
62    clippy::host_endian_bytes,
63    clippy::unwrap_in_result,
64    clippy::print_stderr
65)]
66*/
67
68mod errors;
69pub mod glibc_ld_so_cache_1dot1;
70pub mod ld_elf_so_hints;
71pub mod ld_so_1dot7;
72pub mod ld_so_hints;
73#[cfg(test)]
74mod tests;
75mod utils;
76
77extern crate alloc;
78
79use alloc::borrow::Cow;
80use alloc::fmt;
81use core::ffi::CStr;
82use core::iter::FusedIterator;
83use core::mem::size_of;
84use std::ffi::OsStr;
85use std::path::Path;
86
87use arrayvec::ArrayVec;
88use static_assertions::const_assert;
89
90pub use crate::errors::Error;
91
92const CACHE_IMPL_COUNT: usize = 5;
93
94/// Result of a fallible operation.
95pub type Result<T> = core::result::Result<T, Error>;
96
97/// Supported data models.
98/// See: https://en.wikipedia.org/wiki/64-bit_computing#64-bit_data_models
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100enum DataModel {
101    /// c_int=i32 c_long=i32
102    ILP32,
103    /// c_int=i32 c_long=i64
104    LP64,
105}
106
107/// Cache entry.
108#[derive(Debug)]
109#[non_exhaustive]
110pub struct Entry<'cache> {
111    /// File name of the shared library.
112    pub file_name: Cow<'cache, OsStr>,
113    /// Absolute path of the shared library.
114    pub full_path: Cow<'cache, Path>,
115}
116
117impl<'cache> Entry<'cache> {
118    pub(crate) fn new_by_string_table_indices(
119        string_table: &utils::CStringTable<'cache>,
120        key: u32,
121        value: u32,
122    ) -> Result<Self> {
123        let key = string_table.get(key)?;
124        let value = string_table.get(value)?;
125
126        Self::cstr_entry_to_crate_entry(key, value)
127    }
128
129    #[cfg(unix)]
130    fn cstr_entry_to_crate_entry(key: &'cache CStr, value: &'cache CStr) -> Result<Self> {
131        let file_name = utils::os_str_from_cstr(key).map(Cow::Borrowed)?;
132        let full_path = utils::path_from_cstr(value).map(Cow::Borrowed)?;
133
134        Ok(Self {
135            file_name,
136            full_path,
137        })
138    }
139
140    #[cfg(not(unix))]
141    fn cstr_entry_to_crate_entry(key: &'cache CStr, value: &'cache CStr) -> Result<Self> {
142        let file_name = utils::os_string_from_cstr(key).map(Cow::Owned)?;
143        let full_path = utils::path_buf_from_cstr(value).map(Cow::Owned)?;
144
145        Ok(Self {
146            file_name,
147            full_path,
148        })
149    }
150}
151
152trait CacheProvider: fmt::Debug + Sync + Send {
153    fn entries_iter<'cache>(
154        &'cache self,
155    ) -> Result<Box<dyn FusedIterator<Item = Result<Entry<'cache>>> + 'cache>>;
156}
157
158#[derive(Debug)]
159enum CacheImpl {
160    LdSO1dot7(ld_so_1dot7::Cache),
161    GLibCLdSOCache1dot1(glibc_ld_so_cache_1dot1::Cache),
162    LdELFSOHints(ld_elf_so_hints::Cache),
163    LdSOHints(ld_so_hints::Cache),
164}
165
166impl AsRef<dyn CacheProvider> for CacheImpl {
167    fn as_ref(&self) -> &(dyn CacheProvider + 'static) {
168        match self {
169            Self::LdSO1dot7(cache) => cache,
170            Self::GLibCLdSOCache1dot1(cache) => cache,
171            Self::LdELFSOHints(cache) => cache,
172            Self::LdSOHints(cache) => cache,
173        }
174    }
175}
176
177/// Reader of the dynamic loader shared libraries cache.
178#[derive(Debug)]
179pub struct Cache {
180    caches: ArrayVec<CacheImpl, CACHE_IMPL_COUNT>,
181}
182
183impl Cache {
184    /// Load all dynamic loader caches supported and present on the system.
185    pub fn load() -> Result<Self> {
186        const_assert!(size_of::<u32>() <= size_of::<usize>());
187
188        let mut caches = ArrayVec::<CacheImpl, CACHE_IMPL_COUNT>::default();
189
190        if cfg!(target_os = "freebsd") {
191            Self::try_loading_ld_elf_so_hints(&mut caches)?;
192            Self::try_loading_ld_so_hints(&mut caches)?;
193            Self::try_loading_ld_so_1dot7(&mut caches)?;
194            Self::try_loading_glibc_ld_so_cache_1dot1(&mut caches)?;
195        } else if cfg!(any(target_os = "openbsd", target_os = "netbsd")) {
196            Self::try_loading_ld_so_hints(&mut caches)?;
197            Self::try_loading_ld_elf_so_hints(&mut caches)?;
198            Self::try_loading_ld_so_1dot7(&mut caches)?;
199            Self::try_loading_glibc_ld_so_cache_1dot1(&mut caches)?;
200        } else {
201            Self::try_loading_glibc_ld_so_cache_1dot1(&mut caches)?;
202            Self::try_loading_ld_elf_so_hints(&mut caches)?;
203            Self::try_loading_ld_so_hints(&mut caches)?;
204            Self::try_loading_ld_so_1dot7(&mut caches)?;
205        }
206
207        Ok(Self { caches })
208    }
209
210    fn try_loading_glibc_ld_so_cache_1dot1(
211        caches: &mut ArrayVec<CacheImpl, CACHE_IMPL_COUNT>,
212    ) -> Result<()> {
213        if let Ok(cache) = glibc_ld_so_cache_1dot1::Cache::load_default() {
214            caches.push(CacheImpl::GLibCLdSOCache1dot1(cache));
215        }
216        Ok(())
217    }
218
219    fn try_loading_ld_elf_so_hints(
220        caches: &mut ArrayVec<CacheImpl, CACHE_IMPL_COUNT>,
221    ) -> Result<()> {
222        for path in ld_elf_so_hints::CACHE_FILE_PATHS.iter().map(Path::new) {
223            if let Ok(cache) = ld_elf_so_hints::Cache::load(path) {
224                caches.push(CacheImpl::LdELFSOHints(cache));
225            }
226        }
227        Ok(())
228    }
229
230    fn try_loading_ld_so_hints(caches: &mut ArrayVec<CacheImpl, CACHE_IMPL_COUNT>) -> Result<()> {
231        if let Ok(cache) = ld_so_hints::Cache::load_default() {
232            caches.push(CacheImpl::LdSOHints(cache));
233        }
234        Ok(())
235    }
236
237    fn try_loading_ld_so_1dot7(caches: &mut ArrayVec<CacheImpl, CACHE_IMPL_COUNT>) -> Result<()> {
238        if let Ok(cache) = ld_so_1dot7::Cache::load_default() {
239            caches.push(CacheImpl::LdSO1dot7(cache));
240        }
241        Ok(())
242    }
243
244    /// Returns an iterator that returns the cache entries.
245    ///
246    /// The entries are aggregated from all dynamic loader caches that have been previously loaded.
247    pub fn iter(&self) -> Result<impl FusedIterator<Item = Result<Entry<'_>>> + '_> {
248        Ok(self
249            .caches
250            .iter()
251            .map(AsRef::as_ref)
252            .map(CacheProvider::entries_iter)
253            .collect::<Result<ArrayVec<_, CACHE_IMPL_COUNT>>>()?
254            .into_iter()
255            .flatten()
256            .fuse())
257    }
258}