mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 10:15:07 +08:00
A native addon inside a virtual file system could not be require()d: dlopen() and LoadLibrary() open a shared object by path, and a VFS path has no inode for them to open. Read the addon's bytes from the VFS and load them from a private, self-cleaning image instead, using the smallest on-disk footprint each platform allows: on Linux an anonymous memfd loaded through /proc/self/fd, so the bytes never reach the file system; on other POSIX platforms a file in a 0700 mkdtemp() directory, unlinked right after loading, with the mapping keeping it alive; on Windows a temp file opened FILE_FLAG_DELETE_ON_CLOSE whose handle is held until exit. This is internal. process.dlopen() keeps its documented (module, filename[, flags]) signature and ignores anything further; the bytes are passed to a dlopenBinary() reachable only through the process_methods binding, which the VFS hook calls for VFS paths. Addons on the real file system are untouched and load directly. Signed-off-by: Philipp Dunkel <pip@pipobscure.com> PR-URL: https://github.com/nodejs/node/pull/65680 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Paolo Insogna <paolo@cowtech.it> Reviewed-By: James M Snell <jasnell@gmail.com>
52 lines
2.1 KiB
JavaScript
52 lines
2.1 KiB
JavaScript
// Flags: --expose-internals
|
|
'use strict';
|
|
const common = require('../common');
|
|
const assert = require('assert');
|
|
const fs = require('fs');
|
|
const os = require('os');
|
|
const path = require('path');
|
|
const { internalBinding } = require('internal/test/binding');
|
|
|
|
// dlopenBinary() loads a native addon from bytes already in memory, for an
|
|
// addon that the dynamic loader cannot open by path (e.g. one served by a
|
|
// virtual file system). It is internal: process.dlopen() keeps its documented
|
|
// (module, filename[, flags]) signature and never takes the bytes.
|
|
const { dlopenBinary } = internalBinding('process_methods');
|
|
assert.strictEqual(typeof dlopenBinary, 'function');
|
|
assert.strictEqual(process.dlopen.length, 0);
|
|
|
|
const addonPath = path.join(
|
|
__dirname, '..', 'addons', 'hello-world', 'build', 'Release', 'binding.node');
|
|
if (!fs.existsSync(addonPath)) common.skip('the hello-world addon is not built');
|
|
const bytes = fs.readFileSync(addonPath);
|
|
const flags = (os.constants.dlopen?.RTLD_LAZY) || 0;
|
|
|
|
// A path that does not exist on disk, as a VFS-resident addon would be.
|
|
const fakePath = path.join(os.tmpdir(), 'nonexistent-vfs-dir', 'binding.node');
|
|
assert.ok(!fs.existsSync(fakePath));
|
|
|
|
const m = { exports: {} };
|
|
dlopenBinary(m, fakePath, flags, bytes);
|
|
assert.strictEqual(typeof m.exports.hello, 'function');
|
|
assert.strictEqual(m.exports.hello(), 'world');
|
|
|
|
// The flags argument may be left undefined when passing bytes.
|
|
const m2 = { exports: {} };
|
|
dlopenBinary(m2, fakePath, undefined, bytes);
|
|
assert.strictEqual(m2.exports.hello(), 'world');
|
|
|
|
// Without the bytes, that nonexistent path cannot be loaded.
|
|
assert.throws(() => dlopenBinary({ exports: {} }, fakePath, flags),
|
|
{ code: 'ERR_DLOPEN_FAILED' });
|
|
|
|
// A non-buffer binary is rejected.
|
|
assert.throws(
|
|
() => dlopenBinary({ exports: {} }, fakePath, flags, 'nope'),
|
|
{ code: 'ERR_INVALID_ARG_TYPE' });
|
|
|
|
// process.dlopen() ignores a fourth argument rather than loading from it: the
|
|
// public signature is unchanged, so this still fails on the missing path.
|
|
assert.throws(
|
|
() => process.dlopen({ exports: {} }, fakePath, flags, bytes),
|
|
{ code: 'ERR_DLOPEN_FAILED' });
|