dynlink/
context.rs

1//! Management of global context.
2
3use std::{collections::HashMap, fmt::Display};
4
5use petgraph::stable_graph::{NodeIndex, StableDiGraph};
6use stable_vec::StableVec;
7
8use crate::{
9    compartment::{Compartment, CompartmentId},
10    engines::ContextEngine,
11    library::{Library, LibraryId, UnloadedLibrary},
12    DynlinkError, DynlinkErrorKind,
13};
14
15mod deps;
16mod load;
17pub(crate) mod relocate;
18pub mod runtime;
19mod syms;
20
21pub use load::LoadIds;
22
23#[repr(C)]
24/// A dynamic linker context, the main state struct for this crate.
25pub struct Context {
26    // Implementation callbacks.
27    pub(crate) engine: Box<dyn ContextEngine + Send>,
28    // Track all the compartment names.
29    compartment_names: HashMap<String, usize>,
30    // Compartments get stable IDs from StableVec.
31    compartments: StableVec<Compartment>,
32
33    // This is the primary list of libraries, all libraries have an entry here, and they are
34    // placed here independent of compartment. Edges denote dependency relationships, and may also
35    // cross compartments.
36    pub(crate) library_deps: StableDiGraph<LoadedOrUnloaded, ()>,
37}
38
39// Libraries in the dependency graph are placed there before loading, so that they can participate
40// in dependency search. So we need to track both kinds of libraries that may be at a given index in
41// the graph.
42pub enum LoadedOrUnloaded {
43    Unloaded(UnloadedLibrary),
44    Loaded(Library),
45}
46
47impl Display for LoadedOrUnloaded {
48    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49        match self {
50            LoadedOrUnloaded::Unloaded(unlib) => write!(f, "(unloaded){}", unlib),
51            LoadedOrUnloaded::Loaded(lib) => write!(f, "(loaded){}", lib),
52        }
53    }
54}
55
56impl LoadedOrUnloaded {
57    /// Get the name of this library, loaded or unloaded.
58    pub fn name(&self) -> &str {
59        match self {
60            LoadedOrUnloaded::Unloaded(unlib) => &unlib.name,
61            LoadedOrUnloaded::Loaded(lib) => &lib.name,
62        }
63    }
64
65    /// Get back a reference to the underlying loaded library, if loaded.
66    pub fn loaded(&self) -> Option<&Library> {
67        match self {
68            LoadedOrUnloaded::Unloaded(_) => None,
69            LoadedOrUnloaded::Loaded(lib) => Some(lib),
70        }
71    }
72
73    /// Get back a mutable reference to the underlying loaded library, if loaded.
74    pub fn loaded_mut(&mut self) -> Option<&mut Library> {
75        match self {
76            LoadedOrUnloaded::Unloaded(_) => None,
77            LoadedOrUnloaded::Loaded(lib) => Some(lib),
78        }
79    }
80}
81
82impl Context {
83    /// Construct a new dynamic linker context.
84    pub fn new(engine: Box<dyn ContextEngine + Send>) -> Self {
85        Self {
86            engine,
87            compartment_names: HashMap::new(),
88            library_deps: StableDiGraph::new(),
89            compartments: StableVec::new(),
90        }
91    }
92
93    /// Replace the callback engine for this context.
94    pub fn replace_engine(&mut self, engine: Box<dyn ContextEngine + Send>) {
95        self.engine = engine;
96    }
97
98    /// Lookup a compartment by name.
99    pub fn lookup_compartment(&self, name: &str) -> Option<CompartmentId> {
100        Some(CompartmentId(*self.compartment_names.get(name)?))
101    }
102
103    /// Get a reference to a compartment back by ID.
104    pub fn get_compartment(&self, id: CompartmentId) -> Result<&Compartment, DynlinkError> {
105        if !self.compartments.has_element_at(id.0) {
106            return Err(DynlinkErrorKind::InvalidCompartmentId { id }.into());
107        }
108        Ok(&self.compartments[id.0])
109    }
110
111    /// Get a mut reference to a compartment back by ID.
112    pub fn get_compartment_mut(
113        &mut self,
114        id: CompartmentId,
115    ) -> Result<&mut Compartment, DynlinkError> {
116        if !self.compartments.has_element_at(id.0) {
117            return Err(DynlinkErrorKind::InvalidCompartmentId { id }.into());
118        }
119        Ok(&mut self.compartments[id.0])
120    }
121
122    /// Lookup a library by name
123    pub fn lookup_library(&self, comp: CompartmentId, name: &str) -> Option<LibraryId> {
124        let comp = self.get_compartment(comp).ok()?;
125        Some(LibraryId(*comp.library_names.get(name)?))
126    }
127
128    /// Get a reference to a library back by ID.
129    pub fn get_library(&self, id: LibraryId) -> Result<&Library, DynlinkError> {
130        if !self.library_deps.contains_node(id.0) {
131            return Err(DynlinkErrorKind::InvalidLibraryId { id }.into());
132        }
133        match &self.library_deps[id.0] {
134            LoadedOrUnloaded::Unloaded(unlib) => Err(DynlinkErrorKind::UnloadedLibrary {
135                library: unlib.name.to_string(),
136            }
137            .into()),
138            LoadedOrUnloaded::Loaded(lib) => Ok(lib),
139        }
140    }
141
142    /// Get a mut reference to a library back by ID.
143    pub fn get_library_mut(&mut self, id: LibraryId) -> Result<&mut Library, DynlinkError> {
144        if !self.library_deps.contains_node(id.0) {
145            return Err(DynlinkErrorKind::InvalidLibraryId { id }.into());
146        }
147        match &mut self.library_deps[id.0] {
148            LoadedOrUnloaded::Unloaded(unlib) => Err(DynlinkErrorKind::UnloadedLibrary {
149                library: unlib.name.to_string(),
150            }
151            .into()),
152            LoadedOrUnloaded::Loaded(lib) => Ok(lib),
153        }
154    }
155
156    /// Traverse the library graph with DFS postorder, calling the callback for each library.
157    pub fn with_dfs_postorder<R>(
158        &self,
159        root_id: LibraryId,
160        mut f: impl FnMut(&LoadedOrUnloaded) -> R,
161    ) -> Vec<R> {
162        let mut rets = vec![];
163        let mut visit = petgraph::visit::DfsPostOrder::new(&self.library_deps, root_id.0);
164        while let Some(node) = visit.next(&self.library_deps) {
165            let dep = &self.library_deps[node];
166            rets.push(f(dep))
167        }
168        rets
169    }
170
171    /// Traverse the library graph with DFS postorder, calling the callback for each library
172    /// (mutable ref).
173    pub fn with_dfs_postorder_mut<R>(
174        &mut self,
175        root_id: LibraryId,
176        mut f: impl FnMut(&mut LoadedOrUnloaded) -> R,
177    ) -> Vec<R> {
178        let mut rets = vec![];
179        let mut visit = petgraph::visit::DfsPostOrder::new(&self.library_deps, root_id.0);
180        while let Some(node) = visit.next(&self.library_deps) {
181            let dep = &mut self.library_deps[node];
182            rets.push(f(dep))
183        }
184        rets
185    }
186
187    /// Traverse the library graph with BFS, calling the callback for each library.
188    pub fn with_bfs(&self, root_id: LibraryId, mut f: impl FnMut(&LoadedOrUnloaded) -> bool) {
189        let mut visit = petgraph::visit::Bfs::new(&self.library_deps, root_id.0);
190        while let Some(node) = visit.next(&self.library_deps) {
191            let dep = &self.library_deps[node];
192            if !f(dep) {
193                return;
194            }
195        }
196    }
197
198    pub fn libraries(&self) -> LibraryIter<'_> {
199        LibraryIter { ctx: self, next: 0 }
200    }
201
202    pub(crate) fn add_library(&mut self, lib: UnloadedLibrary) -> NodeIndex {
203        self.library_deps.add_node(LoadedOrUnloaded::Unloaded(lib))
204    }
205
206    pub(crate) fn add_dep(&mut self, parent: NodeIndex, dep: NodeIndex) {
207        self.library_deps.add_edge(parent, dep, ());
208    }
209
210    pub fn add_manual_dependency(&mut self, parent: LibraryId, dependee: LibraryId) {
211        self.add_dep(parent.0, dependee.0);
212    }
213
214    pub fn unload_compartment(
215        &mut self,
216        comp_id: CompartmentId,
217    ) -> (Option<Compartment>, Vec<LoadedOrUnloaded>) {
218        let Ok(comp) = self.get_compartment(comp_id) else {
219            return (None, vec![]);
220        };
221        let name = comp.name.clone();
222        let ids = comp.library_ids();
223        let nodes = ids
224            .collect::<Vec<_>>()
225            .iter()
226            .filter_map(|id| self.library_deps.remove_node(id.0))
227            .collect();
228        self.compartment_names.remove(&name);
229        (self.compartments.remove(comp_id.0), nodes)
230    }
231
232    /// Create a new compartment with a given name.
233    pub fn add_compartment(
234        &mut self,
235        name: impl ToString,
236        new_comp_flags: NewCompartmentFlags,
237    ) -> Result<CompartmentId, DynlinkError> {
238        let name = name.to_string();
239        let idx = self.compartments.next_push_index();
240        let comp = Compartment::new(name.clone(), CompartmentId(idx), new_comp_flags);
241        self.compartments.push(comp);
242        tracing::debug!("added compartment {} with ID {}", name, idx);
243        self.compartment_names.insert(name, idx);
244        Ok(CompartmentId(idx))
245    }
246
247    /// Get a list of external compartments that the given compartment depends on.
248    pub fn compartment_dependencies(
249        &self,
250        id: CompartmentId,
251    ) -> Result<Vec<CompartmentId>, DynlinkError> {
252        let comp = self.get_compartment(id)?;
253        let mut deps = vec![];
254        for lib in comp.library_ids() {
255            for n in self.library_deps.neighbors(lib.0) {
256                let neigh = self.library_deps[n].loaded().unwrap();
257                deps.push(neigh.comp_id);
258            }
259        }
260        deps.sort_unstable();
261        deps.dedup();
262        if let Some(dep) = deps.iter().position(|dep| *dep == id) {
263            deps.remove(dep);
264        }
265        Ok(deps)
266    }
267}
268
269pub struct LibraryIter<'a> {
270    ctx: &'a Context,
271    next: usize,
272}
273
274impl<'a> Iterator for LibraryIter<'a> {
275    type Item = &'a Library;
276
277    fn next(&mut self) -> Option<Self::Item> {
278        loop {
279            let idx = self.ctx.library_deps.node_indices().nth(self.next)?;
280            self.next += 1;
281            let node = &self.ctx.library_deps[idx];
282            match node {
283                LoadedOrUnloaded::Unloaded(_) => {}
284                LoadedOrUnloaded::Loaded(lib) => return Some(lib),
285            }
286        }
287    }
288}
289
290bitflags::bitflags! {
291    #[derive(Clone, Copy, Debug)]
292    pub struct NewCompartmentFlags : u32 {
293        const EXPORT_GATES = 0x1;
294        const DEBUG = 0x2;
295    }
296}