Skip to content
Open
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
23 changes: 23 additions & 0 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,26 @@ process.permission.has('fs.read', 'custom-require.js'); // true
process.permission.has('fs.read', 'custom-require-2.js'); // true
```

### `--allow-fs-vfs`
Comment thread
mcollina marked this conversation as resolved.

<!-- YAML
added: REPLACEME
-->

> Stability: 1.1 - Active development

When using the [Permission Model][], a [virtual file system][] cannot be
mounted by default: [`vfs.mount()`][] throws `ERR_INVALID_STATE` unless the
user explicitly passes the `--allow-fs-vfs` flag when starting Node.js.

A mounted VFS serves paths that the file system permissions do not describe,
so mounting one is gated on its own flag rather than on `--allow-fs-read` or
`--allow-fs-write`.

```console
$ node --experimental-vfs --permission --allow-fs-vfs app.js
```

### `--allow-fs-write`

<!-- YAML
Expand Down Expand Up @@ -3878,6 +3898,7 @@ one is included in the list below.
* `--allow-child-process`
* `--allow-ffi`
* `--allow-fs-read`
* `--allow-fs-vfs`
* `--allow-fs-write`
* `--allow-inspector`
* `--allow-net`
Expand Down Expand Up @@ -4589,6 +4610,7 @@ node --stack-trace-limit=12 -p -e "Error.stackTraceLimit" # prints 12
[`v8.startupSnapshot.addDeserializeCallback()`]: v8.md#v8startupsnapshotadddeserializecallbackcallback-data
[`v8.startupSnapshot.setDeserializeMainFunction()`]: v8.md#v8startupsnapshotsetdeserializemainfunctioncallback-data
[`v8.startupSnapshot` API]: v8.md#startup-snapshot-api
[`vfs.mount()`]: vfs.md#vfsmount
[asynchronous module customization hooks]: module.md#asynchronous-customization-hooks
[captured by the built-in snapshot of Node.js]: https://github.com/nodejs/node/blob/b19525a33cc84033af4addd0f80acd4dc33ce0cf/test/parallel/test-bootstrap-modules.js#L24
[collecting code coverage from tests]: test.md#collecting-code-coverage
Expand Down Expand Up @@ -4620,4 +4642,5 @@ node --stack-trace-limit=12 -p -e "Error.stackTraceLimit" # prints 12
[test runner execution model]: test.md#test-runner-execution-model
[timezone IDs]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
[tracking issue for user-land snapshots]: https://github.com/nodejs/node/issues/44014
[virtual file system]: vfs.md
[ways that `TZ` is handled in other environments]: https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
6 changes: 6 additions & 0 deletions doc/api/vfs.md
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,12 @@ system, the callers are responsible for avoiding removal or
invalidation of modules in the virtual file system while they are
being loaded.

Native addons (`.node` files) stored in a mounted VFS can be `require()`d as
well. The operating system's dynamic loader cannot open a virtual path, so the
addon's bytes are read from the VFS and loaded from a private, self-cleaning
temporary image instead. Addons on the real file system are unaffected and
load directly.

## Class: `VirtualProvider`

<!-- YAML
Expand Down
13 changes: 13 additions & 0 deletions doc/node.1
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,17 @@ process.permission.has('fs.read', 'custom-require.js'); // true
process.permission.has('fs.read', 'custom-require-2.js'); // true
.Ed
.
.It Fl -allow-fs-vfs
When using the Permission Model, a virtual file system cannot be
mounted by default: \fBvfs.mount()\fR throws \fBERR_INVALID_STATE\fR unless the
user explicitly passes the \fB--allow-fs-vfs\fR flag when starting Node.js.
A mounted VFS serves paths that the file system permissions do not describe,
so mounting one is gated on its own flag rather than on \fB--allow-fs-read\fR or
\fB--allow-fs-write\fR.
.Bd -literal
$ node --experimental-vfs --permission --allow-fs-vfs app.js
.Ed
.
.It Fl -allow-fs-write
This flag configures file system write permissions using
the Permission Model.
Expand Down Expand Up @@ -1944,6 +1955,8 @@ one is included in the list below.
.It
\fB--allow-fs-read\fR
.It
\fB--allow-fs-vfs\fR
.It
\fB--allow-fs-write\fR
.It
\fB--allow-inspector\fR
Expand Down
22 changes: 22 additions & 0 deletions lib/internal/vfs/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -914,6 +914,26 @@ function installModuleLoaderOverrides() {
});
}

let originalDlopen;

function installAddonLoader() {
originalDlopen = process.dlopen;
process.dlopen = function(module, filename, flags) {
Comment on lines +920 to +921

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Just reflecting... we need a better way to do this stuff. This kind of monkeypatching makes me sad.

@pipobscure pipobscure Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I agree, but this hits only code loading when on VFS. Making it generic would cause it to be run on every module load, which I was just very hesitant to do. This way it's only a thing if VFS is in use.

Maybe the right answer is to make a note that this needs cleaning up (I volunteer) once VFS is out of experimental. Until then I think this is the right shape.

// dlopen(2) cannot open a native addon that lives in a VFS by path (it has
// no real inode). Read its bytes and hand them to the internal
// dlopenBinary(), which writes them to a private, self-cleaning temporary
// image - an in-memory memfd on Linux - and loads that. Only VFS paths take
// this route; everything else loads straight from disk through the
// unchanged process.dlopen().
if (StringPrototypeStartsWith(filename, normalizedVfsRootPrefix)) {
const { readFileSync } = require('fs');
const { dlopenBinary } = internalBinding('process_methods');
return dlopenBinary(module, filename, flags, readFileSync(filename));
}
return originalDlopen(module, filename, flags);
};
}

/**
* Install all VFS hooks: module loader overrides and fs handlers.
*/
Expand All @@ -922,6 +942,7 @@ function installHooks() {
debug('install hooks');
normalizedVfsRootPrefix = getNormalizedVfsRoot() + sep;
installModuleLoaderOverrides();
installAddonLoader();
vfsHandlerObj = createVfsHandlers();
setVfsHandlers(vfsHandlerObj);
hooksInstalled = true;
Expand All @@ -939,6 +960,7 @@ function uninstallHooks() {
setLoaderOverrides();
setVfsHandlers(null);
vfsHandlerObj = undefined;
process.dlopen = originalDlopen;
hooksInstalled = false;
}

Expand Down
Loading
Loading