Skip to main content

dynlink_posix/symtab/
handle.rs

1use std::{error, ffi, fmt, os::unix::ffi::OsStrExt};
2
3use pointersized::PointerSized;
4use smallvec;
5
6use crate::symtab::PosixSymbol;
7
8/// Lazy symbol resolution option.
9///
10/// Relocations will be performed at an implementation-defined time, ranging from the time
11/// of the `PosixHandle::openc` call until the first reference to a given symbol occurs.
12///
13/// Specifying `RTLD_LAZY` should improve performance on implementations supporting dynamic
14/// symbol binding since a process might not reference all of the symbols in a symbol table.
15/// And, for systems supporting dynamic symbol resolution for normal process execution,
16/// this behavior mimics the normal handling of process execution.
17///
18/// # Notes
19///
20/// Conflicts with `RTLD_NOW`.
21pub const RTLD_LAZY: ffi::c_int = libc::RTLD_LAZY;
22
23/// Eager symbol resolution option.
24///
25/// All necessary relocations will be performed when the symbol table is first loaded.
26///
27/// This may waste some processing if relocations are performed for symbols that are never referenced.
28/// This behavior may be useful for applications that need to know that all symbols referenced
29/// during execution will be available before `PosixHandle::openc` returns.
30///
31/// # Notes
32///
33/// Conflicts with `RTLD_LAZY`.
34pub const RTLD_NOW: ffi::c_int = libc::RTLD_NOW;
35
36/// Global symbol visibility.
37///
38/// The shared object file's symbols will be made available for relocation processing
39/// of any other executable object file.
40///
41/// # Notes
42///
43/// Conflicts with `RTLD_LOCAL`.
44pub const RTLD_GLOBAL: ffi::c_int = libc::RTLD_GLOBAL;
45
46/// Local symbol visibility.
47///
48/// The shared object file's symbols will not be made available for relocation processing
49/// of any other executable object file.
50///
51/// # Notes
52///
53/// Conflicts with: `RTLD_GLOBAL`.
54pub const RTLD_LOCAL: ffi::c_int = libc::RTLD_LOCAL;
55
56/// Represents a system message with diagnostic information.
57pub struct PosixSystemMessage(pub(super) ffi::CString);
58
59impl PosixSystemMessage {
60    /// Creates owned message cloned from c-str.
61    pub(super) fn clone_from_str(msg: &ffi::CStr) -> Self {
62        Self(msg.to_owned())
63    }
64}
65
66impl Clone for PosixSystemMessage {
67    fn clone(&self) -> Self {
68        Self(self.0.clone())
69    }
70}
71
72impl fmt::Debug for PosixSystemMessage {
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        f.write_fmt(format_args!("{:?}", &self.0))
75    }
76}
77
78impl fmt::Display for PosixSystemMessage {
79    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80        f.write_fmt(format_args!("{}", self.0.to_string_lossy()))
81    }
82}
83
84/// Represents an error that occurred during dynamic linking processing.
85pub enum PosixLinkingError {
86    System(PosixSystemMessage),
87    Unknown,
88}
89
90impl PosixLinkingError {
91    /// Creates owned error cloned from c-str.
92    pub(super) fn clone_from_str(msg: &ffi::CStr) -> Self {
93        Self::System(PosixSystemMessage::clone_from_str(msg))
94    }
95
96    /// Creates owned error cloned from raw c-str pointer.
97    pub(super) unsafe fn clone_from_ptr(msg: *const ffi::c_char) -> Self {
98        if !msg.is_null() {
99            Self::clone_from_str(ffi::CStr::from_ptr(msg))
100        } else {
101            Self::Unknown
102        }
103    }
104}
105
106impl Clone for PosixLinkingError {
107    fn clone(&self) -> Self {
108        match self {
109            Self::System(msg) => Self::System(msg.clone()),
110            Self::Unknown => Self::Unknown,
111        }
112    }
113}
114
115impl fmt::Debug for PosixLinkingError {
116    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117        match self {
118            Self::System(msg) => f.write_fmt(format_args!("System({:?})", msg)),
119            Self::Unknown => f.write_str("Unknown"),
120        }
121    }
122}
123
124impl fmt::Display for PosixLinkingError {
125    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126        match self {
127            PosixLinkingError::System(msg) => {
128                f.write_fmt(format_args!("Error occurred dynamic linking: {}", msg))
129            }
130
131            PosixLinkingError::Unknown => f.write_str("Error occurred dynamic linking: unknown"),
132        }
133    }
134}
135
136impl error::Error for PosixLinkingError {}
137
138/// Represents an opaque handle of a shared object file's symbol table.
139///
140/// # Usage
141///
142/// `PosixHandle` is used to symbol lookup.
143///
144/// ```no_run
145/// use dynlink_posix::symtab::{PosixHandle, PosixSymbol};
146///
147/// // sum.c
148/// //
149/// // int sum_of(int a, int b) {
150/// //    return a + b;
151/// // }
152///
153/// fn main() {
154///     unsafe {
155///         let handle = PosixHandle::open("libsum.so")
156///             .expect("libsum handle was not opened");
157///
158///         let symbol = handle.lookup::<extern "C" fn(i32, i32) -> i32>("sum_of")
159///             .expect("sum_of symbol was not found");
160///
161///         let _ = handle.lookup::<extern "C" fn(i32, i32) -> i32>("unknown")
162///             .expect_err("unknown symbol was found");
163///     }
164/// }
165/// ```
166///
167/// Options of symbol resolution and visibility can be specified if necessary.
168///
169/// ```no_run
170/// use dynlink_posix::symtab::{PosixHandle, PosixSymbol, RTLD_LAZY, RTLD_LOCAL};
171///
172/// // sum.c
173/// //
174/// // int sum_of(int a, int b) {
175/// //    return a + b;
176/// // }
177///
178/// fn main() {
179///     unsafe {
180///         let _ = PosixHandle::openc(c"libsum.so", RTLD_LOCAL | RTLD_LAZY)
181///             .expect("libsum handle was not opened");
182///     }
183/// }
184/// ```
185///
186/// # Safety
187///
188/// Shared object initialization routines that are executed when a
189/// `PosixHandle::open` or `PosixHandle::openc` is called may contain
190/// undefined behavior (UB).
191///
192/// The thread-safety of `PosixHandle` depends on the `dlfcn` implementation.
193/// It is thread-safe only if the implementations of `dlopen`, `dlsym`, `dlclose`, and `dlerror` are thread-safe.
194pub struct PosixHandle(pub(super) *mut ffi::c_void);
195
196impl PosixHandle {
197    /// Opens shared object file specified by `path` with default options and loads
198    /// it into the process address space and returns an owned handle.
199    ///
200    /// # Safety
201    ///
202    /// Shared object initialization routines that are executed when this
203    /// function is called may be UB.
204    pub unsafe fn open(path: impl AsRef<ffi::OsStr>) -> Result<Self, PosixLinkingError> {
205        let path_bytes = path.as_ref().as_bytes();
206        let options = RTLD_LAZY | RTLD_LOCAL;
207
208        match ffi::CStr::from_bytes_until_nul(path_bytes) {
209            Ok(cpath) => Self::openc(cpath, options),
210            Err(_) => {
211                const PATH_ESTIMATED_MAX_LEN: usize = 4096;
212
213                let mut buf =
214                    smallvec::SmallVec::<[u8; PATH_ESTIMATED_MAX_LEN]>::from_slice(path_bytes);
215                buf.push(0);
216
217                let cpath = unsafe { ffi::CStr::from_bytes_with_nul_unchecked(&buf) };
218                Self::openc(cpath, options)
219            }
220        }
221    }
222
223    /// Opens shared object file specified by null-terminated `path` and loads it into the process address
224    /// space according to `options` and returns an owned handle.
225    ///
226    /// # Safety
227    ///
228    /// Shared object initialization routines that are executed when this
229    /// function is called may be UB.
230    pub unsafe fn openc(path: &ffi::CStr, options: ffi::c_int) -> Result<Self, PosixLinkingError> {
231        #[cfg(target_os = "freebsd")]
232        let _ = libc::dlerror();
233
234        let handle = libc::dlopen(path.as_ptr(), options);
235
236        if !handle.is_null() {
237            Ok(Self(handle))
238        } else {
239            let err = libc::dlerror();
240            Err(PosixLinkingError::clone_from_ptr(err))
241        }
242    }
243
244    /// Looks up a symbol from the shared object file's symbol table by name.
245    ///
246    /// # Safety
247    ///
248    /// Type `T` must be ABI compatible with the type of symbol from the shared object.
249    pub unsafe fn lookup<T: pointersized::PointerSized>(
250        &self,
251        symbol: &str,
252    ) -> Result<PosixSymbol<'_, T>, PosixLinkingError> {
253        let symbol_bytes = symbol.as_bytes();
254
255        match ffi::CStr::from_bytes_until_nul(symbol_bytes) {
256            Ok(csymbol) => self.lookupc(csymbol),
257            Err(_) => {
258                const SYMBOL_ESTIMATED_MAX_LEN: usize = 4096;
259
260                let mut buf =
261                    smallvec::SmallVec::<[u8; SYMBOL_ESTIMATED_MAX_LEN]>::from_slice(symbol_bytes);
262                buf.push(0);
263
264                let csymbol = unsafe { ffi::CStr::from_bytes_with_nul_unchecked(&buf) };
265                self.lookupc(csymbol)
266            }
267        }
268    }
269
270    /// Looks up a symbol from the shared object file's symbol table by null-terminated name.
271    ///
272    /// # Safety
273    ///
274    /// Type `T` must be ABI compatible with the type of symbol from the shared object.
275    pub unsafe fn lookupc<T: PointerSized>(
276        &self,
277        symbol: &ffi::CStr,
278    ) -> Result<PosixSymbol<'_, T>, PosixLinkingError> {
279        #[cfg(target_os = "freebsd")]
280        let _ = libc::dlerror();
281
282        let ptr = libc::dlsym(self.0, symbol.as_ptr());
283
284        #[cfg(any(
285            target_os = "linux",
286            target_os = "android",
287            target_os = "macos",
288            target_os = "ios",
289            target_os = "openbsd",
290            target_os = "netbsd",
291            target_os = "dragonfly",
292            target_os = "solaris",
293            target_os = "illumos",
294            target_os = "haiku",
295        ))]
296        if !ptr.is_null() {
297            Ok(PosixSymbol::from_ptr(ptr))
298        } else {
299            let err = libc::dlerror();
300            if err.is_null() {
301                Ok(PosixSymbol::from_ptr(ptr))
302            } else {
303                Err(PosixLinkingError::clone_from_ptr(err))
304            }
305        }
306
307        #[cfg(target_os = "freebsd")]
308        if !ptr.is_null() {
309            Ok(PosixSymbol::from_ptr(ptr))
310        } else {
311            let err = libc::dlerror();
312            Err(PosixLinkingError::clone_from_ptr(err))
313        }
314    }
315}
316
317unsafe impl Send for PosixHandle {}
318unsafe impl Sync for PosixHandle {}
319
320impl Drop for PosixHandle {
321    fn drop(&mut self) {
322        unsafe { libc::dlclose(self.0) };
323    }
324}
325
326impl fmt::Debug for PosixHandle {
327    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
328        f.write_fmt(format_args!("PosixHandle({:p})", self.0))
329    }
330}
331
332#[cfg(test)]
333mod unittest {
334    use crate::symtab::PosixHandle;
335
336    pub fn assert_send<T: Send>() {}
337    pub fn assert_sync<T: Sync>() {}
338
339    #[test]
340    pub fn posix_handle_marked_as_send_test() {
341        assert_send::<PosixHandle>();
342    }
343
344    #[test]
345    pub fn posix_handle_marked_as_sync_test() {
346        assert_sync::<PosixHandle>();
347    }
348}