ffi: load libraries from a mounted VFS - #65909
Conversation
|
LETM (Looks Excellent To Me 😃 ) |
|
The only question I have is whether we want to hide the detail that the path may need materializing inside And since |
fa3e51d to
dd7d5f0
Compare
|
@pipobscure updated, PTAL |
dd7d5f0 to
c994e3d
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #65909 +/- ##
==========================================
- Coverage 90.16% 90.16% -0.01%
==========================================
Files 771 773 +2
Lines 265097 265578 +481
Branches 50358 50488 +130
==========================================
+ Hits 239026 239453 +427
- Misses 17011 17070 +59
+ Partials 9060 9055 -5
🚀 New features to boost your workflow:
|
c994e3d to
672232a
Compare
|
Awesome stuff! => LGTM |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
The operating system's dynamic loader cannot open a library that lives in a mounted virtual file system: the reserved mount path has no real inode. Native addons already handle this in require(): the loader hands their bytes to process.dlopen(), which loads them from a private, self-cleaning image - an anonymous in-memory memfd on Linux. Make ffi.dlopen() and new DynamicLibrary() do the same transparently. Mirroring the fs handler integration, the VFS hook installer sets a library reader into node:ffi while at least one VFS is mounted and clears it when the last one unmounts; DynamicLibrary consults it before every load, so the dependency points from the VFS into ffi and ffi never loads any VFS code. The reader hands the library's bytes to the native constructor, which loads them from the same kind of image, released right after the load, while library.path keeps reporting the virtual path. Libraries on the real file system are unaffected and load directly, and pay only a null check while no VFS is mounted. The AddonImage materializer moves from an anonymous namespace in node_binding.cc to node_binding.h so that node_ffi.cc can reuse it. On Windows the image is now written and closed before the load, because the loader shares read alone and a retained writable delete-on-close handle failed the load with ERROR_SHARING_VIOLATION; since a mapped image cannot be unlinked there, it is kept with the module it loaded as and both are released at process exit. On POSIX the image still never outlives the constructor call, so nothing is left for dlclose() to clean up. Also fix the VFS dlopen hook forwarding a missing flags argument as undefined, which process.dlopen() coerces to 0 - not a valid dlopen(2) mode - so loading any addon from the real file system failed with EINVAL while a VFS was mounted. Co-authored-by: Philipp Dunkel <pipobscure@users.noreply.github.com> Signed-off-by: Matteo Collina <hello@matteocollina.com>
f0084c5 to
17f29b3
Compare
Failed to start CI- Validating Jenkins credentials ✔ Jenkins credentials valid - Querying data for job/node-test-pull-request/77322/ SyntaxError: Unexpected token '<', ..." https://github.com/nodejs/node/actions/runs/34634996295 |
Failed to start CI- Validating Jenkins credentials ✔ Jenkins credentials valid - Querying data for job/node-test-pull-request/77322/ SyntaxError: Unexpected token '<', ..." https://github.com/nodejs/node/actions/runs/34647251003 |
Failed to start CI- Validating Jenkins credentials ✔ Jenkins credentials valid - Querying data for job/node-test-pull-request/77322/ SyntaxError: Unexpected token '<', ..." https://github.com/nodejs/node/actions/runs/34648712529 |
Failed to start CI- Validating Jenkins credentials ✔ Jenkins credentials valid - Querying data for job/node-test-pull-request/77322/ SyntaxError: Unexpected token '<', ..." https://github.com/nodejs/node/actions/runs/34683737619 |
This comment has been minimized.
This comment has been minimized.
|
The operating system's dynamic loader cannot open a library that lives in a mounted virtual file system: the reserved mount path has no real inode. Native addons already handle this in
require(): the loader hands their bytes toprocess.dlopen(), which loads them from a private, self-cleaning image — an anonymous in-memory memfd on Linux.This makes
ffi.dlopen()andnew ffi.DynamicLibrary()do the same, transparently:setVfsHandlers), the VFS hook installer sets a library reader intonode:ffiwhile at least one VFS is mounted and clears it when the last one unmounts, so the dependency points from the VFS into ffi and ffi never loads any VFS code. The reader hands the library's bytes to the native constructor, which loads them from the same kind of private image (AddonImage, moved from an anonymous namespace innode_binding.cctonode_binding.hsonode_ffi.cccan reuse it).library.pathkeeps reporting the virtual path.uv_dlopen()on POSIX (in-memory memfd on Linux, so nothing touches the file system at all), and there is nothing left fordlclose()to clean up or reference-count. Windows retains the delete-on-close handle for the process lifetime, exactly as for addons.dlopenBinary().Bug fix included
Writing the test exposed a pre-existing bug, fixed in the first commit: the dlopen hook installed while a VFS is mounted always forwarded its
flagsparameter, so a two-argumentprocess.dlopen()call for a real file-system path reached the original implementation withundefinedas the flags. That coerces to0, which is not a validdlopen(2)mode, and loading any addon from the real file system failed with EINVAL while a VFS was mounted.