@@ -35,6 +35,7 @@ const { assertEncoding, setVfsHandlers } = require('internal/fs/utils');
3535const permission = require ( 'internal/process/permission' ) ;
3636const { getOptionValue } = require ( 'internal/options' ) ;
3737const nativeModulesBinding = internalBinding ( 'modules' ) ;
38+ const { UV_ENOENT } = internalBinding ( 'uv' ) ;
3839let debug = require ( 'internal/util/debuglog' ) . debuglog ( 'vfs' , ( fn ) => {
3940 debug = fn ;
4041} ) ;
@@ -115,29 +116,50 @@ function deregisterVFS(vfs) {
115116}
116117
117118/**
118- * Resolves a path string to the active VFS that owns it , or null.
119- * Ownership is decidable from the path alone: all mount points live
120- * under the reserved `${os.devNull}/vfs/<id>` namespace, so a single
121- * prefix comparison rejects every real-file-system path and a map
122- * lookup finds the owning layer. The normalized path is returned
119+ * Resolves a path string to the reserved VFS root , or null for a path
120+ * outside it. Ownership is decidable from the path alone: all mount
121+ * points live under the reserved `${os.devNull}/vfs/<id>` namespace, so
122+ * a single prefix comparison rejects every real-file-system path and a
123+ * map lookup finds the owning layer. The normalized path is returned
123124 * alongside the layer so downstream helpers can skip renormalization.
125+ *
126+ * A path under the root that no active layer owns comes back with
127+ * `vfs: null` rather than as `null`, because the two cases must not be
128+ * treated alike by the module loader. The loader manufactures such
129+ * paths itself: resolving a mount point as a directory first probes the
130+ * sibling names `<mount>.js`, `<mount>.json`, ..., and a package.json
131+ * walk-up passes the parents of the mount point. They cannot name
132+ * anything real, but on Windows the root sits under `\\.\nul`, and
133+ * `\\.\nul\<anything>` opens the NUL device, which stats as a character
134+ * device and reads as empty. Handed to the native loader, such a probe
135+ * "finds" a file and the walk-up above it rejects the empty device as an
136+ * invalid package.json, so the loader must answer for the whole root.
124137 * @param {string } inputPath
125- * @returns {{ vfs: object, normalized: string }|null }
138+ * @returns {{ vfs: object|null , normalized: string }|null }
126139 */
127- function findVFS ( inputPath ) {
140+ function findVFSOrRoot ( inputPath ) {
128141 const normalized = normalizeMountedPath ( inputPath ) ;
129142 if ( ! StringPrototypeStartsWith ( normalized , normalizedVfsRootPrefix ) ) {
130143 return null ;
131144 }
132145 const layerId = getLayerIdFromPath ( normalized ) ;
133- if ( layerId === - 1 ) return null ;
134- const vfs = activeVFSLayers . get ( layerId ) ;
146+ const vfs = layerId === - 1 ? undefined : activeVFSLayers . get ( layerId ) ;
135147 if ( vfs === undefined || ! vfs . shouldHandleNormalized ( normalized ) ) {
136- return null ;
148+ return { vfs : null , normalized } ;
137149 }
138150 return { vfs, normalized } ;
139151}
140152
153+ /**
154+ * Resolves a path string to the active VFS that owns it, or null.
155+ * @param {string } inputPath
156+ * @returns {{ vfs: object, normalized: string }|null }
157+ */
158+ function findVFS ( inputPath ) {
159+ const r = findVFSOrRoot ( inputPath ) ;
160+ return r === null || r . vfs === null ? null : r ;
161+ }
162+
141163/**
142164 * Drop the cache entries under `vfs`'s mount point from the
143165 * JS-reachable loader caches. Real-fs entries and other-VFS entries
@@ -210,16 +232,16 @@ function findVFSForStat(filename) {
210232}
211233
212234/**
213- * Finds the VFS owning `filename` and reads it.
235+ * Reads `filename` from the VFS that owns it, reporting a missing file
236+ * or a directory the way the native loader's read does.
237+ * @param {object } vfs The VFS owning filename
214238 * @param {string } filename The absolute path to read
215239 * @param {string|object } options Read options
216- * @returns {{ vfs: object, content: Buffer|string }|null }
240+ * @returns {Buffer|string }
217241 */
218- function findVFSForRead ( filename , options ) {
219- const r = findVFS ( filename ) ;
220- if ( r === null ) return null ;
242+ function readVFS ( vfs , filename , options ) {
221243 try {
222- return { vfs : r . vfs , content : r . vfs . readFileSync ( filename , options ) } ;
244+ return vfs . readFileSync ( filename , options ) ;
223245 } catch ( e ) {
224246 const code = e ?. code ;
225247 if ( code === 'ENOENT' || code === 'EISDIR' ) {
@@ -794,30 +816,40 @@ function installModuleLoaderOverrides() {
794816 // wrapLoaderMethod then falls through to the native binding.
795817 setLoaderOverrides ( {
796818 internalModuleStat ( filename ) {
797- const result = findVFSForStat ( filename ) ;
798- return result !== null ? result . result : undefined ;
819+ const r = findVFSOrRoot ( filename ) ;
820+ if ( r === null ) return undefined ;
821+ return r . vfs === null ? UV_ENOENT : vfsStat ( r . vfs , filename ) ;
799822 } ,
800823 readFileSync ( filename , options ) {
801824 const pathStr = typeof filename === 'string' ? filename :
802825 ( filename instanceof URL ? fileURLToPath ( filename ) : String ( filename ) ) ;
803- const result = findVFSForRead ( pathStr , options ) ;
804- return result !== null ? result . content : undefined ;
826+ const r = findVFSOrRoot ( pathStr ) ;
827+ if ( r === null ) return undefined ;
828+ if ( r . vfs === null ) throw createENOENT ( 'open' , pathStr ) ;
829+ return readVFS ( r . vfs , pathStr , options ) ;
805830 } ,
806831 realpathSync ( filename ) {
807- return findVFSWith ( filename , 'realpath' , ( vfs , n ) => vfs . realpathSync ( n ) ) ;
832+ const r = findVFSOrRoot ( filename ) ;
833+ if ( r === null ) return undefined ;
834+ if ( r . vfs === null || ! r . vfs . existsSync ( filename ) ) {
835+ throw createENOENT ( 'realpath' , filename ) ;
836+ }
837+ return r . vfs . realpathSync ( filename ) ;
808838 } ,
809839 getResolutionRoot ( pathStr ) {
810- const r = findVFS ( pathStr ) ;
840+ const r = findVFSOrRoot ( pathStr ) ;
811841 if ( r === null ) return undefined ;
812- const mountPoint = r . vfs . mountPoint ;
813842 // The boundary is compared as a plain string prefix by the
814- // callers, so only report it when the input carries the mount
815- // point verbatim.
816- return StringPrototypeStartsWith ( pathStr , mountPoint ) ?
817- mountPoint : undefined ;
843+ // callers, so only report it when the input carries it verbatim.
844+ // An unowned path stops at the reserved root itself, so no
845+ // node_modules lookup walks out into the real file system.
846+ const boundary = r . vfs === null ?
847+ getNormalizedVfsRoot ( ) : r . vfs . mountPoint ;
848+ return StringPrototypeStartsWith ( pathStr , boundary ) ?
849+ boundary : undefined ;
818850 } ,
819851 legacyMainResolve ( pkgPath , main , base ) {
820- if ( findVFS ( pkgPath ) === null ) return undefined ;
852+ if ( findVFSOrRoot ( pkgPath ) === null ) return undefined ;
821853
822854 for ( let i = 0 ; i < legacyMainResolveExtensions . length ; i ++ ) {
823855 const byMain = i <= kResolvedByMainIndexNode ;
@@ -835,14 +867,14 @@ function installModuleLoaderOverrides() {
835867 throw new ERR_MODULE_NOT_FOUND ( initial , base , undefined ) ;
836868 } ,
837869 getFormatOfExtensionlessFile ( filePath ) {
838- let result ;
870+ const r = findVFSOrRoot ( filePath ) ;
871+ if ( r === null ) return undefined ;
872+ let content ;
839873 try {
840- result = findVFSForRead ( filePath , null ) ;
874+ content = r . vfs === null ? null : readVFS ( r . vfs , filePath , null ) ;
841875 } catch {
842876 return internalConstants . EXTENSIONLESS_FORMAT_JAVASCRIPT ;
843877 }
844- if ( result === null ) return undefined ;
845- const content = result . content ;
846878 // Wasm magic bytes: 0x00 0x61 0x73 0x6d
847879 if ( content && content . length >= 4 &&
848880 content [ 0 ] === 0x00 && content [ 1 ] === 0x61 &&
@@ -852,8 +884,9 @@ function installModuleLoaderOverrides() {
852884 return internalConstants . EXTENSIONLESS_FORMAT_JAVASCRIPT ;
853885 } ,
854886 readPackageJSON ( jsonPath , isESM , base , specifier ) {
855- const r = findVFS ( jsonPath ) ;
887+ const r = findVFSOrRoot ( jsonPath ) ;
856888 if ( r === null ) return undefined ;
889+ if ( r . vfs === null ) return kLoaderOverrideNoResult ;
857890 const { vfs } = r ;
858891 if ( vfsStat ( vfs , jsonPath ) !== 0 ) return kLoaderOverrideNoResult ;
859892 let content ;
@@ -868,8 +901,9 @@ function installModuleLoaderOverrides() {
868901 content , jsonPath , isESM , base , specifier ) ;
869902 } ,
870903 getNearestParentPackageJSON ( checkPath ) {
871- const r = findVFS ( checkPath ) ;
904+ const r = findVFSOrRoot ( checkPath ) ;
872905 if ( r === null ) return undefined ;
906+ if ( r . vfs === null ) return kLoaderOverrideNoResult ;
873907 const found = findVFSPackageJSON ( r . vfs , checkPath , r . normalized ) ;
874908 return found . tuple ?? kLoaderOverrideNoResult ;
875909 } ,
@@ -884,8 +918,11 @@ function installModuleLoaderOverrides() {
884918 } else {
885919 filePath = resolved ;
886920 }
887- const r = findVFS ( filePath ) ;
921+ const r = findVFSOrRoot ( filePath ) ;
888922 if ( r === null ) return undefined ;
923+ // The "not found" marker is the package.json beside the queried
924+ // path, which is what the native binding reports for it.
925+ if ( r . vfs === null ) return join ( dirname ( filePath ) , 'package.json' ) ;
889926 const found = findVFSPackageJSON ( r . vfs , filePath , r . normalized ) ;
890927 if ( found . tuple !== undefined ) return found . tuple ;
891928 return found . sentinel ;
@@ -901,8 +938,9 @@ function installModuleLoaderOverrides() {
901938 } else {
902939 filePath = url ;
903940 }
904- const r = findVFS ( filePath ) ;
941+ const r = findVFSOrRoot ( filePath ) ;
905942 if ( r === null ) return undefined ;
943+ if ( r . vfs === null ) return kLoaderOverrideNoResult ;
906944 const found = findVFSPackageJSON ( r . vfs , filePath , r . normalized ) ;
907945 if ( found . tuple !== undefined ) {
908946 // Tuple shape: [name, main, type, imports, exports, filePath].
0 commit comments