Skip to content

kernel32: full DOS-wildcard semantics for FindFirstFile#142

Open
Luminger wants to merge 1 commit into
decompals:mainfrom
Luminger:pr-dos-wildcards
Open

kernel32: full DOS-wildcard semantics for FindFirstFile#142
Luminger wants to merge 1 commit into
decompals:mainfrom
Luminger:pr-dos-wildcards

Conversation

@Luminger

@Luminger Luminger commented Jul 17, 2026

Copy link
Copy Markdown

Replaces the ad-hoc glob matcher in the FindFirstFile path with an FsRtl-exact implementation of the NT kernel's FsRtlIsNameInExpression DOS-wildcard matching.

Why

Windows does not match filenames with a plain */? globber: FindFirstFile rewrites *, ? and . into the DOS tokens DOS_STAR (<), DOS_QM (>) and DOS_DOT (") and matches with an NFA. That is why *.* matches extensionless names, *. matches only extensionless names, a trailing ? matches zero-or-one character, and a trailing . is absorbed.

Real-world impact: Watcom's wmake locates its makefile by enumerating the directory with FindFirstFileA("*.*"). wibo's dot-requiring globber silently dropped extensionless names, so makefile never appeared and wmake failed with "No targets specified" — while the Watcom compiler/linker (which open files by name) worked fine, making this miserable to track down.

What

  • translateWin32Expression() + matchWin32Expression(): based on .NET's System.IO.Enumeration.FileSystemName (MIT; Microsoft's own reimplementation of the native FsRtl matcher), with the .NET-isms removed so the matcher is FsRtl-exact: no \ escape (FsRtl treats \ as a literal) and FsRtl empty-string semantics.
  • containsWildcard() widened from *? to the full FsRtlIsUnicodeCharacterWild set *?<>", so DOS-token-only patterns route to enumeration instead of a direct lookup, as on Windows.

Validation

  • ReactOS kmtest FsRtlExpression table (151 expression/name pairs with results recorded from real Windows kernels): 151/151 pass
  • exhaustive differential test against an independent transliteration of ReactOS's FsRtlIsNameInExpressionPrivate: 0 divergences over 121M generated cases (all patterns ≤ 5 over {a,b,.,*,?,<,>,",\} × all names ≤ 6 over {a,b,.})
  • test_findfile gains assertions for the strict DOS quirks: *. extensionless-only, trailing DOS_QM zero-or-one, DOS_QM never consuming a dot (file???? vs file????.txt), raw < routing + the final-dot rule
  • full test suite green (48/48)

Note: wine's own matcher does not implement the stricter DOS quirks (*. extensionless-only, DOS_QM zero-match) — wibo becomes more Windows-faithful than wine here.

Related: #127 adds its own plain */? globber (wildcardMatchCI in dll/msvc_compat_ntdll.cpp, explicitly "DOS wildcards not handled") for its NtQueryDirectoryFile path — no conflict with this PR (different file), but once this lands that enumeration could reuse matchWin32Expression() and get the DOS semantics for free.

Replace the ad-hoc glob matcher with an FsRtl-exact implementation of
the NT kernel's FsRtlIsNameInExpression DOS-wildcard matching.

Windows does not match filenames with a plain '*'/'?' globber: the
FindFirstFile path rewrites '*', '?' and '.' into the special DOS tokens
DOS_STAR ('<'), DOS_QM ('>') and DOS_DOT ('"') and matches with an NFA.
That is why "*.*" matches extensionless names, "*." matches *only*
extensionless names, a trailing '?' matches zero-or-one char, and a
trailing '.' is absorbed.

Real-world impact: Watcom wmake locates its makefile by enumerating the
directory with FindFirstFileA("*.*") and matching the result names.
With a dot-requiring globber, "makefile" never appeared in the listing,
so wmake failed with "No targets specified" even when a makefile was
present -- while tools that open files by name directly (the Watcom
compiler/linker) worked fine.

translateWin32Expression() + matchWin32Expression() are based on .NET's
System.IO.Enumeration.FileSystemName (MIT: TranslateWin32Expression +
MatchPattern), Microsoft's own reimplementation of the native
FsRtlIsNameInExpression, with two deliberate deviations that make the
matcher FsRtl-exact rather than .NET-exact:

  * no '\' escape character (a .NET extension for FileSystemWatcher
    filters; FsRtl treats '\' as a literal character);
  * FsRtl empty-string semantics (both empty => match; one empty =>
    no match).

containsWildcard() is widened from "*?" to the full
FsRtlIsUnicodeCharacterWild set "*?<>\"" so that patterns containing
only DOS tokens are routed to enumeration/matching instead of a direct
file lookup, as on Windows (all five are invalid in on-disk Windows
names).

Validation:
  * the ReactOS kmtest FsRtlExpression table (151 expression/name pairs
    with results recorded from real Windows kernels): 151/151 pass;
  * exhaustive differential test against an independent char-unit
    transliteration of ReactOS's FsRtlIsNameInExpressionPrivate
    (ntoskrnl/fsrtl/name.c): 0 divergences over 121M generated cases
    (all patterns up to length 5 over {a,b,.,*,?,<,>,",\} x all names
    up to length 6 over {a,b,.});
  * test_findfile gains assertions for the strict DOS quirks: "*."
    matches only extensionless names, trailing DOS_QM zero-or-one,
    DOS_QM never consuming a dot ("file????" vs "file????.txt"), and
    raw '<' routing + final-dot rule ("make<" matches "makefile",
    "fi<" does not match "file.txt").

Note: wine's own matcher does not implement the stricter DOS quirks
("*." extensionless-only, DOS_QM zero-match); wibo is intentionally
more Windows-faithful than wine here.
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