Skip to content

Commit 91889e1

Browse files
committed
vfs: add remaining fs method interceptions and openAsBlob support
Add VFS interceptions for truncate, ftruncate, link, mkdtemp, opendir, openAsBlob, chmod, and utimes. Create VirtualDir class for opendir support. Fix cp/cpSync to bypass C++ fast paths (cpSyncCopyDir, cpSyncOverrideFile, cpSyncCheckPaths, internalModuleStat) when operating on VFS paths, since C++ bindings cannot see virtual files. Add chmod and utimes as no-ops for VFS paths since VFS does not track permissions or timestamps. Add hard link support to MemoryProvider. Add comprehensive tests for all new interceptions including cp and glob on VFS.
1 parent 772ee52 commit 91889e1

File tree

11 files changed

+1000
-19
lines changed

11 files changed

+1000
-19
lines changed

lib/fs.js

Lines changed: 119 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -665,6 +665,13 @@ function openAsBlob(path, options = kEmptyObject) {
665665
validateObject(options, 'options');
666666
const type = options.type || '';
667667
validateString(type, 'options.type');
668+
669+
const h = vfsState.handlers;
670+
if (h !== null) {
671+
const result = h.openAsBlob(path, options);
672+
if (result !== undefined) return PromiseResolve(result);
673+
}
674+
668675
// The underlying implementation here returns the Blob synchronously for now.
669676
// To give ourselves flexibility to maybe return the Blob asynchronously,
670677
// this API returns a Promise.
@@ -1206,6 +1213,21 @@ function truncate(path, len, callback) {
12061213
validateInteger(len, 'len');
12071214
len = MathMax(0, len);
12081215
validateFunction(callback, 'cb');
1216+
1217+
const h = vfsState.handlers;
1218+
if (h !== null) {
1219+
try {
1220+
const result = h.truncateSync(path, len);
1221+
if (result !== undefined) {
1222+
process.nextTick(callback, null);
1223+
return;
1224+
}
1225+
} catch (err) {
1226+
process.nextTick(callback, err);
1227+
return;
1228+
}
1229+
}
1230+
12091231
fs.open(path, 'r+', (er, fd) => {
12101232
if (er) return callback(er);
12111233
const req = new FSReqCallback();
@@ -1228,6 +1250,13 @@ function truncateSync(path, len) {
12281250
if (len === undefined) {
12291251
len = 0;
12301252
}
1253+
1254+
const h = vfsState.handlers;
1255+
if (h !== null) {
1256+
const result = h.truncateSync(path, len);
1257+
if (result !== undefined) return;
1258+
}
1259+
12311260
// Allow error to be thrown, but still close fd.
12321261
const fd = fs.openSync(path, 'r+');
12331262
try {
@@ -1253,6 +1282,15 @@ function ftruncate(fd, len = 0, callback) {
12531282
len = MathMax(0, len);
12541283
callback = makeCallback(callback);
12551284

1285+
const h = vfsState.handlers;
1286+
if (h !== null) {
1287+
const result = h.ftruncateSync(fd, len);
1288+
if (result !== undefined) {
1289+
process.nextTick(callback, null);
1290+
return;
1291+
}
1292+
}
1293+
12561294
const req = new FSReqCallback();
12571295
req.oncomplete = callback;
12581296
binding.ftruncate(fd, len, req);
@@ -1266,6 +1304,13 @@ function ftruncate(fd, len = 0, callback) {
12661304
*/
12671305
function ftruncateSync(fd, len = 0) {
12681306
validateInteger(len, 'len');
1307+
1308+
const h = vfsState.handlers;
1309+
if (h !== null) {
1310+
const result = h.ftruncateSync(fd, len < 0 ? 0 : len);
1311+
if (result !== undefined) return;
1312+
}
1313+
12691314
binding.ftruncate(fd, len < 0 ? 0 : len);
12701315
}
12711316

@@ -2233,6 +2278,20 @@ function symlinkSync(target, path, type) {
22332278
function link(existingPath, newPath, callback) {
22342279
callback = makeCallback(callback);
22352280

2281+
const h = vfsState.handlers;
2282+
if (h !== null) {
2283+
try {
2284+
const result = h.linkSync(existingPath, newPath);
2285+
if (result !== undefined) {
2286+
process.nextTick(callback, null);
2287+
return;
2288+
}
2289+
} catch (err) {
2290+
process.nextTick(callback, err);
2291+
return;
2292+
}
2293+
}
2294+
22362295
existingPath = getValidatedPath(existingPath, 'existingPath');
22372296
newPath = getValidatedPath(newPath, 'newPath');
22382297

@@ -2250,6 +2309,12 @@ function link(existingPath, newPath, callback) {
22502309
* @returns {void}
22512310
*/
22522311
function linkSync(existingPath, newPath) {
2312+
const h = vfsState.handlers;
2313+
if (h !== null) {
2314+
const result = h.linkSync(existingPath, newPath);
2315+
if (result !== undefined) return;
2316+
}
2317+
22532318
existingPath = getValidatedPath(existingPath, 'existingPath');
22542319
newPath = getValidatedPath(newPath, 'newPath');
22552320

@@ -2392,6 +2457,15 @@ function chmod(path, mode, callback) {
23922457
mode = parseFileMode(mode, 'mode');
23932458
callback = makeCallback(callback);
23942459

2460+
const h = vfsState.handlers;
2461+
if (h !== null) {
2462+
const result = h.chmodSync(path, mode);
2463+
if (result !== undefined) {
2464+
process.nextTick(callback, null);
2465+
return;
2466+
}
2467+
}
2468+
23952469
const req = new FSReqCallback();
23962470
req.oncomplete = callback;
23972471
binding.chmod(path, mode, req);
@@ -2407,6 +2481,12 @@ function chmodSync(path, mode) {
24072481
path = getValidatedPath(path);
24082482
mode = parseFileMode(mode, 'mode');
24092483

2484+
const h = vfsState.handlers;
2485+
if (h !== null) {
2486+
const result = h.chmodSync(path, mode);
2487+
if (result !== undefined) return;
2488+
}
2489+
24102490
binding.chmod(path, mode);
24112491
}
24122492

@@ -2529,6 +2609,15 @@ function utimes(path, atime, mtime, callback) {
25292609
callback = makeCallback(callback);
25302610
path = getValidatedPath(path);
25312611

2612+
const h = vfsState.handlers;
2613+
if (h !== null) {
2614+
const result = h.utimesSync(path, atime, mtime);
2615+
if (result !== undefined) {
2616+
process.nextTick(callback, null);
2617+
return;
2618+
}
2619+
}
2620+
25322621
const req = new FSReqCallback();
25332622
req.oncomplete = callback;
25342623
binding.utimes(
@@ -2548,8 +2637,16 @@ function utimes(path, atime, mtime, callback) {
25482637
* @returns {void}
25492638
*/
25502639
function utimesSync(path, atime, mtime) {
2640+
path = getValidatedPath(path);
2641+
2642+
const h = vfsState.handlers;
2643+
if (h !== null) {
2644+
const result = h.utimesSync(path, atime, mtime);
2645+
if (result !== undefined) return;
2646+
}
2647+
25512648
binding.utimes(
2552-
getValidatedPath(path),
2649+
path,
25532650
toUnixTimestamp(atime),
25542651
toUnixTimestamp(mtime),
25552652
);
@@ -3460,6 +3557,21 @@ realpath.native = (path, options, callback) => {
34603557
*/
34613558
function mkdtemp(prefix, options, callback) {
34623559
callback = makeCallback(typeof options === 'function' ? options : callback);
3560+
3561+
const h = vfsState.handlers;
3562+
if (h !== null) {
3563+
try {
3564+
const result = h.mkdtempSync(prefix);
3565+
if (result !== undefined) {
3566+
process.nextTick(callback, null, result);
3567+
return;
3568+
}
3569+
} catch (err) {
3570+
process.nextTick(callback, err);
3571+
return;
3572+
}
3573+
}
3574+
34633575
options = getOptions(options);
34643576

34653577
prefix = getValidatedPath(prefix, 'prefix');
@@ -3477,6 +3589,12 @@ function mkdtemp(prefix, options, callback) {
34773589
* @returns {string}
34783590
*/
34793591
function mkdtempSync(prefix, options) {
3592+
const h = vfsState.handlers;
3593+
if (h !== null) {
3594+
const result = h.mkdtempSync(prefix);
3595+
if (result !== undefined) return result;
3596+
}
3597+
34803598
options = getOptions(options);
34813599

34823600
prefix = getValidatedPath(prefix, 'prefix');

lib/internal/fs/cp/cp-sync.js

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,25 @@ function cpSyncFn(src, dest, opts) {
5353
if (!shouldCopy) return;
5454
}
5555

56-
fsBinding.cpSyncCheckPaths(src, dest, opts.dereference, opts.recursive);
56+
// Skip C++ path checks for VFS paths (C++ lstat can't see VFS files).
57+
// The JS-level getStats() calls lstatSync which is VFS-intercepted.
58+
const { vfsState } = require('internal/fs/utils');
59+
if (vfsState.handlers === null || !vfsState.handlers.statSync(src)) {
60+
fsBinding.cpSyncCheckPaths(src, dest, opts.dereference, opts.recursive);
61+
} else {
62+
// Minimal JS-level validation that mirrors what C++ does
63+
const srcResolved = resolve(src);
64+
const destResolved = resolve(dest);
65+
if (isSrcSubdir(srcResolved, destResolved)) {
66+
throw new ERR_FS_CP_EINVAL({
67+
message: `cannot copy '${srcResolved}' to a subdirectory of itself, '${destResolved}'`,
68+
path: destResolved,
69+
syscall: 'cp',
70+
errno: EINVAL,
71+
code: 'EINVAL',
72+
});
73+
}
74+
}
5775

5876
return getStats(src, dest, opts);
5977
}
@@ -83,7 +101,13 @@ function onFile(srcStat, destStat, src, dest, opts) {
83101
if (!destStat) return copyFile(srcStat, src, dest, opts);
84102

85103
if (opts.force) {
86-
return fsBinding.cpSyncOverrideFile(src, dest, opts.mode, opts.preserveTimestamps);
104+
// Skip C++ fast path for VFS paths (C++ can't see VFS files).
105+
const { vfsState } = require('internal/fs/utils');
106+
if (vfsState.handlers === null || !vfsState.handlers.statSync(src)) {
107+
return fsBinding.cpSyncOverrideFile(src, dest, opts.mode, opts.preserveTimestamps);
108+
}
109+
unlinkSync(dest);
110+
return copyFile(srcStat, src, dest, opts);
87111
}
88112

89113
if (opts.errorOnExist) {
@@ -140,14 +164,18 @@ function onDir(srcStat, destStat, src, dest, opts) {
140164
function copyDir(src, dest, opts, mkDir, srcMode) {
141165
if (!opts.filter) {
142166
// The caller didn't provide a js filter function, in this case
143-
// we can run the whole function faster in C++
144-
// TODO(dario-piotrowicz): look into making cpSyncCopyDir also accept the potential filter function
145-
return fsBinding.cpSyncCopyDir(src, dest,
146-
opts.force,
147-
opts.dereference,
148-
opts.errorOnExist,
149-
opts.verbatimSymlinks,
150-
opts.preserveTimestamps);
167+
// we can run the whole function faster in C++.
168+
// Skip C++ fast path for VFS paths (C++ can't see VFS files).
169+
const { vfsState } = require('internal/fs/utils');
170+
if (vfsState.handlers === null || !vfsState.handlers.statSync(src)) {
171+
// TODO(dario-piotrowicz): look into making cpSyncCopyDir also accept the potential filter function
172+
return fsBinding.cpSyncCopyDir(src, dest,
173+
opts.force,
174+
opts.dereference,
175+
opts.errorOnExist,
176+
opts.verbatimSymlinks,
177+
opts.preserveTimestamps);
178+
}
151179
}
152180

153181
if (mkDir) {
@@ -209,7 +237,14 @@ function onLink(destStat, src, dest, verbatimSymlinks) {
209237
if (!isAbsolute(resolvedDest)) {
210238
resolvedDest = resolve(dirname(dest), resolvedDest);
211239
}
212-
const srcIsDir = fsBinding.internalModuleStat(src) === 1;
240+
// internalModuleStat is a C++ call that can't see VFS files.
241+
const { vfsState } = require('internal/fs/utils');
242+
let srcIsDir;
243+
if (vfsState.handlers !== null && vfsState.handlers.statSync(src)) {
244+
srcIsDir = statSync(src).isDirectory();
245+
} else {
246+
srcIsDir = fsBinding.internalModuleStat(src) === 1;
247+
}
213248

214249
if (srcIsDir && isSrcSubdir(resolvedSrc, resolvedDest)) {
215250
throw new ERR_FS_CP_EINVAL({

lib/internal/fs/cp/cp.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,14 @@ async function onLink(destStat, src, dest, opts) {
355355
resolvedDest = resolve(dirname(dest), resolvedDest);
356356
}
357357

358-
const srcIsDir = fsBinding.internalModuleStat(src) === 1;
358+
// internalModuleStat is a C++ call that can't see VFS files.
359+
const { vfsState } = require('internal/fs/utils');
360+
let srcIsDir;
361+
if (vfsState.handlers !== null && vfsState.handlers.statSync(src)) {
362+
srcIsDir = (await stat(src)).isDirectory();
363+
} else {
364+
srcIsDir = fsBinding.internalModuleStat(src) === 1;
365+
}
359366

360367
if (srcIsDir && isSrcSubdir(resolvedSrc, resolvedDest)) {
361368
throw new ERR_FS_CP_EINVAL({

lib/internal/fs/dir.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const {
3131
getDirent,
3232
getOptions,
3333
getValidatedPath,
34+
vfsState,
3435
} = require('internal/fs/utils');
3536
const {
3637
validateFunction,
@@ -330,6 +331,20 @@ function opendir(path, options, callback) {
330331
callback = typeof options === 'function' ? options : callback;
331332
validateFunction(callback, 'callback');
332333

334+
const h = vfsState.handlers;
335+
if (h !== null) {
336+
try {
337+
const result = h.opendirSync(path, options);
338+
if (result !== undefined) {
339+
process.nextTick(callback, null, result);
340+
return;
341+
}
342+
} catch (err) {
343+
process.nextTick(callback, err);
344+
return;
345+
}
346+
}
347+
333348
path = getValidatedPath(path);
334349
options = getOptions(options, {
335350
encoding: 'utf8',
@@ -354,6 +369,12 @@ function opendir(path, options, callback) {
354369
}
355370

356371
function opendirSync(path, options) {
372+
const h = vfsState.handlers;
373+
if (h !== null) {
374+
const result = h.opendirSync(path, options);
375+
if (result !== undefined) return result;
376+
}
377+
357378
path = getValidatedPath(path);
358379
options = getOptions(options, { encoding: 'utf8' });
359380

0 commit comments

Comments
 (0)