Welcome to Princeland depends on that.
Added tests show that NtQueryDirectoryFile mask handling is different from FindFirstFile (even though it doesn't quite match Windows too, the details are different, e. g., on Windows mask "*." will select file "ea" with FindFirstFile but not with NtQueryDirectoryFile). So the correct way is probably do the wildcards matching in kernelbase directly without relying on NtQueryDirectoryFile.
Having files not apprearing in the output of FindFirstFile is not obvious on Windows as well as the mask matches generated short names and that might provide an unexpected output. However, the part with not returning the files with extension when mask requires so looks rather straightforward and depending on that is not probably supposed to semi-randomly break on Windows. While checking the tests before after the changes I was checking that besides not introducing new TODOs no test files are appearing in the missing part in todo tests (as starting to incorrectly missing some files in the output has an obvious regression potential).
From: Paul Gofman pgofman@codeweavers.com
--- dlls/ntdll/tests/path.c | 4 ++++ 1 file changed, 4 insertions(+)
diff --git a/dlls/ntdll/tests/path.c b/dlls/ntdll/tests/path.c index 9210cfb8843..4771a3f588b 100644 --- a/dlls/ntdll/tests/path.c +++ b/dlls/ntdll/tests/path.c @@ -306,6 +306,9 @@ static void test_RtlGetFullPathName_U(void) { "c:/test../file", "c:\test.\file", "file", "c:\test..\file", "file"}, /* vista */ { "c:\test", "c:\test", "test"}, + { "c:\test\*.", "c:\test\*", "*"}, + { "c:\test\a*b.*", "c:\test\a*b.*", "a*b.*"}, + { "c:\test\a*b*.", "c:\test\a*b*", "a*b*"}, { "C:\test", "C:\test", "test"}, { "c:/", "c:\", NULL}, { "c:.", "C:\windows", "windows"}, @@ -441,6 +444,7 @@ static void test_RtlDosPathNameToNtPathName_U(void) tests[] = { {L"c:\", L"\??\c:\", -1}, + {L"c:\test\*.", L"\??\c:\test\*", 12}, {L"c:/", L"\??\c:\", -1}, {L"c:/foo", L"\??\c:\foo", 7}, {L"c:/foo.", L"\??\c:\foo", 7},
From: Paul Gofman pgofman@codeweavers.com
--- dlls/ntdll/tests/directory.c | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-)
diff --git a/dlls/ntdll/tests/directory.c b/dlls/ntdll/tests/directory.c index 3b9cd693c3e..7210c1bbaa3 100644 --- a/dlls/ntdll/tests/directory.c +++ b/dlls/ntdll/tests/directory.c @@ -70,6 +70,7 @@ static struct testfile_s { { 0, FILE_ATTRIBUTE_NORMAL, {0xe9,'a','.','t','m','p'}, "normal" }, { 0, FILE_ATTRIBUTE_NORMAL, {0xc9,'b','.','t','m','p'}, "normal" }, { 0, FILE_ATTRIBUTE_NORMAL, {'e','a','.','t','m','p'}, "normal" }, + { 0, FILE_ATTRIBUTE_NORMAL, {'e','a'}, "normal" }, { 0, FILE_ATTRIBUTE_DIRECTORY, {'.'}, ". directory" }, { 0, FILE_ATTRIBUTE_DIRECTORY, {'.','.'}, ".. directory" } }; @@ -237,13 +238,13 @@ static void test_flags_NtQueryDirectoryFile(OBJECT_ATTRIBUTES *attr, const char } ok(numfiles < max_test_dir_size, "too many loops\n");
- if (mask) + if (mask && !wcspbrk( mask->Buffer, L"*?" )) for (i = 0; i < test_dir_count; i++) ok(testfiles[i].nfound == (testfiles[i].name == mask->Buffer), "Wrong number %d of %s files found (single_entry=%d,mask=%s)\n", testfiles[i].nfound, testfiles[i].description, single_entry, wine_dbgstr_wn(mask->Buffer, mask->Length/sizeof(WCHAR) )); - else + else if (!mask) for (i = 0; i < test_dir_count; i++) ok(testfiles[i].nfound == 1, "Wrong number %d of %s files found (single_entry=%d,restart=%d)\n", testfiles[i].nfound, testfiles[i].description, single_entry, restart_flag); @@ -448,11 +449,27 @@ static void test_NtQueryDirectoryFile_classes( HANDLE handle, UNICODE_STRING *ma
static void test_NtQueryDirectoryFile(void) { + static const struct + { + const WCHAR *mask; + int found[ARRAY_SIZE(testfiles)]; + BOOL todo_missing; + } + mask_tests[] = + { + {L"*.", {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1}, TRUE}, + {L"*.*", {1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1}, TRUE}, + {L"*.**", {1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1}, TRUE}, + {L"*", {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}}, + {L"**", {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}}, + {L"??.???", {0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0}}, + }; + OBJECT_ATTRIBUTES attr; UNICODE_STRING ntdirname, mask; char testdirA[MAX_PATH], buffer[MAX_PATH]; WCHAR testdirW[MAX_PATH]; - int i; + int i, j; IO_STATUS_BLOCK io; WCHAR short_name[12]; UINT data_size; @@ -494,6 +511,19 @@ static void test_NtQueryDirectoryFile(void) test_flags_NtQueryDirectoryFile(&attr, testdirA, &mask, TRUE, FALSE); }
+ for (i = 0; i < ARRAY_SIZE(mask_tests); ++i) + { + winetest_push_context("mask %s", debugstr_w(mask_tests[i].mask)); + RtlInitUnicodeString(&mask, mask_tests[i].mask); + test_flags_NtQueryDirectoryFile(&attr, testdirA, &mask, FALSE, TRUE); + for (j = 0; j < test_dir_count; j++) + { + todo_wine_if(mask_tests[i].todo_missing && !mask_tests[i].found[j]) + ok(testfiles[j].nfound == mask_tests[i].found[j], "%S, got %d.\n", testfiles[j].name, testfiles[j].nfound); + } + winetest_pop_context(); + } + /* short path passed as mask */ status = pNtOpenFile(&dirh, SYNCHRONIZE | FILE_LIST_DIRECTORY, &attr, &io, FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT | FILE_DIRECTORY_FILE);
From: Paul Gofman pgofman@codeweavers.com
--- dlls/kernel32/tests/file.c | 39 +++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-)
diff --git a/dlls/kernel32/tests/file.c b/dlls/kernel32/tests/file.c index 22137dbc79a..0918651bc1c 100644 --- a/dlls/kernel32/tests/file.c +++ b/dlls/kernel32/tests/file.c @@ -2963,38 +2963,43 @@ static void test_FindFirstFile_wildcards(void) int i; static const char* files[] = { "..a", "..a.a", ".a", ".a..a", ".a.a", ".aaa", - "a", "a..a", "a.a", "a.a.a", "aa", "aaa", "aaaa" + "a", "a..a", "a.a", "a.a.a", "aa", "aaa", "aaaa", " .a" }; static const struct { int todo; const char *pattern, *result; } tests[] = { - {0, "*.*.*", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa'"}, - {0, "*.*.", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa'"}, + {0, "*.*.*", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa', ' .a'"}, + {0, "*.*.", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa', ' .a'"}, {0, ".*.*", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa'"}, - {0, "*.*", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa'"}, + {0, "*.*", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa', ' .a'"}, {0, ".*", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa'"}, {1, "*.", ", '.', '..', 'a', '.a', '..a', 'aa', 'aaa', 'aaaa', '.aaa'"}, - {0, "*", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa'"}, + {0, "*", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa', ' .a'"}, {1, "*..*", ", '.', '..', '..a', '..a.a', '.a..a', 'a..a'"}, {1, "*..", ", '.', '..', 'a', '.a', '..a', 'aa', 'aaa', 'aaaa', '.aaa'"}, {1, ".*.", ", '.', '..', '.a', '.aaa'"}, {0, "..*", ", '.', '..', '..a', '..a.a'"}, - {0, "**", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa'"}, - {0, "**.", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa'"}, - {0, "*. ", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa'"}, + {0, "**", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa', ' .a'"}, + {0, "**.", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa', ' .a'"}, + {0, "*. ", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa', ' .a'"}, {1, "* .", ", '.', '..', 'a', '.a', '..a', 'aa', 'aaa', 'aaaa', '.aaa'"}, - {0, "* . ", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa'"}, - {0, "*.. ", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa'"}, + {0, "* . ", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa', ' .a'"}, + {0, "*.. ", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa', ' .a'"}, {1, "*. .", ", '.', '..', 'a', '.a', '..a', 'aa', 'aaa', 'aaaa', '.aaa'"}, {1, "* ..", ", '.', '..', 'a', '.a', '..a', 'aa', 'aaa', 'aaaa', '.aaa'"}, - {0, " *..", ""}, + {1, " *..", ""}, {0, "..* ", ", '.', '..', '..a', '..a.a'"}, + {1, "a*.", ", '..a', '.a', '.aaa', 'a', 'aa', 'aaa', 'aaaa'"}, + {0, "*a ", ", '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa', ' .a'"}, + + /* a.a.a not found due to short name mismatch, a.a.a -> "AA6BF5~1.A on Windows. */ + {1, "*aa*", ", '.aaa', 'a.a.a', 'aa', 'aaa', 'aaaa'"},
{1, "<.<.<", ", '..a', '..a.a', '.a..a', '.a.a', 'a..a', 'a.a.a'"}, - {1, "<.<.", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a..a', 'a.a', 'a.a.a'"}, + {1, "<.<.", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a..a', 'a.a', 'a.a.a', ' .a'"}, {1, ".<.<", ", '..a', '..a.a', '.a..a', '.a.a'"}, - {1, "<.<", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a..a', 'a.a', 'a.a.a'"}, + {1, "<.<", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a..a', 'a.a', 'a.a.a', ' .a'"}, {1, ".<", ", '.', '..', '.a', '.aaa'"}, {1, "<.", ", '.', '..', 'a', '.a', '..a', 'aa', 'aaa', 'aaaa', '.aaa'"}, {1, "<", ", '.', '..', '..a', '.a', '.aaa', 'a', 'aa', 'aaa', 'aaaa'"}, @@ -3002,8 +3007,8 @@ static void test_FindFirstFile_wildcards(void) {1, "<..", ", '.', '..', 'a', '.a', '..a', 'aa', 'aaa', 'aaaa', '.aaa'"}, {1, ".<.", ", '.', '..', '.a', '.aaa'"}, {1, "..<", ", '..a'"}, - {1, "<<", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa'"}, - {1, "<<.", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa'"}, + {1, "<<", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa', ' .a'"}, + {1, "<<.", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa', ' .a'"}, {1, "<. ", ", '.', '..', '..a', '.a', '.aaa', 'a', 'aa', 'aaa', 'aaaa'"}, {1, "< .", ", '.', '..', 'a', '.a', '..a', 'aa', 'aaa', 'aaaa', '.aaa'"}, {1, "< . ", ", '.', '..', '..a', '.a', '.aaa', 'a', 'aa', 'aaa', 'aaaa'"}, @@ -3019,7 +3024,7 @@ static void test_FindFirstFile_wildcards(void) {1, "??.", ", '.', '..', 'a', 'aa'"}, {1, "??. ", ", '.', '..', 'a', 'aa'"}, {1, "???.", ", '.', '..', 'a', 'aa', 'aaa'"}, - {1, "?.??.", ", '.', '..', '.a', 'a', 'a.a'"}, + {1, "?.??.", ", '.', '..', '.a', 'a', 'a.a', ' .a'"},
{1, ">", ", '.', '..', 'a'"}, {1, ">.", ", '.', '..', 'a'"}, @@ -3027,7 +3032,7 @@ static void test_FindFirstFile_wildcards(void) {1, ">>.", ", '.', '..', 'a', 'aa'"}, {1, ">>. ", ", '.', '..', 'a', 'aa'"}, {1, ">>>.", ", '.', '..', 'a', 'aa', 'aaa'"}, - {1, ">.>>.", ", '.', '..', '.a', 'a.a'"}, + {1, ">.>>.", ", '.', '..', '.a', 'a.a', ' .a'"}, };
CreateDirectoryA("test-dir", NULL);
From: Paul Gofman pgofman@codeweavers.com
--- dlls/kernelbase/file.c | 97 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 93 insertions(+), 4 deletions(-)
diff --git a/dlls/kernelbase/file.c b/dlls/kernelbase/file.c index 29d005e0e28..e4d9a1d70c1 100644 --- a/dlls/kernelbase/file.c +++ b/dlls/kernelbase/file.c @@ -58,6 +58,7 @@ typedef struct UINT data_pos; /* current position in dir data */ UINT data_len; /* length of dir data */ UINT data_size; /* size of data buffer, or 0 when everything has been read */ + WCHAR *mask; /* mask string to match if wildcards are used */ BYTE data[1]; /* directory data */ } FIND_FIRST_INFO;
@@ -1124,7 +1125,7 @@ HANDLE WINAPI DECLSPEC_HOTPATCH FindFirstFileExW( LPCWSTR filename, FINDEX_INFO_ OBJECT_ATTRIBUTES attr; IO_STATUS_BLOCK io; NTSTATUS status; - DWORD size, device = 0; + DWORD size, mask_size = 0, device = 0;
TRACE( "%s %d %p %d %p %lx\n", debugstr_w(filename), level, data, search_op, filter, flags );
@@ -1186,10 +1187,15 @@ HANDLE WINAPI DECLSPEC_HOTPATCH FindFirstFileExW( LPCWSTR filename, FINDEX_INFO_ { nt_name.Length = (mask - nt_name.Buffer) * sizeof(WCHAR); has_wildcard = wcspbrk( mask, L"*?" ) != NULL; - size = has_wildcard ? 8192 : max_entry_size; + if (has_wildcard) + { + size = 8192; + mask_size = (lstrlenW( mask ) + 1) * sizeof(*mask); + } + else size = max_entry_size; }
- if (!(info = HeapAlloc( GetProcessHeap(), 0, offsetof( FIND_FIRST_INFO, data[size] )))) + if (!(info = HeapAlloc( GetProcessHeap(), 0, offsetof( FIND_FIRST_INFO, data[size + mask_size] )))) { SetLastError( ERROR_NOT_ENOUGH_MEMORY ); goto error; @@ -1233,6 +1239,13 @@ HANDLE WINAPI DECLSPEC_HOTPATCH FindFirstFileExW( LPCWSTR filename, FINDEX_INFO_ info->data_size = size; info->search_op = search_op; info->level = level; + if (mask_size) + { + info->mask = (WCHAR *)(info->data + size); + memcpy( info->mask, mask, mask_size ); + mask = NULL; + } + else info->mask = NULL;
if (device) { @@ -1250,7 +1263,7 @@ HANDLE WINAPI DECLSPEC_HOTPATCH FindFirstFileExW( LPCWSTR filename, FINDEX_INFO_
RtlInitUnicodeString( &mask_str, mask ); status = NtQueryDirectoryFile( info->handle, 0, NULL, NULL, &io, info->data, info->data_size, - FileBothDirectoryInformation, FALSE, &mask_str, TRUE ); + FileBothDirectoryInformation, FALSE, has_wildcard ? NULL : &mask_str, TRUE ); if (status) { FindClose( info ); @@ -1333,6 +1346,74 @@ BOOL WINAPI DECLSPEC_HOTPATCH FindNextFileA( HANDLE handle, WIN32_FIND_DATAA *da }
+/*********************************************************************** + * match_filename + * + * Check if the file name matches mask containing wildcards. + */ +static BOOL match_filename( const WCHAR *name, int length, const WCHAR *mask ) +{ + BOOL mismatch; + const WCHAR *name_end = name + length; + const WCHAR *mask_end = mask + lstrlenW( mask ); + const WCHAR *lastjoker = NULL; + const WCHAR *next_to_retry = NULL; + + while (name < name_end && mask < mask_end) + { + switch(*mask) + { + case '*': + mask++; + while (mask < mask_end && *mask == '*') mask++; + if (mask == mask_end) return TRUE; /* end of mask is all '*', so match */ + lastjoker = mask; + + /* skip to the next match after the joker(s) */ + while (name < name_end && towupper( *name ) != towupper( *mask )) name++; + next_to_retry = name; + break; + case '?': + case '>': + mask++; + name++; + break; + default: + mismatch = towupper( *mask ) != towupper( *name ); + + if (!mismatch) + { + mask++; + name++; + if (mask == mask_end) + { + if (name == name_end) return TRUE; + if (lastjoker) mask = lastjoker; + } + } + else /* mismatch ! */ + { + if (lastjoker) /* we had an '*', so we can try unlimitedly */ + { + mask = lastjoker; + + /* this scan sequence was a mismatch, so restart + * 1 char after the first char we checked last time */ + next_to_retry++; + name = next_to_retry; + } + else return FALSE; + } + break; + } + } + + while (mask < mask_end && (*mask == '.' || *mask == '*')) + mask++; + return (name == name_end && mask == mask_end); +} + + /****************************************************************************** * FindNextFileW (kernelbase.@) */ @@ -1392,6 +1473,14 @@ BOOL WINAPI DECLSPEC_HOTPATCH FindNextFileW( HANDLE handle, WIN32_FIND_DATAW *da dir_info->FileName[0] == '.' && dir_info->FileName[1] == '.') continue; }
+ if (info->mask) + { + if (!match_filename( dir_info->FileName, dir_info->FileNameLength / sizeof(WCHAR), info->mask ) + && (!dir_info->ShortNameLength + || !match_filename( dir_info->ShortName, dir_info->ShortNameLength / sizeof(WCHAR), info->mask ))) + continue; + } + data->dwFileAttributes = dir_info->FileAttributes; data->ftCreationTime = *(FILETIME *)&dir_info->CreationTime; data->ftLastAccessTime = *(FILETIME *)&dir_info->LastAccessTime;
From: Paul Gofman pgofman@codeweavers.com
--- dlls/kernel32/tests/file.c | 4 ++-- dlls/kernelbase/file.c | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/dlls/kernel32/tests/file.c b/dlls/kernel32/tests/file.c index 0918651bc1c..af2347504e0 100644 --- a/dlls/kernel32/tests/file.c +++ b/dlls/kernel32/tests/file.c @@ -3019,8 +3019,8 @@ static void test_FindFirstFile_wildcards(void) {1, "..< ", ", '..a'"},
{1, "?", ", '.', '..', 'a'"}, - {1, "?.", ", '.', '..', 'a'"}, - {1, "?. ", ", '.', '..', 'a'"}, + {0, "?.", ", '.', '..', 'a'"}, + {0, "?. ", ", '.', '..', 'a'"}, {1, "??.", ", '.', '..', 'a', 'aa'"}, {1, "??. ", ", '.', '..', 'a', 'aa'"}, {1, "???.", ", '.', '..', 'a', 'aa', 'aaa'"}, diff --git a/dlls/kernelbase/file.c b/dlls/kernelbase/file.c index e4d9a1d70c1..f6d5087aa2b 100644 --- a/dlls/kernelbase/file.c +++ b/dlls/kernelbase/file.c @@ -1190,6 +1190,7 @@ HANDLE WINAPI DECLSPEC_HOTPATCH FindFirstFileExW( LPCWSTR filename, FINDEX_INFO_ if (has_wildcard) { size = 8192; + mask = PathFindFileNameW( filename ); mask_size = (lstrlenW( mask ) + 1) * sizeof(*mask); } else size = max_entry_size; @@ -1408,7 +1409,7 @@ static BOOL match_filename( const WCHAR *name, int length, const WCHAR *mask ) } }
- while (mask < mask_end && (*mask == '.' || *mask == '*')) + while (mask < mask_end && (*mask == ' ' || *mask == '.' || *mask == '*')) mask++; return (name == name_end && mask == mask_end); }
From: Paul Gofman pgofman@codeweavers.com
--- dlls/kernel32/tests/file.c | 12 ++++++------ dlls/kernelbase/file.c | 21 +++++++++++++++++++++ 2 files changed, 27 insertions(+), 6 deletions(-)
diff --git a/dlls/kernel32/tests/file.c b/dlls/kernel32/tests/file.c index af2347504e0..65164d7f2a4 100644 --- a/dlls/kernel32/tests/file.c +++ b/dlls/kernel32/tests/file.c @@ -2974,21 +2974,21 @@ static void test_FindFirstFile_wildcards(void) {0, ".*.*", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa'"}, {0, "*.*", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa', ' .a'"}, {0, ".*", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa'"}, - {1, "*.", ", '.', '..', 'a', '.a', '..a', 'aa', 'aaa', 'aaaa', '.aaa'"}, + {0, "*.", ", '.', '..', 'a', '.a', '..a', 'aa', 'aaa', 'aaaa', '.aaa'"}, {0, "*", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa', ' .a'"}, {1, "*..*", ", '.', '..', '..a', '..a.a', '.a..a', 'a..a'"}, - {1, "*..", ", '.', '..', 'a', '.a', '..a', 'aa', 'aaa', 'aaaa', '.aaa'"}, + {0, "*..", ", '.', '..', 'a', '.a', '..a', 'aa', 'aaa', 'aaaa', '.aaa'"}, {1, ".*.", ", '.', '..', '.a', '.aaa'"}, {0, "..*", ", '.', '..', '..a', '..a.a'"}, {0, "**", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa', ' .a'"}, {0, "**.", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa', ' .a'"}, {0, "*. ", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa', ' .a'"}, - {1, "* .", ", '.', '..', 'a', '.a', '..a', 'aa', 'aaa', 'aaaa', '.aaa'"}, + {0, "* .", ", '.', '..', 'a', '.a', '..a', 'aa', 'aaa', 'aaaa', '.aaa'"}, {0, "* . ", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa', ' .a'"}, {0, "*.. ", ", '.', '..', '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa', ' .a'"}, - {1, "*. .", ", '.', '..', 'a', '.a', '..a', 'aa', 'aaa', 'aaaa', '.aaa'"}, - {1, "* ..", ", '.', '..', 'a', '.a', '..a', 'aa', 'aaa', 'aaaa', '.aaa'"}, - {1, " *..", ""}, + {0, "*. .", ", '.', '..', 'a', '.a', '..a', 'aa', 'aaa', 'aaaa', '.aaa'"}, + {0, "* ..", ", '.', '..', 'a', '.a', '..a', 'aa', 'aaa', 'aaaa', '.aaa'"}, + {0, " *..", ""}, {0, "..* ", ", '.', '..', '..a', '..a.a'"}, {1, "a*.", ", '..a', '.a', '.aaa', 'a', 'aa', 'aaa', 'aaaa'"}, {0, "*a ", ", '..a', '..a.a', '.a', '.a..a', '.a.a', '.aaa', 'a', 'a..a', 'a.a', 'a.a.a', 'aa', 'aaa', 'aaaa', ' .a'"}, diff --git a/dlls/kernelbase/file.c b/dlls/kernelbase/file.c index f6d5087aa2b..aaa549bfd85 100644 --- a/dlls/kernelbase/file.c +++ b/dlls/kernelbase/file.c @@ -1347,6 +1347,19 @@ BOOL WINAPI DECLSPEC_HOTPATCH FindNextFileA( HANDLE handle, WIN32_FIND_DATAA *da }
+/*********************************************************************** + * name_has_ext + * + * Check if the file name has extension (skipping leading dots). + */ +static BOOL name_has_ext( const WCHAR *name, const WCHAR *name_end ) +{ + while (name != name_end && *name == '.') ++name; + while (name != name_end && *name != '.') ++name; + return name != name_end; +} + + /*********************************************************************** * match_filename * @@ -1359,6 +1372,14 @@ static BOOL match_filename( const WCHAR *name, int length, const WCHAR *mask ) const WCHAR *mask_end = mask + lstrlenW( mask ); const WCHAR *lastjoker = NULL; const WCHAR *next_to_retry = NULL; + const WCHAR *asterisk; + + if (mask != mask_end && mask_end[-1] == '.' && (asterisk = wcschr( mask, '*' )) && asterisk == wcsrchr( mask, '*' ) + && name_has_ext( name, name_end )) + { + /* Single '*' mask ending with '.' only matches files without extension. */ + return FALSE; + }
while (name < name_end && mask < mask_end) {