2
2
#include " env-inl.h"
3
3
#include " node_native_module.h"
4
4
#include " util.h"
5
+ #include < atomic>
5
6
6
7
#if HAVE_OPENSSL
7
8
#define NODE_BUILTIN_OPENSSL_MODULES (V ) V(crypto) V(tls_wrap)
96
97
NODE_BUILTIN_MODULES (V)
97
98
#undef V
98
99
100
+ #ifdef _AIX
101
+ // On AIX, dlopen() behaves differently from other operating systems, in that
102
+ // it returns unique values from each call, rather than identical values, when
103
+ // loading the same handle.
104
+ // We try to work around that by providing wrappers for the dlopen() family of
105
+ // functions, and using st_dev and st_ino for the file that is to be loaded
106
+ // as keys for a cache.
107
+
108
+ namespace node {
109
+ namespace dlwrapper {
110
+
111
+ struct dl_wrap {
112
+ uint64_t st_dev;
113
+ uint64_t st_ino;
114
+ uint64_t refcount;
115
+ void * real_handle;
116
+
117
+ struct hash {
118
+ size_t operator ()(const dl_wrap* wrap) const {
119
+ return std::hash<uint64_t >()(wrap->st_dev ) ^
120
+ std::hash<uint64_t >()(wrap->st_ino );
121
+ }
122
+ };
123
+
124
+ struct equal {
125
+ bool operator ()(const dl_wrap* a,
126
+ const dl_wrap* b) const {
127
+ return a->st_dev == b->st_dev && a->st_ino == b->st_ino ;
128
+ }
129
+ };
130
+ };
131
+
132
+ static Mutex dlhandles_mutex;
133
+ static std::unordered_set<dl_wrap*, dl_wrap::hash, dl_wrap::equal>
134
+ dlhandles;
135
+ static thread_local std::string dlerror_storage;
136
+
137
+ char * wrapped_dlerror () {
138
+ return &dlerror_storage[0 ];
139
+ }
140
+
141
+ void * wrapped_dlopen (const char * filename, int flags) {
142
+ CHECK_NOT_NULL (filename); // This deviates from the 'real' dlopen().
143
+ Mutex::ScopedLock lock (dlhandles_mutex);
144
+
145
+ uv_fs_t req;
146
+ OnScopeLeave cleanup ([&]() { uv_fs_req_cleanup (&req); });
147
+ int rc = uv_fs_stat (nullptr , &req, filename, nullptr );
148
+
149
+ if (rc != 0 ) {
150
+ dlerror_storage = uv_strerror (rc);
151
+ return nullptr ;
152
+ }
153
+
154
+ dl_wrap search = {
155
+ req.statbuf .st_dev ,
156
+ req.statbuf .st_ino ,
157
+ 0 , nullptr
158
+ };
159
+
160
+ auto it = dlhandles.find (&search);
161
+ if (it != dlhandles.end ()) {
162
+ (*it)->refcount ++;
163
+ return *it;
164
+ }
165
+
166
+ void * real_handle = dlopen (filename, flags);
167
+ if (real_handle == nullptr ) {
168
+ dlerror_storage = dlerror ();
169
+ return nullptr ;
170
+ }
171
+ dl_wrap* wrap = new dl_wrap ();
172
+ wrap->st_dev = req.statbuf .st_dev ;
173
+ wrap->st_ino = req.statbuf .st_ino ;
174
+ wrap->refcount = 1 ;
175
+ wrap->real_handle = real_handle;
176
+ dlhandles.insert (wrap);
177
+ return wrap;
178
+ }
179
+
180
+ int wrapped_dlclose (void * handle) {
181
+ Mutex::ScopedLock lock (dlhandles_mutex);
182
+ dl_wrap* wrap = static_cast <dl_wrap*>(handle);
183
+ int ret = 0 ;
184
+ CHECK_GE (wrap->refcount , 1 );
185
+ if (--wrap->refcount == 0 ) {
186
+ ret = dlclose (wrap->real_handle );
187
+ if (ret != 0 ) dlerror_storage = dlerror ();
188
+ dlhandles.erase (wrap);
189
+ delete wrap;
190
+ }
191
+ return ret;
192
+ }
193
+
194
+ void * wrapped_dlsym (void * handle, const char * symbol) {
195
+ if (handle == RTLD_DEFAULT || handle == RTLD_NEXT)
196
+ return dlsym (handle, symbol);
197
+ dl_wrap* wrap = static_cast <dl_wrap*>(handle);
198
+ return dlsym (wrap->real_handle , symbol);
199
+ }
200
+
201
+ #define dlopen node::dlwrapper::wrapped_dlopen
202
+ #define dlerror node::dlwrapper::wrapped_dlerror
203
+ #define dlclose node::dlwrapper::wrapped_dlclose
204
+ #define dlsym node::dlwrapper::wrapped_dlsym
205
+
206
+ } // namespace dlwrapper
207
+ } // namespace node
208
+
209
+ #endif // _AIX
210
+
211
+ #ifdef __linux__
212
+ static bool libc_may_be_musl () {
213
+ static std::atomic_bool retval; // Cache the return value.
214
+ static std::atomic_bool has_cached_retval { false };
215
+ if (has_cached_retval) return retval;
216
+ retval = dlsym (RTLD_DEFAULT, " gnu_get_libc_version" ) == nullptr ;
217
+ has_cached_retval = true ;
218
+ return retval;
219
+ }
220
+ #else // __linux__
221
+ static bool libc_may_be_musl () { return false ; }
222
+ #endif // __linux__
223
+
99
224
namespace node {
100
225
101
226
using v8::Context;
@@ -110,7 +235,6 @@ using v8::Value;
110
235
// Globals per process
111
236
static node_module* modlist_internal;
112
237
static node_module* modlist_linked;
113
- static node_module* modlist_addon;
114
238
static uv_once_t init_modpending_once = UV_ONCE_INIT;
115
239
static uv_key_t thread_local_modpending;
116
240
@@ -136,6 +260,57 @@ extern "C" void node_module_register(void* m) {
136
260
137
261
namespace binding {
138
262
263
+ static struct global_handle_map_t {
264
+ public:
265
+ void set (void * handle, node_module* mod) {
266
+ CHECK_NE (handle, nullptr );
267
+ Mutex::ScopedLock lock (mutex_);
268
+
269
+ map_[handle].module = mod;
270
+ // We need to store this flag internally to avoid a chicken-and-egg problem
271
+ // during cleanup. By the time we actually use the flag's value,
272
+ // the shared object has been unloaded, and its memory would be gone,
273
+ // making it impossible to access fields of `mod` --
274
+ // unless `mod` *is* dynamically allocated, but we cannot know that
275
+ // without checking the flag.
276
+ map_[handle].wants_delete_module = mod->nm_flags & NM_F_DELETEME;
277
+ map_[handle].refcount ++;
278
+ }
279
+
280
+ node_module* get_and_increase_refcount (void * handle) {
281
+ CHECK_NE (handle, nullptr );
282
+ Mutex::ScopedLock lock (mutex_);
283
+
284
+ auto it = map_.find (handle);
285
+ if (it == map_.end ()) return nullptr ;
286
+ it->second .refcount ++;
287
+ return it->second .module ;
288
+ }
289
+
290
+ void erase (void * handle) {
291
+ CHECK_NE (handle, nullptr );
292
+ Mutex::ScopedLock lock (mutex_);
293
+
294
+ auto it = map_.find (handle);
295
+ if (it == map_.end ()) return ;
296
+ CHECK_GE (it->second .refcount , 1 );
297
+ if (--it->second .refcount == 0 ) {
298
+ if (it->second .wants_delete_module )
299
+ delete it->second .module ;
300
+ map_.erase (handle);
301
+ }
302
+ }
303
+
304
+ private:
305
+ Mutex mutex_;
306
+ struct Entry {
307
+ unsigned int refcount;
308
+ bool wants_delete_module;
309
+ node_module* module;
310
+ };
311
+ std::unordered_map<void *, Entry> map_;
312
+ } global_handle_map;
313
+
139
314
DLib::DLib (const char * filename, int flags)
140
315
: filename_(filename), flags_(flags), handle_(nullptr ) {}
141
316
@@ -149,7 +324,21 @@ bool DLib::Open() {
149
324
150
325
void DLib::Close () {
151
326
if (handle_ == nullptr ) return ;
152
- dlclose (handle_);
327
+
328
+ if (libc_may_be_musl ()) {
329
+ // musl libc implements dlclose() as a no-op which returns 0.
330
+ // As a consequence, trying to re-load a previously closed addon at a later
331
+ // point will not call its static constructors, which Node.js uses.
332
+ // Therefore, when we may be using musl libc, we assume that the shared
333
+ // object exists indefinitely and keep it in our handle map.
334
+ return ;
335
+ }
336
+
337
+ int err = dlclose (handle_);
338
+ if (err == 0 ) {
339
+ if (has_entry_in_global_handle_map_)
340
+ global_handle_map.erase (handle_);
341
+ }
153
342
handle_ = nullptr ;
154
343
}
155
344
@@ -170,6 +359,8 @@ bool DLib::Open() {
170
359
171
360
void DLib::Close () {
172
361
if (handle_ == nullptr ) return ;
362
+ if (has_entry_in_global_handle_map_)
363
+ global_handle_map.erase (handle_);
173
364
uv_dlclose (&lib_);
174
365
handle_ = nullptr ;
175
366
}
@@ -181,6 +372,16 @@ void* DLib::GetSymbolAddress(const char* name) {
181
372
}
182
373
#endif // !__POSIX__
183
374
375
+ void DLib::SaveInGlobalHandleMap (node_module* mp) {
376
+ has_entry_in_global_handle_map_ = true ;
377
+ global_handle_map.set (handle_, mp);
378
+ }
379
+
380
+ node_module* DLib::GetSavedModuleFromGlobalHandleMap () {
381
+ has_entry_in_global_handle_map_ = true ;
382
+ return global_handle_map.get_and_increase_refcount (handle_);
383
+ }
384
+
184
385
using InitializerCallback = void (*)(Local<Object> exports,
185
386
Local<Value> module,
186
387
Local<Context> context);
@@ -235,12 +436,15 @@ void DLOpen(const FunctionCallbackInfo<Value>& args) {
235
436
236
437
node::Utf8Value filename (env->isolate (), args[1 ]); // Cast
237
438
env->TryLoadAddon (*filename, flags, [&](DLib* dlib) {
439
+ static Mutex dlib_load_mutex;
440
+ Mutex::ScopedLock lock (dlib_load_mutex);
441
+
238
442
const bool is_opened = dlib->Open ();
239
443
240
444
// Objects containing v14 or later modules will have registered themselves
241
445
// on the pending list. Activate all of them now. At present, only one
242
446
// module per object is supported.
243
- node_module* const mp =
447
+ node_module* mp =
244
448
static_cast <node_module*>(uv_key_get (&thread_local_modpending));
245
449
uv_key_set (&thread_local_modpending, nullptr );
246
450
@@ -257,17 +461,24 @@ void DLOpen(const FunctionCallbackInfo<Value>& args) {
257
461
return false ;
258
462
}
259
463
260
- if (mp == nullptr ) {
464
+ if (mp != nullptr ) {
465
+ mp->nm_dso_handle = dlib->handle_ ;
466
+ dlib->SaveInGlobalHandleMap (mp);
467
+ } else {
261
468
if (auto callback = GetInitializerCallback (dlib)) {
262
469
callback (exports, module, context);
470
+ return true ;
263
471
} else if (auto napi_callback = GetNapiInitializerCallback (dlib)) {
264
472
napi_module_register_by_symbol (exports, module, context, napi_callback);
473
+ return true ;
265
474
} else {
266
- dlib->Close ();
267
- env->ThrowError (" Module did not self-register." );
268
- return false ;
475
+ mp = dlib->GetSavedModuleFromGlobalHandleMap ();
476
+ if (mp == nullptr || mp->nm_context_register_func == nullptr ) {
477
+ dlib->Close ();
478
+ env->ThrowError (" Module did not self-register." );
479
+ return false ;
480
+ }
269
481
}
270
- return true ;
271
482
}
272
483
273
484
// -1 is used for N-API modules
@@ -300,10 +511,8 @@ void DLOpen(const FunctionCallbackInfo<Value>& args) {
300
511
}
301
512
CHECK_EQ (mp->nm_flags & NM_F_BUILTIN, 0 );
302
513
303
- mp->nm_dso_handle = dlib->handle_ ;
304
- mp->nm_link = modlist_addon;
305
- modlist_addon = mp;
306
-
514
+ // Do not keep the lock while running userland addon loading code.
515
+ Mutex::ScopedUnlock unlock (lock);
307
516
if (mp->nm_context_register_func != nullptr ) {
308
517
mp->nm_context_register_func (exports, module, context, mp->nm_priv );
309
518
} else if (mp->nm_register_func != nullptr ) {
0 commit comments