22#include " env-inl.h"
33#include " node_native_module.h"
44#include " util.h"
5+ #include < atomic>
56
67#if HAVE_OPENSSL
78#define NODE_BUILTIN_OPENSSL_MODULES (V ) V(crypto) V(tls_wrap)
9697NODE_BUILTIN_MODULES (V)
9798#undef V
9899
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+
99224namespace node {
100225
101226using v8::Context;
@@ -110,7 +235,6 @@ using v8::Value;
110235// Globals per process
111236static node_module* modlist_internal;
112237static node_module* modlist_linked;
113- static node_module* modlist_addon;
114238static uv_once_t init_modpending_once = UV_ONCE_INIT;
115239static uv_key_t thread_local_modpending;
116240
@@ -136,6 +260,57 @@ extern "C" void node_module_register(void* m) {
136260
137261namespace binding {
138262
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+
139314DLib::DLib (const char * filename, int flags)
140315 : filename_(filename), flags_(flags), handle_(nullptr ) {}
141316
@@ -149,7 +324,21 @@ bool DLib::Open() {
149324
150325void DLib::Close () {
151326 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+ }
153342 handle_ = nullptr ;
154343}
155344
@@ -170,6 +359,8 @@ bool DLib::Open() {
170359
171360void DLib::Close () {
172361 if (handle_ == nullptr ) return ;
362+ if (has_entry_in_global_handle_map_)
363+ global_handle_map.erase (handle_);
173364 uv_dlclose (&lib_);
174365 handle_ = nullptr ;
175366}
@@ -181,6 +372,16 @@ void* DLib::GetSymbolAddress(const char* name) {
181372}
182373#endif // !__POSIX__
183374
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+
184385using InitializerCallback = void (*)(Local<Object> exports,
185386 Local<Value> module ,
186387 Local<Context> context);
@@ -235,12 +436,15 @@ void DLOpen(const FunctionCallbackInfo<Value>& args) {
235436
236437 node::Utf8Value filename (env->isolate (), args[1 ]); // Cast
237438 env->TryLoadAddon (*filename, flags, [&](DLib* dlib) {
439+ static Mutex dlib_load_mutex;
440+ Mutex::ScopedLock lock (dlib_load_mutex);
441+
238442 const bool is_opened = dlib->Open ();
239443
240444 // Objects containing v14 or later modules will have registered themselves
241445 // on the pending list. Activate all of them now. At present, only one
242446 // module per object is supported.
243- node_module* const mp =
447+ node_module* mp =
244448 static_cast <node_module*>(uv_key_get (&thread_local_modpending));
245449 uv_key_set (&thread_local_modpending, nullptr );
246450
@@ -257,17 +461,24 @@ void DLOpen(const FunctionCallbackInfo<Value>& args) {
257461 return false ;
258462 }
259463
260- if (mp == nullptr ) {
464+ if (mp != nullptr ) {
465+ mp->nm_dso_handle = dlib->handle_ ;
466+ dlib->SaveInGlobalHandleMap (mp);
467+ } else {
261468 if (auto callback = GetInitializerCallback (dlib)) {
262469 callback (exports, module , context);
470+ return true ;
263471 } else if (auto napi_callback = GetNapiInitializerCallback (dlib)) {
264472 napi_module_register_by_symbol (exports, module , context, napi_callback);
473+ return true ;
265474 } 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+ }
269481 }
270- return true ;
271482 }
272483
273484 // -1 is used for N-API modules
@@ -300,10 +511,8 @@ void DLOpen(const FunctionCallbackInfo<Value>& args) {
300511 }
301512 CHECK_EQ (mp->nm_flags & NM_F_BUILTIN, 0 );
302513
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);
307516 if (mp->nm_context_register_func != nullptr ) {
308517 mp->nm_context_register_func (exports, module , context, mp->nm_priv );
309518 } else if (mp->nm_register_func != nullptr ) {
0 commit comments