Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add process.dlopenFlags #12794

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions doc/api/os.md
Original file line number Diff line number Diff line change
Expand Up @@ -1170,6 +1170,43 @@ The following error codes are specific to the Windows operating system:
</tr>
</table>

### dlopen Constants

If available on the operating system, the following constants
are exported in `os.constants.dlopen`. See dlopen(3) for detailed
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

@ezequielgarcia ezequielgarcia Sep 7, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it done automagically? I think we already discussed this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad. Yes, tools/doc/html.js will take care of this.

information.

<table>
<tr>
<th>Constant</th>
<th>Description</th>
</tr>
<tr>
<td><code>RTLD_LAZY</code></td>
<td>Perform lazy binding. Node.js sets this flag by default.</td>
</tr>
<tr>
<td><code>RTLD_NOW</code></td>
<td>Resolve all undefined symbols in the library before dlopen(3)
returns.</td>
</tr>
<tr>
<td><code>RTLD_GLOBAL</code></td>
<td>Symbols defined by the library will be made available for symbol
resolution of subsequently loaded libraries.</td>
</tr>
<tr>
<td><code>RTLD_LOCAL</code></td>
<td>The converse of RTLD_GLOBAL. This is the default behavior if neither
flag is specified.</td>
</tr>
<tr>
<td><code>RTLD_DEEPBIND</code></td>
<td>Make a self-contained library use its own symbols in preference to
symbols from previously loaded libraries.</td>
</tr>
</table>

### libuv Constants

<table>
Expand Down
45 changes: 45 additions & 0 deletions doc/api/process.md
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,48 @@ process's [`ChildProcess.disconnect()`][].
If the Node.js process was not spawned with an IPC channel,
`process.disconnect()` will be `undefined`.

## process.dlopen(module, filename[, flags])
<!-- YAML
added: v0.1.16
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/12794
description: Added support for the `flags` argument.
-->

* `module` {Object}
* `filename` {string}
* `flags` {os.constants.dlopen}. Defaults to `os.constants.dlopen.RTLD_LAZY`.

The `process.dlopen()` method allows to dynamically load shared
objects. It is primarily used by `require()` to load
C++ Addons, and should not be used directly, except in special
cases. In other words, [`require()`][] should be preferred over
`process.dlopen()`, unless there are specific reasons.

The `flags` argument is an integer that allows to specify dlopen
behavior. See the [`os.constants.dlopen`][] documentation for details.

If there are specific reasons to use `process.dlopen()` (for instance,
to specify dlopen flags), it's often useful to use [`require.resolve()`][]
to look up the module's path.

*Note*: An important drawback when calling `process.dlopen()` is that the
`module` instance must be passed. Functions exported by the C++ Addon will
be accessible via `module.exports`.

The example below shows how to load a C++ Addon, named as `binding`,
that exports a `foo` function. All the symbols will be loaded before
the call returns, by passing the `RTLD_NOW` constant. In this example
the constant is assumed to be available.

```js
const os = require('os');
process.dlopen(module, require.resolve('binding'),
os.constants.dlopen.RTLD_NOW);
module.exports.foo();
```

## process.emitWarning(warning[, options])
<!-- YAML
added: 8.0.0
Expand Down Expand Up @@ -1841,13 +1883,16 @@ cases:
[`end()`]: stream.html#stream_writable_end_chunk_encoding_callback
[`net.Server`]: net.html#net_class_net_server
[`net.Socket`]: net.html#net_class_net_socket
[`os.constants.dlopen`]: os.html#os_dlopen_constants
[`process.argv`]: #process_process_argv
[`process.execPath`]: #process_process_execpath
[`process.exit()`]: #process_process_exit_code
[`process.exitCode`]: #process_process_exitcode
[`process.kill()`]: #process_process_kill_pid_signal
[`promise.catch()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch
[`require()`]: globals.html#globals_require
[`require.main`]: modules.html#modules_accessing_the_main_module
[`require.resolve()`]: globals.html#globals_require_resolve
[`setTimeout(fn, 0)`]: timers.html#timers_settimeout_callback_delay_args
[Child Process]: child_process.html
[Cluster]: cluster.html
Expand Down
1 change: 1 addition & 0 deletions lib/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
// Deprecation Code: DEP0008
const constants = process.binding('constants');
Object.assign(exports,
constants.os.dlopen,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should add things to something that's already deprecated.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has already been discussed, on this same PR-thread. I'll let you do the digging. (BTW: this is where mailing lists outpass github PRs: it's so much harder to dig discussion history).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, in that case SGTM. Sorry about not reading through the entire thread – though in my defense it is quite long.

constants.os.errno,
constants.os.signals,
constants.fs,
Expand Down
81 changes: 67 additions & 14 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ typedef int mode_t;
#include <grp.h> // getgrnam()
#endif

#if defined(__POSIX__)
#include <dlfcn.h>
#endif

#ifdef __APPLE__
#include <crt_externs.h>
#define environ (*_NSGetEnviron())
Expand Down Expand Up @@ -2503,36 +2507,85 @@ struct node_module* get_linked_module(const char* name) {
return mp;
}

// DLOpen is process.dlopen(module, filename).
struct DLib {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be a struct? If we keep it as a class and accept the values of filename_ and errmsg_ in the constructor, then we don't have to expose all the members as public right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already discussed. See above.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't find the actual discussion corresponding to this. I know it is exhausting to search through it, but if you could find it, please point me to it. I'll try again when I get back home.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On #12794 (comment) it was suggested to do a class. So I did it and commented
#12794 (comment). Later, #12794 (comment), where it was suggested to do a struct instead.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I didn't check properly then.

std::string filename_;
std::string errmsg_;
void* handle_;
int flags_;

#ifdef __POSIX__
static const int kDefaultFlags = RTLD_LAZY;

bool Open() {
handle_ = dlopen(filename_.c_str(), flags_);
if (handle_ != nullptr)
return true;
errmsg_ = dlerror();
return false;
}

void Close() {
if (handle_ != nullptr)
dlclose(handle_);
}
#else // !__POSIX__
static const int kDefaultFlags = 0;
uv_lib_t lib_;

bool Open() {
int ret = uv_dlopen(filename_.c_str(), &lib_);
if (ret == 0) {
handle_ = static_cast<void*>(lib_.handle);
return true;
}
errmsg_ = uv_dlerror(&lib_);
uv_dlclose(&lib_);
return false;
}

void Close() {
uv_dlclose(&lib_);
}
#endif // !__POSIX__
};

// DLOpen is process.dlopen(module, filename, flags).
// Used to load 'module.node' dynamically shared objects.
//
// FIXME(bnoordhuis) Not multi-context ready. TBD how to resolve the conflict
// when two contexts try to load the same shared object. Maybe have a shadow
// cache that's a plain C list or hash table that's shared across contexts?
static void DLOpen(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
uv_lib_t lib;

CHECK_EQ(modpending, nullptr);

if (args.Length() != 2) {
env->ThrowError("process.dlopen takes exactly 2 arguments.");
if (args.Length() < 2) {
env->ThrowError("process.dlopen needs at least 2 arguments.");
return;
}

int32_t flags = DLib::kDefaultFlags;
if (args.Length() > 2 && !args[2]->Int32Value(env->context()).To(&flags)) {
return env->ThrowTypeError("flag argument must be an integer.");
}

Local<Object> module = args[0]->ToObject(env->isolate()); // Cast
node::Utf8Value filename(env->isolate(), args[1]); // Cast
const bool is_dlopen_error = uv_dlopen(*filename, &lib);
DLib dlib;
dlib.filename_ = *filename;
dlib.flags_ = flags;
bool is_opened = dlib.Open();

// Objects containing v14 or later modules will have registered themselves
// on the pending list. Activate all of them now. At present, only one
// module per object is supported.
node_module* const mp = modpending;
modpending = nullptr;

if (is_dlopen_error) {
Local<String> errmsg = OneByteString(env->isolate(), uv_dlerror(&lib));
uv_dlclose(&lib);
if (!is_opened) {
Local<String> errmsg = OneByteString(env->isolate(), dlib.errmsg_.c_str());
dlib.Close();
#ifdef _WIN32
// Windows needs to add the filename into the error message
errmsg = String::Concat(errmsg, args[1]->ToString(env->isolate()));
Expand All @@ -2542,7 +2595,7 @@ static void DLOpen(const FunctionCallbackInfo<Value>& args) {
}

if (mp == nullptr) {
uv_dlclose(&lib);
dlib.Close();
env->ThrowError("Module did not self-register.");
return;
}
Expand All @@ -2569,18 +2622,18 @@ static void DLOpen(const FunctionCallbackInfo<Value>& args) {
}

// NOTE: `mp` is allocated inside of the shared library's memory, calling
// `uv_dlclose` will deallocate it
uv_dlclose(&lib);
// `dlclose` will deallocate it
dlib.Close();
env->ThrowError(errmsg);
return;
}
if (mp->nm_flags & NM_F_BUILTIN) {
uv_dlclose(&lib);
dlib.Close();
env->ThrowError("Built-in module self-registered.");
return;
}

mp->nm_dso_handle = lib.handle;
mp->nm_dso_handle = dlib.handle_;
mp->nm_link = modlist_addon;
modlist_addon = mp;

Expand All @@ -2592,7 +2645,7 @@ static void DLOpen(const FunctionCallbackInfo<Value>& args) {
} else if (mp->nm_register_func != nullptr) {
mp->nm_register_func(exports, module, mp->nm_priv);
} else {
uv_dlclose(&lib);
dlib.Close();
env->ThrowError("Module has no declared entry point.");
return;
}
Expand Down
32 changes: 32 additions & 0 deletions src/node_constants.cc
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@
# endif // !OPENSSL_NO_ENGINE
#endif

#if defined(__POSIX__)
#include <dlfcn.h>
#endif

namespace node {

using v8::Local;
Expand Down Expand Up @@ -1238,6 +1242,28 @@ void DefineZlibConstants(Local<Object> target) {
NODE_DEFINE_CONSTANT(target, Z_DEFAULT_LEVEL);
}

void DefineDLOpenConstants(Local<Object> target) {
#ifdef RTLD_LAZY
NODE_DEFINE_CONSTANT(target, RTLD_LAZY);
#endif

#ifdef RTLD_NOW
NODE_DEFINE_CONSTANT(target, RTLD_NOW);
#endif

#ifdef RTLD_GLOBAL
NODE_DEFINE_CONSTANT(target, RTLD_GLOBAL);
#endif

#ifdef RTLD_LOCAL
NODE_DEFINE_CONSTANT(target, RTLD_LOCAL);
#endif

#ifdef RTLD_DEEPBIND
NODE_DEFINE_CONSTANT(target, RTLD_DEEPBIND);
#endif
}

} // anonymous namespace

void DefineConstants(v8::Isolate* isolate, Local<Object> target) {
Expand Down Expand Up @@ -1267,18 +1293,24 @@ void DefineConstants(v8::Isolate* isolate, Local<Object> target) {
CHECK(zlib_constants->SetPrototype(env->context(),
Null(env->isolate())).FromJust());

Local<Object> dlopen_constants = Object::New(isolate);
CHECK(dlopen_constants->SetPrototype(env->context(),
Null(env->isolate())).FromJust());

DefineErrnoConstants(err_constants);
DefineWindowsErrorConstants(err_constants);
DefineSignalConstants(sig_constants);
DefineSystemConstants(fs_constants);
DefineOpenSSLConstants(crypto_constants);
DefineCryptoConstants(crypto_constants);
DefineZlibConstants(zlib_constants);
DefineDLOpenConstants(dlopen_constants);

// Define libuv constants.
NODE_DEFINE_CONSTANT(os_constants, UV_UDP_REUSEADDR);
NODE_DEFINE_CONSTANT(fs_constants, UV_FS_COPYFILE_EXCL);

os_constants->Set(OneByteString(isolate, "dlopen"), dlopen_constants);
os_constants->Set(OneByteString(isolate, "errno"), err_constants);
os_constants->Set(OneByteString(isolate, "signals"), sig_constants);
target->Set(OneByteString(isolate, "os"), os_constants);
Expand Down
48 changes: 48 additions & 0 deletions test/addons/dlopen-ping-pong/binding.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#include <node.h>
#include <v8.h>

#ifndef _WIN32

#include <dlfcn.h>

extern "C" const char* dlopen_pong(void) {
return "pong";
}

namespace {

using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;

typedef const char* (*ping)(void);

static ping ping_func;

void LoadLibrary(const FunctionCallbackInfo<Value>& args) {
const String::Utf8Value filename(args[0]);
void* handle = dlopen(*filename, RTLD_LAZY);
assert(handle != nullptr);
ping_func = reinterpret_cast<ping>(dlsym(handle, "dlopen_ping"));
assert(ping_func != nullptr);
}

void Ping(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
assert(ping_func != nullptr);
args.GetReturnValue().Set(String::NewFromUtf8(isolate, ping_func()));
}

void init(Local<Object> exports) {
NODE_SET_METHOD(exports, "load", LoadLibrary);
NODE_SET_METHOD(exports, "ping", Ping);
}

NODE_MODULE(binding, init)

} // anonymous namespace
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect that you need to wrap this whole file in a #ifndef _WIN32 ... #endif guard. The CI buildbots build all add-ons irrespective of platform, but this file won't build on Windows.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


#endif // _WIN32
Loading