Skip to content

Commit

Permalink
node-api: allow retrieval of add-on file name
Browse files Browse the repository at this point in the history
Unlike JS-only modules, native add-ons are always associated with a
dynamic shared object from which they are loaded. Being able to
retrieve its absolute path is important to native-only add-ons, i.e.
add-ons that are not themselves being loaded from a JS-only module
located in the same package as the native add-on itself.

Currently, the file name is obtained at environment construction time
from the JS `module.filename`. Nevertheless, the presence of `module`
is not required, because the file name could also be passed in via a
private property added onto `exports` from the `process.dlopen`
binding.

As an attempt at future-proofing, the file name is provided as a URL,
i.e. prefixed with the `file://` protocol.

Fixes: nodejs/node-addon-api#449
PR-URL: nodejs#37195
Backport-PR-URL: nodejs#37328
Co-authored-by: Michael Dawson <[email protected]>
Reviewed-By: Michael Dawson <[email protected]>
  • Loading branch information
2 people authored and richardlau committed Mar 17, 2021
1 parent 86f34ee commit f569209
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 7 deletions.
25 changes: 25 additions & 0 deletions doc/api/n-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -5791,6 +5791,31 @@ idempotent.

This API may only be called from the main thread.

## Miscellaneous utilities

## node_api_get_module_file_name

<!-- YAML
added: REPLACEME
-->

> Stability: 1 - Experimental

```c
NAPI_EXTERN napi_status
node_api_get_module_file_name(napi_env env, const char** result);

```

* `[in] env`: The environment that the API is invoked under.
* `[out] result`: A URL containing the absolute path of the
location from which the add-on was loaded. For a file on the local
file system it will start with `file://`. The string is null-terminated and
owned by `env` and must thus not be modified or freed.

`result` may be an empty string if the add-on loading process fails to establish
the add-on's file name during loading.

[ABI Stability]: https://nodejs.org/en/docs/guides/abi-stability/
[AppVeyor]: https://www.appveyor.com
[C++ Addons]: addons.html
Expand Down
1 change: 1 addition & 0 deletions src/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ constexpr size_t kFsStatsBufferLength =
V(fd_string, "fd") \
V(fields_string, "fields") \
V(file_string, "file") \
V(filename_string, "filename") \
V(fingerprint256_string, "fingerprint256") \
V(fingerprint_string, "fingerprint") \
V(flags_string, "flags") \
Expand Down
45 changes: 39 additions & 6 deletions src/node_api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@
#include <memory>

struct node_napi_env__ : public napi_env__ {
explicit node_napi_env__(v8::Local<v8::Context> context):
napi_env__(context) {
explicit node_napi_env__(v8::Local<v8::Context> context,
const std::string& module_filename):
napi_env__(context), filename(module_filename) {
CHECK_NOT_NULL(node_env());
}

Expand Down Expand Up @@ -43,6 +44,10 @@ struct node_napi_env__ : public napi_env__ {
});
});
}

const char* GetFilename() const { return filename.c_str(); }

std::string filename;
};

typedef node_napi_env__* node_napi_env;
Expand Down Expand Up @@ -84,10 +89,11 @@ class BufferFinalizer : private Finalizer {
};
};

static inline napi_env NewEnv(v8::Local<v8::Context> context) {
static inline napi_env
NewEnv(v8::Local<v8::Context> context, const std::string& module_filename) {
node_napi_env result;

result = new node_napi_env__(context);
result = new node_napi_env__(context, module_filename);
// TODO(addaleax): There was previously code that tried to delete the
// napi_env when its v8::Context was garbage collected;
// However, as long as N-API addons using this napi_env are in place,
Expand Down Expand Up @@ -454,16 +460,35 @@ void napi_module_register_by_symbol(v8::Local<v8::Object> exports,
v8::Local<v8::Value> module,
v8::Local<v8::Context> context,
napi_addon_register_func init) {
node::Environment* node_env = node::Environment::GetCurrent(context);
std::string module_filename = "";
if (init == nullptr) {
node::Environment* node_env = node::Environment::GetCurrent(context);
CHECK_NOT_NULL(node_env);
node_env->ThrowError(
"Module has no declared entry point.");
return;
}

// We set `env->filename` from `module.filename` here, but we could just as
// easily add a private property to `exports` in `process.dlopen`, which
// receives the file name from JS, and retrieve *that* here. Thus, we are not
// endorsing commonjs here by making use of `module.filename`.
v8::Local<v8::Value> filename_js;
v8::Local<v8::Object> modobj;
if (module->ToObject(context).ToLocal(&modobj) &&
modobj->Get(context, node_env->filename_string()).ToLocal(&filename_js) &&
filename_js->IsString()) {
node::Utf8Value filename(node_env->isolate(), filename_js); // Cast

// Turn the absolute path into a URL. Currently the absolute path is always
// a file system path.
// TODO(gabrielschulhof): Pass the `filename` through unchanged if/when we
// receive it as a URL already.
module_filename = std::string("file://") + (*filename);
}

// Create a new napi_env for this specific module.
napi_env env = v8impl::NewEnv(context);
napi_env env = v8impl::NewEnv(context, module_filename);

napi_value _exports;
env->CallIntoModule([&](napi_env env) {
Expand Down Expand Up @@ -1154,3 +1179,11 @@ napi_ref_threadsafe_function(napi_env env, napi_threadsafe_function func) {
CHECK_NOT_NULL(func);
return reinterpret_cast<v8impl::ThreadSafeFunction*>(func)->Ref();
}

napi_status node_api_get_module_file_name(napi_env env, const char** result) {
CHECK_ENV(env);
CHECK_ARG(env, result);

*result = static_cast<node_napi_env>(env)->GetFilename();
return napi_clear_last_error(env);
}
3 changes: 3 additions & 0 deletions src/node_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,9 @@ NAPI_EXTERN napi_status napi_add_async_cleanup_hook(
NAPI_EXTERN napi_status napi_remove_async_cleanup_hook(
napi_async_cleanup_hook_handle remove_handle);

NAPI_EXTERN napi_status
node_api_get_module_file_name(napi_env env, const char** result);

#endif // NAPI_EXPERIMENTAL

EXTERN_C_END
Expand Down
7 changes: 6 additions & 1 deletion test/node-api/test_general/test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
'use strict';

const common = require('../../common');
const test_general = require(`./build/${common.buildType}/test_general`);
const filename = require.resolve(`./build/${common.buildType}/test_general`);
const test_general = require(filename);
const assert = require('assert');

// TODO(gabrielschulhof): This test may need updating if/when the filename
// becomes a full-fledged URL.
assert.strictEqual(test_general.filename, `file://${filename}`);

const [ major, minor, patch, release ] = test_general.testGetNodeVersion();
assert.strictEqual(process.version.split('-')[0],
`v${major}.${minor}.${patch}`);
Expand Down
13 changes: 13 additions & 0 deletions test/node-api/test_general/test_general.c
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#define NAPI_EXPERIMENTAL
#include <node_api.h>
#include <stdlib.h>
#include "../../js-native-api/common.h"
Expand All @@ -21,9 +22,21 @@ static napi_value testGetNodeVersion(napi_env env, napi_callback_info info) {
return result;
}

static napi_value GetFilename(napi_env env, napi_callback_info info) {
const char* filename;
napi_value result;

NAPI_CALL(env, node_api_get_module_file_name(env, &filename));
NAPI_CALL(env,
napi_create_string_utf8(env, filename, NAPI_AUTO_LENGTH, &result));

return result;
}

static napi_value Init(napi_env env, napi_value exports) {
napi_property_descriptor descriptors[] = {
DECLARE_NAPI_PROPERTY("testGetNodeVersion", testGetNodeVersion),
DECLARE_NAPI_GETTER("filename", GetFilename),
};

NAPI_CALL(env, napi_define_properties(
Expand Down

0 comments on commit f569209

Please sign in to comment.