@@ -51,51 +51,77 @@ mod tests;
51
51
52
52
#[ cfg( unix) ]
53
53
mod dl {
54
- use std:: ffi:: { CStr , CString , OsStr } ;
54
+ use std:: ffi:: { CString , OsStr } ;
55
55
use std:: os:: unix:: prelude:: * ;
56
- use std:: ptr;
57
- use std:: str;
58
56
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 } ;
65
63
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
+ }
90
82
}
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) ;
91
98
}
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 ( ) ) )
92
104
}
93
105
94
106
pub ( super ) unsafe fn symbol (
95
107
handle : * mut u8 ,
96
108
symbol : * const libc:: c_char ,
97
109
) -> 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)
99
125
}
100
126
101
127
pub ( super ) unsafe fn close ( handle : * mut u8 ) {
0 commit comments