33const {
44 MathRandom,
55 ObjectFreeze,
6+ StringPrototypeLastIndexOf,
7+ StringPrototypeSlice,
68 StringPrototypeStartsWith,
79 Symbol,
810 SymbolDispose,
@@ -21,6 +23,7 @@ const { join: joinPath } = pathPosix;
2123const {
2224 getLayerRoot,
2325 getRelativePath,
26+ getVfsRoot,
2427} = require ( 'internal/vfs/router' ) ;
2528const {
2629 openVirtualFd,
@@ -30,6 +33,7 @@ const {
3033const {
3134 createENOENT,
3235 createEBADF,
36+ createEBUSY,
3337 createEISDIR,
3438} = require ( 'internal/vfs/errors' ) ;
3539const { VirtualReadStream, VirtualWriteStream } = require ( 'internal/vfs/streams' ) ;
@@ -47,6 +51,7 @@ const kNormalizedMountPoint = Symbol('kNormalizedMountPoint');
4751const kMounted = Symbol ( 'kMounted' ) ;
4852const kPromises = Symbol ( 'kPromises' ) ;
4953const kLayerId = Symbol ( 'kLayerId' ) ;
54+ const kReservedRoot = Symbol ( 'kReservedRoot' ) ;
5055
5156let nextLayerId = 0 ;
5257
@@ -74,6 +79,12 @@ function randomSuffix() {
7479 return suffix ;
7580}
7681
82+ // The root of a file system is its mount point, and like any mount point it
83+ // cannot be removed or renamed, nor replaced by a rename.
84+ function checkNotRoot ( providerPath , syscall , path ) {
85+ if ( providerPath === '/' ) throw createEBUSY ( syscall , path ) ;
86+ }
87+
7788let registerVFS ;
7889let deregisterVFS ;
7990
@@ -116,10 +127,20 @@ class VirtualFileSystem {
116127 }
117128
118129 this [ kProvider ] = provider ?? new MemoryProvider ( ) ;
130+ this [ kPromises ] = null ;
131+ if ( options [ kReservedRoot ] === true ) {
132+ // Serves the reserved root directory itself. It is not a layer, so it
133+ // takes no layer id and leaves the numbering of real mounts alone.
134+ const root = getVfsRoot ( ) ;
135+ this [ kMountPoint ] = root ;
136+ this [ kNormalizedMountPoint ] = normalizeMountedPath ( root ) ;
137+ this [ kMounted ] = true ;
138+ this [ kLayerId ] = - 1 ;
139+ return ;
140+ }
119141 this [ kMountPoint ] = null ;
120142 this [ kNormalizedMountPoint ] = null ;
121143 this [ kMounted ] = false ;
122- this [ kPromises ] = null ;
123144 this [ kLayerId ] = nextLayerId ++ ;
124145 }
125146
@@ -254,6 +275,8 @@ class VirtualFileSystem {
254275 */
255276 #toMountedPath( providerPath ) {
256277 if ( this [ kMounted ] && this [ kMountPoint ] ) {
278+ // path.join() would keep the trailing separator of the provider root.
279+ if ( providerPath === '/' ) return this [ kMountPoint ] ;
257280 return path . join ( this [ kMountPoint ] , providerPath ) ;
258281 }
259282 return providerPath ;
@@ -337,28 +360,36 @@ class VirtualFileSystem {
337360 readdirSync ( dirPath , options ) {
338361 const providerPath = this . #toProviderPath( dirPath ) ;
339362 const result = this [ kProvider ] . readdirSync ( providerPath , options ) ;
363+ return this . #toMountedDirents( dirPath , result , options ) ;
364+ }
340365
341- // Rewrite Dirent parentPath from provider-relative to VFS path.
342- if ( options ?. withFileTypes === true ) {
343- const recursive = options ?. recursive === true ;
344- for ( let i = 0 ; i < result . length ; i ++ ) {
345- const dirent = result [ i ] ;
346- if ( recursive ) {
347- // In recursive mode, name may contain slashes (e.g. 'a/b.txt').
348- const slashIdx = dirent . name . lastIndexOf ( '/' ) ;
349- if ( slashIdx !== - 1 ) {
350- const subdir = dirent . name . slice ( 0 , slashIdx ) ;
351- dirent . parentPath = joinPath ( dirPath , subdir ) ;
352- dirent . name = dirent . name . slice ( slashIdx + 1 ) ;
353- } else {
354- dirent . parentPath = dirPath ;
355- }
356- } else {
357- dirent . parentPath = dirPath ;
366+ /**
367+ * Rewrites the Dirents of a listing of `dirPath` from provider-relative to
368+ * VFS paths, so that each `parentPath` is the directory the entry is in.
369+ * @param {string } dirPath The listed directory, as given by the caller
370+ * @param {string[]|Dirent[] } result The provider's listing
371+ * @param {object } [options] The readdir options
372+ * @returns {string[]|Dirent[] }
373+ */
374+ #toMountedDirents( dirPath , result , options ) {
375+ if ( options ?. withFileTypes !== true ) return result ;
376+ const recursive = options ?. recursive === true ;
377+ // A mounted VFS is addressed by host paths, so, like fs, join with the
378+ // host's separator; an unmounted one uses POSIX paths throughout.
379+ const join = this [ kMounted ] ? path . join : joinPath ;
380+ for ( let i = 0 ; i < result . length ; i ++ ) {
381+ const dirent = result [ i ] ;
382+ dirent . parentPath = dirPath ;
383+ if ( recursive ) {
384+ // In recursive mode, name may contain slashes (e.g. 'a/b.txt').
385+ const slashIdx = StringPrototypeLastIndexOf ( dirent . name , '/' ) ;
386+ if ( slashIdx !== - 1 ) {
387+ const subdir = StringPrototypeSlice ( dirent . name , 0 , slashIdx ) ;
388+ dirent . parentPath = join ( dirPath , subdir ) ;
389+ dirent . name = StringPrototypeSlice ( dirent . name , slashIdx + 1 ) ;
358390 }
359391 }
360392 }
361-
362393 return result ;
363394 }
364395
@@ -380,6 +411,7 @@ class VirtualFileSystem {
380411 */
381412 rmdirSync ( dirPath ) {
382413 const providerPath = this . #toProviderPath( dirPath ) ;
414+ checkNotRoot ( providerPath , 'rmdir' , dirPath ) ;
383415 this [ kProvider ] . rmdirSync ( providerPath ) ;
384416 }
385417
@@ -400,6 +432,8 @@ class VirtualFileSystem {
400432 renameSync ( oldPath , newPath ) {
401433 const oldProviderPath = this . #toProviderPath( oldPath ) ;
402434 const newProviderPath = this . #toProviderPath( newPath ) ;
435+ checkNotRoot ( oldProviderPath , 'rename' , oldPath ) ;
436+ checkNotRoot ( newProviderPath , 'rename' , newPath ) ;
403437 this [ kProvider ] . renameSync ( oldProviderPath , newProviderPath ) ;
404438 }
405439
@@ -768,7 +802,8 @@ class VirtualFileSystem {
768802 }
769803
770804 this [ kProvider ] . readdir ( this . #toProviderPath( dirPath ) , options )
771- . then ( ( entries ) => callback ( null , entries ) , ( err ) => callback ( err ) ) ;
805+ . then ( ( entries ) => callback ( null , this . #toMountedDirents( dirPath , entries , options ) ) ,
806+ ( err ) => callback ( err ) ) ;
772807 }
773808
774809 /**
@@ -1128,6 +1163,8 @@ class VirtualFileSystem {
11281163 const toProviderPath = ( p ) => this . #toProviderPath( p ) ;
11291164 const toProviderPrefix = ( p ) => this . #toProviderPrefix( p ) ;
11301165 const toMountedPath = ( p ) => this . #toMountedPath( p ) ;
1166+ const toMountedDirents = ( p , result , options ) =>
1167+ this . #toMountedDirents( p , result , options ) ;
11311168
11321169 return ObjectFreeze ( {
11331170 async readFile ( filePath , options ) {
@@ -1157,7 +1194,8 @@ class VirtualFileSystem {
11571194
11581195 async readdir ( dirPath , options ) {
11591196 const providerPath = toProviderPath ( dirPath ) ;
1160- return provider . readdir ( providerPath , options ) ;
1197+ const result = await provider . readdir ( providerPath , options ) ;
1198+ return toMountedDirents ( dirPath , result , options ) ;
11611199 } ,
11621200
11631201 async mkdir ( dirPath , options ) {
@@ -1168,6 +1206,7 @@ class VirtualFileSystem {
11681206
11691207 async rmdir ( dirPath ) {
11701208 const providerPath = toProviderPath ( dirPath ) ;
1209+ checkNotRoot ( providerPath , 'rmdir' , dirPath ) ;
11711210 return provider . rmdir ( providerPath ) ;
11721211 } ,
11731212
@@ -1179,6 +1218,8 @@ class VirtualFileSystem {
11791218 async rename ( oldPath , newPath ) {
11801219 const oldProviderPath = toProviderPath ( oldPath ) ;
11811220 const newProviderPath = toProviderPath ( newPath ) ;
1221+ checkNotRoot ( oldProviderPath , 'rename' , oldPath ) ;
1222+ checkNotRoot ( newProviderPath , 'rename' , newPath ) ;
11821223 return provider . rename ( oldProviderPath , newProviderPath ) ;
11831224 } ,
11841225
@@ -1234,7 +1275,7 @@ class VirtualFileSystem {
12341275 for ( let i = 0 ; i < entries . length ; i ++ ) {
12351276 await this . rm ( joinPath ( filePath , entries [ i ] ) , options ) ;
12361277 }
1237- await provider . rmdir ( toProviderPath ( filePath ) ) ;
1278+ await this . rmdir ( filePath ) ;
12381279 } else {
12391280 await provider . unlink ( toProviderPath ( filePath ) ) ;
12401281 }
@@ -1309,5 +1350,6 @@ class VirtualFileSystem {
13091350module . exports = {
13101351 VirtualFileSystem,
13111352 kLayerId,
1353+ kReservedRoot,
13121354 normalizeMountedPath,
13131355} ;
0 commit comments