Skip to content

Alt: Fall back to unfiltered enumeration on STATUS_OBJECT_NAME_NOT_FOUND (#130134)#10

Closed
jeffhandley wants to merge 1 commit into
jeffhandley/directory-getfilesfrom
jeffhandley/directory-getfiles-unfiltered-fallback
Closed

Alt: Fall back to unfiltered enumeration on STATUS_OBJECT_NAME_NOT_FOUND (#130134)#10
jeffhandley wants to merge 1 commit into
jeffhandley/directory-getfilesfrom
jeffhandley/directory-getfiles-unfiltered-fallback

Conversation

@jeffhandley

Copy link
Copy Markdown
Owner

Summary

Alternative approach for dotnet/runtime#130134 (zero-match Directory.GetFiles throws FileNotFoundException on the Azure App Service run-from-package zip mount; regression from dotnet/runtime#127974).

See the primary/recommended approach in the peer PR from jeffhandley/directory-getfiles-status-object.

Root cause

dotnet/runtime#127974 passes the search pattern to NtQueryDirectoryFile as an OS-level filter. A filtered query that matches zero entries returns STATUS_NO_SUCH_FILE (0xC000000F) on NTFS but STATUS_OBJECT_NAME_NOT_FOUND (0xC0000034) on the zip mount; only the former is handled, so the latter throws.

Change (this branch)

In FileSystemEnumerator<T>.GetData() (Windows), when the first filtered NtQueryDirectoryFile call returns STATUS_OBJECT_NAME_NOT_FOUND, re-issue the query with no FileName filter and RestartScan, reverting to the pre-#127974 behavior for that directory (enumerate everything, then filter in managed code — which always runs anyway). The empty result is then produced without throwing.

Trade-off vs. the recommended approach: behavior-preserving and also guards drivers that reject otherwise-valid filters, but adds an extra syscall on the affected path and is more code.

Validation

Validated with the same faithful NtQueryDirectoryFile repro harness: on the .NET 11 branch, the zero-match probe returns empty with count = 0 (was throwing) while a matching pattern still returns count = 1.

Note

This pull request (including its description) was created by GitHub Copilot.

…Windows)

In FileSystemEnumerator<T>.GetData() on Windows, when the first filtered
NtQueryDirectoryFile call returns STATUS_OBJECT_NAME_NOT_FOUND, re-issue the query
with no FileName filter and RestartScan, reverting to enumerate-all-then-filter for
that directory. Some file system drivers (notably the Azure App Service
run-from-package zip mount) report a zero-match filtered query with this status; the
unfiltered retry lets the managed matching produce the empty result instead of
throwing FileNotFoundException.

Alternative approach for dotnet#130134.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jeffhandley

Copy link
Copy Markdown
Owner Author

Closing in favor of #8 (handle STATUS_OBJECT_NAME_NOT_FOUND) after determining that the affected share does honor the OS-level filter.

Does the package-backed share do the filtering?

Yes. Two independent lines of evidence:

Logical (from the issue's two probes): the behavior is asymmetric -- a matching pattern (*.dll) returns results, while a zero-match pattern (__no_such_file__*.dll) throws. That is only possible if the driver evaluates the filter. A driver that ignored the filter would return the full listing with STATUS_SUCCESS for both, so the zero-match case would come back empty and never throw.

Empirical (harness, shipped .NET 10.0.9): I simulated both driver models at the NtQueryDirectoryFile boundary and ran the unmodified 10.0.9 runtime against each:

Driver model zero-match __no_such_file__*.dll matching *.dll
Honors filter, reports zero-match as STATUS_OBJECT_NAME_NOT_FOUND (0xC0000034) THREW FileNotFoundException: Could not find file '<dir>' -- matches the report returned, count = 1
Ignores filter (managed filtering only) returned, count = 0 (empty, no throw) -- does not match the report returned, count = 1

Only the filter-honoring model reproduces the reported exception. Since the real run-from-package mount does throw, it must be honoring the filter.

Why that rules out this approach

Because the driver already evaluates the filter and reports "no matches," dotnet#127974's optimization is genuinely working on this share. This approach would discard that result and re-query with no filter, forcing the driver to return every entry to managed code to be filtered there -- exactly the O(all-entries) scan dotnet#127974 set out to eliminate, and precisely in the failing scenario (a selective pattern that matches nothing in a large directory, e.g. Ninject.*.dll in a big wwwroot).

#8 instead returns empty immediately on 0xC0000034 (1 syscall, no managed enumeration), fixing the bug while preserving the optimization. It is also safe regardless of filtering quality, since .NET's managed matching always runs as the authoritative filter on top.

Note

This comment was generated by GitHub Copilot.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant