Handle STATUS_OBJECT_NAME_NOT_FOUND in Windows directory enumeration#130196
Open
jeffhandley wants to merge 1 commit into
Open
Handle STATUS_OBJECT_NAME_NOT_FOUND in Windows directory enumeration#130196jeffhandley wants to merge 1 commit into
jeffhandley wants to merge 1 commit into
Conversation
Add STATUS_OBJECT_NAME_NOT_FOUND (0xC0000034) to the no-matches branch of FileSystemEnumerator<T>.GetData() on Windows, alongside STATUS_FILE_NOT_FOUND. A filtered NtQueryDirectoryFile that matches zero entries can return this status instead of STATUS_NO_SUCH_FILE on some file systems (for example, certain read-only or virtual file systems); it is now treated as an empty result instead of throwing FileNotFoundException. Fixes dotnet#130134 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
|
Tagging subscribers to this area: @dotnet/area-system-io |
Contributor
There was a problem hiding this comment.
Pull request overview
This PR updates Windows directory enumeration in System.Private.CoreLib to treat STATUS_OBJECT_NAME_NOT_FOUND from NtQueryDirectoryFile as a “no matches / end of enumeration” condition (like existing STATUS_FILE_NOT_FOUND handling), avoiding an incorrect FileNotFoundException when an OS-level filter matches zero entries on certain filesystems.
Changes:
- Handle
Interop.StatusOptions.STATUS_OBJECT_NAME_NOT_FOUNDinFileSystemEnumerator<TResult>.GetData()as “no results” and finish the directory.
JeremyKuhne
approved these changes
Jul 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #130134
Summary
Directory.GetFiles/Directory.EnumerateFiles(and theDirectoryInfo/Directory.GetFileSystemEntriesvariants) with a search pattern that matches zero files throwsSystem.IO.FileNotFoundException-- naming the directory itself -- on some read-only / virtual file systems, instead of returning an empty result. This was reported on an Azure App Service Windows app deployed withWEBSITE_RUN_FROM_PACKAGE, where the content root is a read-only mounted zip. It is a regression in 10.0.9, introduced by #127974 (backport of #122947 / #123695).Root cause
#127974 began passing the search pattern to
NtQueryDirectoryFileas an OS-levelFileNamefilter. When zero entries match,FileSystemEnumerator<T>.GetData()'sswitchonly handlesSTATUS_NO_SUCH_FILE(0xC000000F). Some file system drivers report the same "no matching entries" condition asSTATUS_OBJECT_NAME_NOT_FOUND(0xC0000034), which fell through to thedefaultcase →ERROR_FILE_NOT_FOUND→throw.Evidence that the culprit is
STATUS_OBJECT_NAME_NOT_FOUNDThe
defaultarm ofGetData()turns the rawNTSTATUSinto the thrown exception in two hops:Hop 2 —
Win32Marshal.GetExceptionForWin32Error(src/libraries/Common/src/System/IO/Win32Marshal.cs) selects the exception type and message purely from the Win32 error code:ERROR_FILE_NOT_FOUND(2)FileNotFoundExceptionIO_FileNotFound_FileName="Could not find file '{0}'."ERROR_PATH_NOT_FOUND(3)DirectoryNotFoundExceptionIO_PathNotFound_Path="Could not find a part of the path '{0}'."The report is
System.IO.FileNotFoundException: Could not find file '<dir>'— exactly theERROR_FILE_NOT_FOUND(2) branch. So the rawNTSTATUSmust be one that hop 1 (RtlNtStatusToDosError, an ntdll OS function — the mapping is defined by Windows, not .NET) translates to2, and that is not already handled by theswitch.RtlNtStatusToDosErrortranslations for the relevant statuses (values verified by calling it directly):RtlNtStatusToDosErrorSTATUS_SUCCESS0x00000000ERROR_SUCCESS(0)STATUS_NO_MORE_FILES0x80000006ERROR_NO_MORE_FILES(18)STATUS_NO_SUCH_FILE(akaSTATUS_FILE_NOT_FOUND)0xC000000FERROR_FILE_NOT_FOUND(2)STATUS_OBJECT_NAME_INVALID0xC0000033ERROR_INVALID_NAME(123)STATUS_OBJECT_NAME_NOT_FOUND0xC0000034ERROR_FILE_NOT_FOUND(2)STATUS_OBJECT_PATH_NOT_FOUND0xC000003AERROR_PATH_NOT_FOUND(3)Only two
NTSTATUSvalues map toERROR_FILE_NOT_FOUND(2):STATUS_NO_SUCH_FILE(already handled, so it can't produce the throw) andSTATUS_OBJECT_NAME_NOT_FOUND. Therefore0xC0000034is the only possible culprit.STATUS_OBJECT_PATH_NOT_FOUND(0xC000003A) is ruled out: it maps toERROR_PATH_NOT_FOUND(3), which hop 2 turns into aDirectoryNotFoundExceptionwith"Could not find a part of the path '{0}'."— a different exception type and message than the report.Empirically, a direct
NtQueryDirectoryFileP/Invoke with a zero-match filter returns the handledSTATUS_NO_SUCH_FILE(0xC000000F) on NTFS, ReFS, UDF, and CDFS — so the built-in drivers don't hit this, and it takes a driver that returns0xC0000034(as reported for the read-only zip mount) to trigger it. The affected file systems do honor the filter -- a matching pattern returns results while only the zero-match case reports0xC0000034-- so the OS-level filtering added by #127974 is working; the runtime simply needs to treat this status as "no matches."Change
Add
case Interop.StatusOptions.STATUS_OBJECT_NAME_NOT_FOUNDto the existing no-matches branch inGetData()(Windows), alongsideSTATUS_FILE_NOT_FOUND. The directory handle was already opened successfully inInit(), so this status can only mean the filter matched nothing -- consistent with how the siblingSTATUS_FILE_NOT_FOUND/STATUS_OBJECT_NAME_INVALIDstatuses are already handled. TheSTATUS_OBJECT_NAME_NOT_FOUNDconstant already exists inInterop.NtStatus.cs.Testing
Reproducing this requires a file system driver that returns
0xC0000034for a zero-match filteredNtQueryDirectoryFile; the built-in drivers (NTFS, ReFS, UDF, CDFS) all return the already-handled0xC000000F, so there is no in-repo regression test. The fix was validated out-of-band by hookingntdll!NtQueryDirectoryFilein-process to rewrite the zero-match status0xC000000F->0xC0000034for a single directory, faithfully mirroring the reported driver behavior:Directory.GetFiles(dir, "__no_such_file__*.dll")probe throws on the shipped 10.0.9 runtime and on an unmodified build of this branch, and returns an empty array with this fix, while a matching pattern (*.dll) continues to return results in all cases.dotnet-installed 10.0.7 / 10.0.8 (return empty; pre-regression), 10.0.9 (throws), and this branch (throws without the fix, empty with it) on both x64 and Arm64.System.IO.FileSystem.Testsshows no regressions from this change.Alternatives considered
Two alternative approaches were prototyped and rejected (prototype PRs on my fork: error-mapping, unfiltered-fallback):
ERROR_FILE_NOT_FOUNDin thedefaultarm as no-matches -- broader than necessary; keys off the translated Win32 error rather than the specific status, so it could absorb a file-not-found produced for an unrelated reason.0xC0000034-- because the affected drivers honor the filter, this would discard the driver's result and re-enumerate every entry into managed code for the zero-match case, reintroducing the O(all-entries) scan that [release/10.0] Optimize Directory.GetFiles by passing safe patterns to NtQueryDirectoryFile #127974 set out to eliminate, precisely in the failing scenario (a selective pattern matching nothing in a large directory).Note
This pull request was authored with assistance from GitHub Copilot.