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}