Skip to content

Commit b421d99

Browse files
Gabriel Schulhofmhdawson
authored andcommitted
node-api: allow retrieval of add-on file name
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: #37195 Backport-PR-URL: #37327 Co-authored-by: Michael Dawson <[email protected]> Reviewed-By: Michael Dawson <[email protected]>
1 parent c6ab198 commit b421d99

File tree

6 files changed

+91
-7
lines changed

6 files changed

+91
-7
lines changed

doc/api/n-api.md

+25
Original file line numberDiff line numberDiff line change
@@ -5938,6 +5938,31 @@ idempotent.
59385938

59395939
This API may only be called from the main thread.
59405940

5941+
## Miscellaneous utilities
5942+
5943+
## node_api_get_module_file_name
5944+
5945+
<!-- YAML
5946+
added: REPLACEME
5947+
-->
5948+
5949+
> Stability: 1 - Experimental
5950+
5951+
```c
5952+
NAPI_EXTERN napi_status
5953+
node_api_get_module_file_name(napi_env env, const char** result);
5954+
5955+
```
5956+
5957+
* `[in] env`: The environment that the API is invoked under.
5958+
* `[out] result`: A URL containing the absolute path of the
5959+
location from which the add-on was loaded. For a file on the local
5960+
file system it will start with `file://`. The string is null-terminated and
5961+
owned by `env` and must thus not be modified or freed.
5962+
5963+
`result` may be an empty string if the add-on loading process fails to establish
5964+
the add-on's file name during loading.
5965+
59415966
[ABI Stability]: https://nodejs.org/en/docs/guides/abi-stability/
59425967
[AppVeyor]: https://www.appveyor.com
59435968
[C++ Addons]: addons.md

src/env.h

+1
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ constexpr size_t kFsStatsBufferLength =
249249
V(fd_string, "fd") \
250250
V(fields_string, "fields") \
251251
V(file_string, "file") \
252+
V(filename_string, "filename") \
252253
V(fingerprint256_string, "fingerprint256") \
253254
V(fingerprint_string, "fingerprint") \
254255
V(flags_string, "flags") \

src/node_api.cc

+39-6
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@
1616
#include <memory>
1717

1818
struct node_napi_env__ : public napi_env__ {
19-
explicit node_napi_env__(v8::Local<v8::Context> context):
20-
napi_env__(context) {
19+
explicit node_napi_env__(v8::Local<v8::Context> context,
20+
const std::string& module_filename):
21+
napi_env__(context), filename(module_filename) {
2122
CHECK_NOT_NULL(node_env());
2223
}
2324

@@ -52,6 +53,10 @@ struct node_napi_env__ : public napi_env__ {
5253
});
5354
});
5455
}
56+
57+
const char* GetFilename() const { return filename.c_str(); }
58+
59+
std::string filename;
5560
};
5661

5762
typedef node_napi_env__* node_napi_env;
@@ -93,10 +98,11 @@ class BufferFinalizer : private Finalizer {
9398
};
9499
};
95100

96-
static inline napi_env NewEnv(v8::Local<v8::Context> context) {
101+
static inline napi_env
102+
NewEnv(v8::Local<v8::Context> context, const std::string& module_filename) {
97103
node_napi_env result;
98104

99-
result = new node_napi_env__(context);
105+
result = new node_napi_env__(context, module_filename);
100106
// TODO(addaleax): There was previously code that tried to delete the
101107
// napi_env when its v8::Context was garbage collected;
102108
// However, as long as N-API addons using this napi_env are in place,
@@ -579,16 +585,35 @@ void napi_module_register_by_symbol(v8::Local<v8::Object> exports,
579585
v8::Local<v8::Value> module,
580586
v8::Local<v8::Context> context,
581587
napi_addon_register_func init) {
588+
node::Environment* node_env = node::Environment::GetCurrent(context);
589+
std::string module_filename = "";
582590
if (init == nullptr) {
583-
node::Environment* node_env = node::Environment::GetCurrent(context);
584591
CHECK_NOT_NULL(node_env);
585592
node_env->ThrowError(
586593
"Module has no declared entry point.");
587594
return;
588595
}
589596

597+
// We set `env->filename` from `module.filename` here, but we could just as
598+
// easily add a private property to `exports` in `process.dlopen`, which
599+
// receives the file name from JS, and retrieve *that* here. Thus, we are not
600+
// endorsing commonjs here by making use of `module.filename`.
601+
v8::Local<v8::Value> filename_js;
602+
v8::Local<v8::Object> modobj;
603+
if (module->ToObject(context).ToLocal(&modobj) &&
604+
modobj->Get(context, node_env->filename_string()).ToLocal(&filename_js) &&
605+
filename_js->IsString()) {
606+
node::Utf8Value filename(node_env->isolate(), filename_js); // Cast
607+
608+
// Turn the absolute path into a URL. Currently the absolute path is always
609+
// a file system path.
610+
// TODO(gabrielschulhof): Pass the `filename` through unchanged if/when we
611+
// receive it as a URL already.
612+
module_filename = std::string("file://") + (*filename);
613+
}
614+
590615
// Create a new napi_env for this specific module.
591-
napi_env env = v8impl::NewEnv(context);
616+
napi_env env = v8impl::NewEnv(context, module_filename);
592617

593618
napi_value _exports;
594619
env->CallIntoModule([&](napi_env env) {
@@ -1287,3 +1312,11 @@ napi_ref_threadsafe_function(napi_env env, napi_threadsafe_function func) {
12871312
CHECK_NOT_NULL(func);
12881313
return reinterpret_cast<v8impl::ThreadSafeFunction*>(func)->Ref();
12891314
}
1315+
1316+
napi_status node_api_get_module_file_name(napi_env env, const char** result) {
1317+
CHECK_ENV(env);
1318+
CHECK_ARG(env, result);
1319+
1320+
*result = static_cast<node_napi_env>(env)->GetFilename();
1321+
return napi_clear_last_error(env);
1322+
}

src/node_api.h

+7
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,13 @@ NAPI_EXTERN napi_status napi_remove_async_cleanup_hook(
263263

264264
#endif // NAPI_VERSION >= 8
265265

266+
#ifdef NAPI_EXPERIMENTAL
267+
268+
NAPI_EXTERN napi_status
269+
node_api_get_module_file_name(napi_env env, const char** result);
270+
271+
#endif // NAPI_EXPERIMENTAL
272+
266273
EXTERN_C_END
267274

268275
#endif // SRC_NODE_API_H_

test/node-api/test_general/test.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
'use strict';
22

33
const common = require('../../common');
4-
const test_general = require(`./build/${common.buildType}/test_general`);
4+
const filename = require.resolve(`./build/${common.buildType}/test_general`);
5+
const test_general = require(filename);
56
const assert = require('assert');
67

8+
// TODO(gabrielschulhof): This test may need updating if/when the filename
9+
// becomes a full-fledged URL.
10+
assert.strictEqual(test_general.filename, `file://${filename}`);
11+
712
const [ major, minor, patch, release ] = test_general.testGetNodeVersion();
813
assert.strictEqual(process.version.split('-')[0],
914
`v${major}.${minor}.${patch}`);

test/node-api/test_general/test_general.c

+13
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#define NAPI_EXPERIMENTAL
12
#include <node_api.h>
23
#include <stdlib.h>
34
#include "../../js-native-api/common.h"
@@ -21,9 +22,21 @@ static napi_value testGetNodeVersion(napi_env env, napi_callback_info info) {
2122
return result;
2223
}
2324

25+
static napi_value GetFilename(napi_env env, napi_callback_info info) {
26+
const char* filename;
27+
napi_value result;
28+
29+
NAPI_CALL(env, node_api_get_module_file_name(env, &filename));
30+
NAPI_CALL(env,
31+
napi_create_string_utf8(env, filename, NAPI_AUTO_LENGTH, &result));
32+
33+
return result;
34+
}
35+
2436
static napi_value Init(napi_env env, napi_value exports) {
2537
napi_property_descriptor descriptors[] = {
2638
DECLARE_NAPI_PROPERTY("testGetNodeVersion", testGetNodeVersion),
39+
DECLARE_NAPI_GETTER("filename", GetFilename),
2740
};
2841

2942
NAPI_CALL(env, napi_define_properties(

0 commit comments

Comments
 (0)