Files
node/test/parallel/test-vfs-reserved-root.js
Matteo Collina 49ad95045a vfs: unflag
Enable node:vfs by default. --no-experimental-vfs disables node:vfs
exposure and --vfs-load.

Assisted-by: pi
Signed-off-by: Matteo Collina <hello@matteocollina.com>
PR-URL: https://github.com/nodejs/node/pull/66318
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Paolo Insogna <paolo@cowtech.it>
2026-10-15 07:53:03 +00:00

209 lines
8.2 KiB
JavaScript

// Flags: --disable-warning=ExperimentalWarning
'use strict';
const common = require('../common');
const assert = require('assert');
const fs = require('fs');
const os = require('os');
const path = require('path');
const vfs = require('node:vfs');
// The reserved root is where every mount point lives, and vfs.vfsBase() is
// how a program finds it without spelling the path out.
const root = vfs.vfsBase();
assert.strictEqual(root, path.join(os.devNull, 'vfs'));
assert.strictEqual(vfs.vfsBase(), root);
const a = vfs.create();
a.mkdirSync('/dir');
a.writeFileSync('/dir/file.txt', 'from a');
a.writeFileSync('/mod.js', 'module.exports = __filename;');
const b = vfs.create();
b.writeFileSync('/file.txt', 'from b');
const empty = vfs.create();
const mountA = a.mount();
const mountB = b.mount();
const mountEmpty = empty.mount();
assert.strictEqual(path.dirname(mountA), root);
const idA = path.basename(mountA);
const idB = path.basename(mountB);
const idEmpty = path.basename(mountEmpty);
const layerIds = [idA, idB, idEmpty].sort((x, y) => x - y);
// The root lists every layer by id.
{
assert.deepStrictEqual(fs.readdirSync(root), layerIds);
assert.deepStrictEqual(fs.readdirSync(`${root}${path.sep}`), layerIds);
assert.deepStrictEqual(fs.readdirSync(root, { encoding: 'buffer' }),
layerIds.map((name) => Buffer.from(name)));
for (const id of fs.readdirSync(root)) {
assert.ok([mountA, mountB, mountEmpty].includes(path.join(root, id)), id);
}
const dirents = fs.readdirSync(root, { withFileTypes: true });
assert.deepStrictEqual(dirents.map((d) => d.name), layerIds);
for (const dirent of dirents) {
assert.strictEqual(dirent.parentPath, root);
assert.ok(dirent.isDirectory(), dirent.name);
}
const dir = fs.opendirSync(root);
const names = [];
let dirent;
while ((dirent = dir.readSync()) !== null) names.push(dirent.name);
dir.closeSync();
assert.deepStrictEqual(names, layerIds);
fs.readdir(root, common.mustSucceed((result) => {
assert.deepStrictEqual(result, layerIds);
}));
fs.promises.readdir(root).then(common.mustCall((result) => {
assert.deepStrictEqual(result, layerIds);
}));
}
// A recursive listing descends into the layers.
{
const expected = [
...layerIds,
`${idA}/dir`, `${idA}/mod.js`, `${idA}/dir/file.txt`,
`${idB}/file.txt`,
];
assert.deepStrictEqual(fs.readdirSync(root, { recursive: true }).sort(),
expected.sort());
// Every API names each entry's directory as a host path.
const options = { recursive: true, withFileTypes: true };
const check = common.mustCall((dirents) => {
const file = dirents.find((d) => d.name === 'file.txt' &&
d.parentPath === path.join(mountA, 'dir'));
assert.ok(file?.isFile());
const layer = dirents.find((d) => d.name === idA);
assert.strictEqual(layer?.parentPath, root);
}, 3);
check(fs.readdirSync(root, options));
fs.readdir(root, options, common.mustSucceed(check));
fs.promises.readdir(root, options).then(common.mustCall((dirents) => {
check(dirents);
}));
}
// The root is a directory, and so is each mount point in it.
{
assert.ok(fs.statSync(root).isDirectory());
assert.ok(fs.lstatSync(root).isDirectory());
assert.ok(fs.statSync(root, { bigint: true }).isDirectory());
assert.ok(fs.existsSync(root));
fs.accessSync(root, fs.constants.R_OK);
assert.strictEqual(fs.realpathSync(root), root);
assert.strictEqual(fs.realpathSync(mountA), mountA);
assert.throws(() => fs.readlinkSync(root), { code: 'EINVAL' });
assert.ok(fs.lstatSync(mountA).isDirectory());
fs.stat(root, common.mustSucceed((stats) => {
assert.ok(stats.isDirectory());
}));
fs.realpath(root, common.mustSucceed((real) => {
assert.strictEqual(real, root);
}));
fs.promises.lstat(root).then(common.mustCall((stats) => {
assert.ok(stats.isDirectory());
}));
}
// Anything else under the root does not exist.
{
for (const missing of ['missing', 'missing/deeper', `${idA}0`, `0${idA}`,
`${idA}.js`, '-1']) {
const p = path.join(root, missing);
assert.strictEqual(fs.existsSync(p), false, p);
assert.throws(() => fs.statSync(p), { code: 'ENOENT' }, p);
assert.throws(() => fs.lstatSync(p), { code: 'ENOENT' }, p);
assert.throws(() => fs.readdirSync(p), { code: 'ENOENT' }, p);
assert.throws(() => fs.readFileSync(p), { code: 'ENOENT' }, p);
assert.throws(() => fs.realpathSync(p), { code: 'ENOENT' }, p);
assert.strictEqual(fs.statSync(p, { throwIfNoEntry: false }), undefined);
}
}
// The root is read-only; the file systems in it are not.
{
const erofs = { code: 'EROFS' };
const newPath = path.join(root, 'new');
assert.throws(() => fs.writeFileSync(newPath, ''), erofs);
assert.throws(() => fs.mkdirSync(newPath), erofs);
assert.throws(() => fs.mkdirSync(path.join(newPath, 'deeper'), { recursive: true }), erofs);
assert.throws(() => fs.mkdtempSync(path.join(root, 'tmp-')), erofs);
assert.throws(() => fs.symlinkSync(idA, newPath), erofs);
assert.throws(() => fs.openSync(newPath, 'w'), erofs);
assert.throws(() => fs.openSync(root, 'r+'), erofs);
assert.throws(() => fs.openSync(root, 'r'), { code: 'EISDIR' });
assert.throws(() => fs.rmSync(root, { recursive: true, force: true }), erofs);
// Like the mount points in it, the root cannot be removed.
assert.throws(() => fs.rmdirSync(root), { code: 'EBUSY' });
assert.throws(() => fs.chmodSync(root, 0o777), erofs);
assert.throws(() => fs.utimesSync(root, 0, 0), erofs);
// Only an existing entry is reported as read-only.
assert.throws(() => fs.unlinkSync(newPath), { code: 'ENOENT' });
fs.rmSync(newPath, { force: true });
// Creating what exists fails as it would anywhere.
assert.throws(() => fs.mkdirSync(root), { code: 'EEXIST' });
fs.mkdirSync(root, { recursive: true });
// A layer and the root are different file systems.
assert.throws(() => fs.renameSync(path.join(mountA, 'mod.js'), newPath),
{ code: 'EXDEV' });
// Nothing was removed, and the layers stay writable.
assert.strictEqual(fs.readFileSync(path.join(mountA, 'dir', 'file.txt'), 'utf8'),
'from a');
fs.writeFileSync(path.join(mountB, 'new.txt'), 'written');
assert.strictEqual(fs.readFileSync(path.join(mountB, 'new.txt'), 'utf8'), 'written');
fs.rm(root, { recursive: true }, common.expectsError(erofs));
assert.rejects(fs.promises.rm(root, { recursive: true }), erofs).then(common.mustCall());
assert.rejects(fs.promises.mkdir(newPath), erofs).then(common.mustCall());
}
// A mount point cannot be removed or renamed, nor replaced by a rename.
{
const busy = { code: 'EBUSY' };
const c = vfs.create();
c.mkdirSync('/dir');
c.writeFileSync('/dir/file.txt', 'data');
const mountC = c.mount();
assert.throws(() => fs.rmdirSync(mountC), busy);
assert.throws(() => fs.renameSync(mountC, path.join(mountC, 'moved')), busy);
assert.throws(() => fs.renameSync(path.join(mountC, 'dir'), mountC), busy);
assert.throws(() => c.rmdirSync(mountC), busy);
// A recursive removal empties the file system, then fails on its root.
assert.throws(() => fs.rmSync(mountC, { recursive: true }), busy);
assert.deepStrictEqual(fs.readdirSync(mountC), []);
assert.throws(() => fs.rmdirSync(mountC), busy);
assert.ok(fs.statSync(mountC).isDirectory());
assert.strictEqual(c.mounted, true);
fs.rmdir(mountC, common.expectsError(busy));
assert.rejects(fs.promises.rmdir(mountC), busy).then(common.mustCall());
assert.rejects(fs.promises.rename(mountC, path.join(mountC, 'moved')), busy)
.then(common.mustCall());
assert.rejects(fs.promises.rm(mountC, { recursive: true }), busy)
.then(() => c.unmount()).then(common.mustCall());
// The same holds for the root of a file system that is not mounted.
const unmounted = vfs.create();
assert.throws(() => unmounted.rmdirSync('/'), busy);
assert.throws(() => unmounted.renameSync('/', '/moved'), busy);
}
// Unmounting removes a layer from the root; with nothing mounted, there is
// no root directory.
process.on('beforeExit', common.mustCall(() => {
a.unmount();
b.unmount();
assert.deepStrictEqual(fs.readdirSync(root), [idEmpty]);
assert.strictEqual(fs.existsSync(mountA), false);
empty.unmount();
assert.strictEqual(fs.existsSync(root), false);
}));