@@ -51,51 +51,77 @@ mod tests;
5151
5252#[ cfg( unix) ]
5353mod dl {
54- use std:: ffi:: { CStr , CString , OsStr } ;
54+ use std:: ffi:: { CString , OsStr } ;
5555 use std:: os:: unix:: prelude:: * ;
56- use std:: ptr;
57- use std:: str;
5856
59- pub ( super ) fn open ( filename : & OsStr ) -> Result < * mut u8 , String > {
60- check_for_errors_in ( || unsafe {
61- let s = CString :: new ( filename . as_bytes ( ) ) . unwrap ( ) ;
62- libc :: dlopen ( s . as_ptr ( ) , libc :: RTLD_LAZY ) as * mut u8
63- } )
64- }
57+ // `dlerror` is process global, so we can only allow a single thread at a
58+ // time to call `dlsym` and `dlopen` if we want to check the error message.
59+ mod error {
60+ use std :: ffi :: CStr ;
61+ use std :: lazy :: SyncLazy ;
62+ use std :: sync :: { Mutex , MutexGuard } ;
6563
66- fn check_for_errors_in < T , F > ( f : F ) -> Result < T , String >
67- where
68- F : FnOnce ( ) -> T ,
69- {
70- use std:: sync:: { Mutex , Once } ;
71- static INIT : Once = Once :: new ( ) ;
72- static mut LOCK : * mut Mutex < ( ) > = ptr:: null_mut ( ) ;
73- unsafe {
74- INIT . call_once ( || {
75- LOCK = Box :: into_raw ( Box :: new ( Mutex :: new ( ( ) ) ) ) ;
76- } ) ;
77- // dlerror isn't thread safe, so we need to lock around this entire
78- // sequence
79- let _guard = ( * LOCK ) . lock ( ) ;
80- let _old_error = libc:: dlerror ( ) ;
81-
82- let result = f ( ) ;
83-
84- let last_error = libc:: dlerror ( ) as * const _ ;
85- if ptr:: null ( ) == last_error {
86- Ok ( result)
87- } else {
88- let s = CStr :: from_ptr ( last_error) . to_bytes ( ) ;
89- Err ( str:: from_utf8 ( s) . unwrap ( ) . to_owned ( ) )
64+ pub fn lock ( ) -> MutexGuard < ' static , Guard > {
65+ static LOCK : SyncLazy < Mutex < Guard > > = SyncLazy :: new ( || Mutex :: new ( Guard { _priv : ( ) } ) ) ;
66+ LOCK . lock ( ) . unwrap ( )
67+ }
68+
69+ pub struct Guard {
70+ _priv : ( ) ,
71+ }
72+
73+ impl Guard {
74+ pub fn get ( & mut self ) -> Result < ( ) , String > {
75+ let msg = unsafe { libc:: dlerror ( ) } ;
76+ if msg. is_null ( ) {
77+ Ok ( ( ) )
78+ } else {
79+ let msg = unsafe { CStr :: from_ptr ( msg as * const _ ) } ;
80+ Err ( msg. to_string_lossy ( ) . into_owned ( ) )
81+ }
9082 }
83+
84+ pub fn clear ( & mut self ) {
85+ let _ = unsafe { libc:: dlerror ( ) } ;
86+ }
87+ }
88+ }
89+
90+ pub ( super ) fn open ( filename : & OsStr ) -> Result < * mut u8 , String > {
91+ let s = CString :: new ( filename. as_bytes ( ) ) . unwrap ( ) ;
92+
93+ let mut dlerror = error:: lock ( ) ;
94+ let ret = unsafe { libc:: dlopen ( s. as_ptr ( ) , libc:: RTLD_LAZY ) } as * mut u8 ;
95+
96+ if !ret. is_null ( ) {
97+ return Ok ( ret) ;
9198 }
99+
100+ // A NULL return from `dlopen` indicates that an error has
101+ // definitely occurred, so if nothing is in `dlerror`, we are
102+ // racing with another thread that has stolen our error message.
103+ dlerror. get ( ) . and_then ( |( ) | Err ( "Unknown error" . to_string ( ) ) )
92104 }
93105
94106 pub ( super ) unsafe fn symbol (
95107 handle : * mut u8 ,
96108 symbol : * const libc:: c_char ,
97109 ) -> Result < * mut u8 , String > {
98- check_for_errors_in ( || libc:: dlsym ( handle as * mut libc:: c_void , symbol) as * mut u8 )
110+ let mut dlerror = error:: lock ( ) ;
111+
112+ // Flush `dlerror` since we need to use it to determine whether the subsequent call to
113+ // `dlsym` is successful.
114+ dlerror. clear ( ) ;
115+
116+ let ret = libc:: dlsym ( handle as * mut libc:: c_void , symbol) as * mut u8 ;
117+
118+ // A non-NULL return value *always* indicates success. There's no need
119+ // to check `dlerror`.
120+ if !ret. is_null ( ) {
121+ return Ok ( ret) ;
122+ }
123+
124+ dlerror. get ( ) . map ( |( ) | ret)
99125 }
100126
101127 pub ( super ) unsafe fn close ( handle : * mut u8 ) {
0 commit comments