[PATCH 0/7] MR10105: ntdll: Various fixes related to NtQueryDirectoryFile().
From: Elizabeth Figura <zfigura@codeweavers.com> --- dlls/ntdll/tests/directory.c | 352 +++++++++++++++++++++++++++++++++++ include/winternl.h | 18 ++ 2 files changed, 370 insertions(+) diff --git a/dlls/ntdll/tests/directory.c b/dlls/ntdll/tests/directory.c index e1453b0b19d..9349f846a28 100644 --- a/dlls/ntdll/tests/directory.c +++ b/dlls/ntdll/tests/directory.c @@ -35,7 +35,9 @@ #include "wine/test.h" #include "winnls.h" +#include "winioctl.h" #include "winternl.h" +#include "ddk/ntifs.h" static NTSTATUS (WINAPI *pNtClose)( PHANDLE ); static NTSTATUS (WINAPI *pNtOpenFile) ( PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES, PIO_STATUS_BLOCK, ULONG, ULONG ); @@ -1360,6 +1362,355 @@ static void test_redirection(void) pRtlWow64EnableFsRedirectionEx( old, &cur ); } +/* Custom reparse data points must use REPARSE_DATA_GUID_BUFFER. + * In practice the two structs are nearly identical, and the GUID and tag can + * contain anything, but this does mean that the ReparseDataLength has to match + * the input size minus the header size, which is different. */ +static size_t init_reparse_custom( REPARSE_GUID_DATA_BUFFER **ret_buffer ) +{ + size_t size = offsetof( REPARSE_GUID_DATA_BUFFER, GenericReparseBuffer.DataBuffer[5] ); + REPARSE_GUID_DATA_BUFFER *buffer; + + buffer = malloc( size ); + buffer->ReparseTag = 0xbeef; + buffer->ReparseDataLength = size - offsetof( REPARSE_GUID_DATA_BUFFER, GenericReparseBuffer ); + buffer->Reserved = 0; + memset( &buffer->ReparseGuid, 0xcc, sizeof(GUID) ); + memcpy( buffer->GenericReparseBuffer.DataBuffer, "bogus", 5 ); + *ret_buffer = buffer; + return size; +} + +static size_t align( size_t n, size_t alignment ) +{ + return (n + alignment - 1) & ~(alignment - 1); +} + +static void check_string( const WCHAR *str, ULONG size, const WCHAR *expect ) +{ + ok( size == wcslen( expect ) * sizeof(WCHAR), "got size %lu\n", size ); + ok( !memcmp( str, expect, size ), "got %s\n", debugstr_wn( str, size / sizeof(WCHAR) )); +} + +static void test_info_classes(void) +{ + char buffer[1024]; + FILE_FULL_EA_INFORMATION *ea_info = (void *)buffer; + FILE_NETWORK_OPEN_INFORMATION open_info; + REPARSE_GUID_DATA_BUFFER *reparse_data; + WCHAR path[MAX_PATH], temp[MAX_PATH]; + FILE_ID_INFORMATION id_info; + LARGE_INTEGER zero = {0}; + OBJECT_ATTRIBUTES attr; + UNICODE_STRING string; + size_t reparse_size; + IO_STATUS_BLOCK io; + HANDLE file, child; + NTSTATUS ret; + + static const struct + { + FILE_INFORMATION_CLASS class; + ULONG size; + } + tests[] = + { + {FileDirectoryInformation, offsetof(FILE_DIRECTORY_INFORMATION, FileName)}, + {FileFullDirectoryInformation, offsetof(FILE_FULL_DIRECTORY_INFORMATION, FileName)}, + {FileBothDirectoryInformation, offsetof(FILE_BOTH_DIRECTORY_INFORMATION, FileName)}, + {FileIdBothDirectoryInformation, offsetof(FILE_ID_BOTH_DIRECTORY_INFORMATION, FileName)}, + {FileIdFullDirectoryInformation, offsetof(FILE_ID_FULL_DIRECTORY_INFORMATION, FileName)}, + {FileIdGlobalTxDirectoryInformation, offsetof(FILE_ID_GLOBAL_TX_DIR_INFORMATION, FileName)}, + {FileIdExtdDirectoryInformation, offsetof(FILE_ID_EXTD_DIRECTORY_INFORMATION, FileName)}, + {FileIdExtdBothDirectoryInformation, offsetof(FILE_ID_EXTD_BOTH_DIRECTORY_INFORMATION, FileName)}, + }; + + GetTempPathW( ARRAY_SIZE(temp), temp ); + swprintf( path, ARRAY_SIZE(path), L"%swinetest_dir", temp ); + RtlDosPathNameToNtPathName_U( path, &string, NULL, NULL ); + InitializeObjectAttributes( &attr, &string, 0, 0, NULL ); + ret = NtCreateFile( &file, GENERIC_ALL, &attr, &io, + NULL, 0, 0, FILE_OPEN_IF, FILE_DIRECTORY_FILE, NULL, 0 ); + ok( !ret, "failed to create %s, status %#lx\n", debugstr_w(path), ret ); + RtlFreeUnicodeString( &string ); + + RtlInitUnicodeString( &string, L"file" ); + InitializeObjectAttributes( &attr, &string, 0, file, NULL ); + ret = NtCreateFile( &child, GENERIC_ALL, &attr, &io, + NULL, 0, 0, FILE_OPEN_IF, FILE_NON_DIRECTORY_FILE, NULL, 0 ); + ok( !ret, "failed to create %s, status %#lx\n", debugstr_w(path), ret ); + reparse_size = init_reparse_custom( &reparse_data ); + + /* Make it a reparse point so we can test the reparse tag field. */ + ret = NtFsControlFile( child, NULL, NULL, NULL, &io, + FSCTL_SET_REPARSE_POINT, reparse_data, reparse_size, NULL, 0 ); + ok( !ret, "failed to set reparse point, status %#lx\n", ret ); + + ret = NtWriteFile( child, NULL, NULL, NULL, &io, "data", 4, &zero, NULL ); + ok( ret == STATUS_PENDING, "got %#lx\n", ret ); + ret = WaitForSingleObject( child, 100 ); + ok( !ret, "got %#lx\n", ret ); + ok( !io.Status, "got %#lx\n", io.Status ); + + /* Some information is stale unless we do this... */ + NtClose( child ); + ret = NtCreateFile( &child, GENERIC_ALL, &attr, &io, + NULL, 0, 0, FILE_OPEN_IF, FILE_NON_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT, NULL, 0 ); + ok( !ret, "failed to create %s, status %#lx\n", debugstr_w(path), ret ); + ret = NtQueryInformationFile( child, &io, &open_info, sizeof(open_info), FileNetworkOpenInformation ); + ok( !ret, "got %#lx\n", ret ); + ret = NtQueryInformationFile( child, &io, &id_info, sizeof(id_info), FileIdInformation ); + if (ret == STATUS_INVALID_INFO_CLASS) + { + memset( &id_info, 0, sizeof(id_info) ); + ret = NtQueryInformationFile( child, &io, &id_info.FileId, + sizeof(FILE_INTERNAL_INFORMATION), FileInternalInformation ); + } + ok( !ret, "got %#lx\n", ret ); + NtClose( child ); + + /* Make another child with EA so we can test the EA field. */ + + RtlInitUnicodeString( &string, L"file2" ); + InitializeObjectAttributes( &attr, &string, 0, file, NULL ); + ret = NtCreateFile( &child, GENERIC_ALL, &attr, &io, + NULL, 0, 0, FILE_OPEN_IF, FILE_NON_DIRECTORY_FILE, NULL, 0 ); + ok( !ret, "failed to create %s, status %#lx\n", debugstr_w(path), ret ); + + ea_info->NextEntryOffset = 0; + ea_info->Flags = 0; + ea_info->EaNameLength = 3; + ea_info->EaValueLength = 3; + strcpy( ea_info->EaName, "foo" ); + strcpy( ea_info->EaName + 4, "bar" ); + ret = NtSetEaFile( child, &io, buffer, offsetof( FILE_FULL_EA_INFORMATION, EaName[8] ) ); + todo_wine ok( !ret, "got %#lx\n", ret ); + NtClose( child ); + + for (unsigned int i = 0; i < ARRAY_SIZE(tests); ++i) + { + unsigned int struct_size = align( tests[i].size + sizeof(WCHAR), 8 ); + const FILE_DIRECTORY_INFORMATION *info = (void *)buffer; + + ret = NtQueryDirectoryFile( file, NULL, NULL, NULL, &io, + buffer, struct_size - 1, tests[i].class, FALSE, NULL, TRUE ); + if (ret == STATUS_INVALID_INFO_CLASS) + continue; + + winetest_push_context("class %u", tests[i].class); + + ok( ret == STATUS_INFO_LENGTH_MISMATCH, "got %#lx\n", ret ); + + memset( &io, 0xcc, sizeof(io) ); + ret = NtQueryDirectoryFile( file, NULL, NULL, NULL, &io, + buffer, struct_size, tests[i].class, FALSE, NULL, TRUE ); + todo_wine ok( ret == STATUS_PENDING, "got %#lx\n", ret ); + ret = WaitForSingleObject( file, 100 ); + ok( !ret, "got %#lx\n", ret ); + ok( !io.Status, "got %#lx\n", io.Status ); + ok( io.Information == tests[i].size + sizeof(WCHAR), + "expected %Iu, got %Iu\n", tests[i].size + sizeof(WCHAR), io.Information ); + + memset( &io, 0xcc, sizeof(io) ); + ret = NtQueryDirectoryFile( file, NULL, NULL, NULL, &io, + buffer, tests[i].size * 2 + (6 * sizeof(WCHAR)), tests[i].class, FALSE, NULL, TRUE ); + todo_wine ok( ret == STATUS_PENDING, "got %#lx\n", ret ); + ret = WaitForSingleObject( file, 100 ); + ok( !ret, "got %#lx\n", ret ); + ok( !io.Status, "got %#lx\n", io.Status ); + /* all classes start with the same few fields; test them here */ + ok( info->NextEntryOffset == align( tests[i].size + sizeof(WCHAR), 8 ), "expected %Iu, got %lu\n", + align( tests[i].size + sizeof(WCHAR), 8 ), info->NextEntryOffset ); + ok( !info->FileIndex, "got index %lu\n", info->FileIndex ); + + ok( io.Information == info->NextEntryOffset + tests[i].size + 2 * sizeof(WCHAR), "expected %Iu, got %Iu\n", + info->NextEntryOffset + tests[i].size + sizeof(WCHAR), io.Information ); + + info = (void *)(buffer + info->NextEntryOffset); + ok( !info->NextEntryOffset, "got %lu\n", info->NextEntryOffset ); + ok( !info->FileIndex, "got index %lu\n", info->FileIndex ); + + ret = NtQueryDirectoryFile( file, NULL, NULL, NULL, &io, + buffer, tests[i].size * 2 + (6 * sizeof(WCHAR)), tests[i].class, FALSE, NULL, FALSE ); + todo_wine ok( ret == STATUS_PENDING, "got %#lx\n", ret ); + ret = WaitForSingleObject( file, 100 ); + ok( !ret, "got %#lx\n", ret ); + ok( !io.Status, "got %#lx\n", io.Status ); + + info = (void *)buffer; + ok( !info->NextEntryOffset, "got %lu\n", info->NextEntryOffset ); + ok( !info->FileIndex, "got index %lu\n", info->FileIndex ); + ok( info->CreationTime.QuadPart == open_info.CreationTime.QuadPart, + "expected %I64u, got %I64u\n", open_info.CreationTime.QuadPart, info->CreationTime.QuadPart ); + ok( info->LastAccessTime.QuadPart == open_info.LastAccessTime.QuadPart, + "expected %I64u, got %I64u\n", open_info.LastAccessTime.QuadPart, info->LastAccessTime.QuadPart ); + ok( info->LastWriteTime.QuadPart == open_info.LastWriteTime.QuadPart, + "expected %I64u, got %I64u\n", open_info.LastWriteTime.QuadPart, info->LastWriteTime.QuadPart ); + ok( info->ChangeTime.QuadPart == open_info.ChangeTime.QuadPart, + "expected %I64u, got %I64u\n", open_info.ChangeTime.QuadPart, info->ChangeTime.QuadPart ); + ok( info->EndOfFile.QuadPart == open_info.EndOfFile.QuadPart, + "expected %I64u, got %I64u\n", open_info.EndOfFile.QuadPart, info->EndOfFile.QuadPart ); + ok( info->AllocationSize.QuadPart == open_info.AllocationSize.QuadPart, + "expected %I64u, got %I64u\n", open_info.AllocationSize.QuadPart, info->AllocationSize.QuadPart ); + ok( info->FileAttributes == open_info.FileAttributes, + "expected %#lx, got %#lx\n", open_info.FileAttributes, info->FileAttributes ); + + switch (tests[i].class) + { + case FileDirectoryInformation: + { + const FILE_DIRECTORY_INFORMATION *info = (void *)buffer; + check_string( info->FileName, info->FileNameLength, L"file" ); + break; + } + case FileFullDirectoryInformation: + { + const FILE_FULL_DIRECTORY_INFORMATION *info = (void *)buffer; + todo_wine ok( info->EaSize == 0xbeef, "got %#lx\n", info->EaSize ); + check_string( info->FileName, info->FileNameLength, L"file" ); + break; + } + case FileBothDirectoryInformation: + { + const FILE_BOTH_DIRECTORY_INFORMATION *info = (void *)buffer; + todo_wine ok( info->EaSize == 0xbeef, "got %#lx\n", info->EaSize ); + check_string( info->ShortName, info->ShortNameLength, L"" ); + check_string( info->FileName, info->FileNameLength, L"file" ); + break; + } + case FileIdBothDirectoryInformation: + { + const FILE_ID_BOTH_DIRECTORY_INFORMATION *info = (void *)buffer; + todo_wine ok( info->EaSize == 0xbeef, "got %#lx\n", info->EaSize ); + ok( !memcmp( &info->FileId, &id_info.FileId, sizeof( info->FileId )), + "expected ID %#I64x, got %#I64x\n", *(ULONGLONG *)&id_info.FileId, info->FileId.QuadPart); + check_string( info->ShortName, info->ShortNameLength, L"" ); + check_string( info->FileName, info->FileNameLength, L"file" ); + break; + } + case FileIdFullDirectoryInformation: + { + const FILE_ID_FULL_DIRECTORY_INFORMATION *info = (void *)buffer; + todo_wine ok( info->EaSize == 0xbeef, "got %#lx\n", info->EaSize ); + ok( !memcmp( &info->FileId, &id_info.FileId, sizeof( info->FileId )), + "expected ID %#I64x, got %#I64x\n", *(ULONGLONG *)&id_info.FileId, info->FileId.QuadPart); + check_string( info->FileName, info->FileNameLength, L"file" ); + break; + } + case FileIdGlobalTxDirectoryInformation: + { + const FILE_ID_GLOBAL_TX_DIR_INFORMATION *info = (void *)buffer; + static const GUID zero_guid; + ok( !memcmp( &info->FileId, &id_info.FileId, sizeof( info->FileId )), + "expected ID %#I64x, got %#I64x\n", *(ULONGLONG *)&id_info.FileId, info->FileId.QuadPart); + todo_wine ok( !memcmp( &info->LockingTransactionId, &zero_guid, sizeof( info->LockingTransactionId )), + "GUID didn't match\n" ); + ok( !info->TxInfoFlags, "got %lu\n", info->TxInfoFlags ); + check_string( info->FileName, info->FileNameLength, L"file" ); + break; + } + case FileIdExtdDirectoryInformation: + { + const FILE_ID_EXTD_DIRECTORY_INFORMATION *info = (void *)buffer; + ok( !info->EaSize, "got %#lx\n", info->EaSize ); + ok( info->ReparsePointTag == 0xbeef, "got tag %#lx\n", info->ReparsePointTag ); + ok( !memcmp( &info->FileId, &id_info.FileId, sizeof( info->FileId )), "ID didn't match\n" ); + check_string( info->FileName, info->FileNameLength, L"file" ); + break; + } + case FileIdExtdBothDirectoryInformation: + { + const FILE_ID_EXTD_BOTH_DIRECTORY_INFORMATION *info = (void *)buffer; + ok( !info->EaSize, "got %#lx\n", info->EaSize ); + ok( info->ReparsePointTag == 0xbeef, "got tag %#lx\n", info->ReparsePointTag ); + todo_wine ok( !memcmp( &info->FileId, &id_info.FileId, sizeof( info->FileId )), "ID didn't match\n" ); + check_string( info->ShortName, info->ShortNameLength, L"" ); + check_string( info->FileName, info->FileNameLength, L"file" ); + break; + } + default: + break; + } + + ret = NtQueryDirectoryFile( file, NULL, NULL, NULL, &io, + buffer, tests[i].size * 2 + (6 * sizeof(WCHAR)), tests[i].class, FALSE, NULL, FALSE ); + todo_wine ok( ret == STATUS_PENDING, "got %#lx\n", ret ); + ret = WaitForSingleObject( file, 100 ); + ok( !ret, "got %#lx\n", ret ); + ok( !io.Status, "got %#lx\n", io.Status ); + + info = (void *)buffer; + ok( !info->NextEntryOffset, "got %lu\n", info->NextEntryOffset ); + ok( !info->FileIndex, "got index %lu\n", info->FileIndex ); + ok( (info->FileAttributes & ~FILE_ATTRIBUTE_NOT_CONTENT_INDEXED) == FILE_ATTRIBUTE_ARCHIVE, + "got %#lx\n", info->FileAttributes ); + + switch (tests[i].class) + { + case FileFullDirectoryInformation: + { + const FILE_FULL_DIRECTORY_INFORMATION *info = (void *)buffer; + todo_wine ok( info->EaSize >= 8 && info->EaSize <= 16, "got %#lx\n", info->EaSize ); + check_string( info->FileName, info->FileNameLength, L"file2" ); + break; + } + case FileBothDirectoryInformation: + { + const FILE_BOTH_DIRECTORY_INFORMATION *info = (void *)buffer; + todo_wine ok( info->EaSize >= 8 && info->EaSize <= 16, "got %#lx\n", info->EaSize ); + check_string( info->FileName, info->FileNameLength, L"file2" ); + break; + } + case FileIdBothDirectoryInformation: + { + const FILE_ID_BOTH_DIRECTORY_INFORMATION *info = (void *)buffer; + todo_wine ok( info->EaSize >= 8 && info->EaSize <= 16, "got %#lx\n", info->EaSize ); + check_string( info->FileName, info->FileNameLength, L"file2" ); + break; + } + case FileIdFullDirectoryInformation: + { + const FILE_ID_FULL_DIRECTORY_INFORMATION *info = (void *)buffer; + todo_wine ok( info->EaSize >= 8 && info->EaSize <= 16, "got %#lx\n", info->EaSize ); + check_string( info->FileName, info->FileNameLength, L"file2" ); + break; + } + case FileIdExtdDirectoryInformation: + { + const FILE_ID_EXTD_DIRECTORY_INFORMATION *info = (void *)buffer; + ok( info->EaSize >= 8 && info->EaSize <= 16, "got %#lx\n", info->EaSize ); + ok( !info->ReparsePointTag, "got tag %#lx\n", info->ReparsePointTag ); + check_string( info->FileName, info->FileNameLength, L"file2" ); + break; + } + case FileIdExtdBothDirectoryInformation: + { + const FILE_ID_EXTD_BOTH_DIRECTORY_INFORMATION *info = (void *)buffer; + todo_wine ok( info->EaSize >= 8 && info->EaSize <= 16, "got %#lx\n", info->EaSize ); + todo_wine ok( !info->ReparsePointTag, "got tag %#lx\n", info->ReparsePointTag ); + check_string( info->FileName, info->FileNameLength, L"file2" ); + break; + } + default: + break; + } + + winetest_pop_context(); + } + + NtClose( file ); + + swprintf( path, ARRAY_SIZE(path), L"%swinetest_dir\\file", temp ); + ret = DeleteFileW( path ); + ok( ret == TRUE, "failed to delete %s, error %lu\n", debugstr_w(path), GetLastError() ); + swprintf( path, ARRAY_SIZE(path), L"%swinetest_dir\\file2", temp ); + ret = DeleteFileW( path ); + ok( ret == TRUE, "failed to delete %s, error %lu\n", debugstr_w(path), GetLastError() ); + swprintf( path, ARRAY_SIZE(path), L"%swinetest_dir", temp ); + ret = RemoveDirectoryW( path ); + ok( ret == TRUE, "failed to delete %s, error %lu\n", debugstr_w(path), GetLastError() ); +} + START_TEST(directory) { WCHAR sysdir[MAX_PATH]; @@ -1385,4 +1736,5 @@ START_TEST(directory) test_NtQueryDirectoryFile_case(); test_NtQueryDirectoryFile_change_mask(); test_redirection(); + test_info_classes(); } diff --git a/include/winternl.h b/include/winternl.h index 12630274ec3..a3b6781c63d 100644 --- a/include/winternl.h +++ b/include/winternl.h @@ -1805,6 +1805,24 @@ typedef struct _FILE_IO_COMPLETION_NOTIFICATION_INFORMATION { #define FILE_SKIP_SET_EVENT_ON_HANDLE 0x2 #define FILE_SKIP_SET_USER_EVENT_ON_FAST_IO 0x4 +typedef struct _FILE_ID_EXTD_DIRECTORY_INFORMATION +{ + ULONG NextEntryOffset; + ULONG FileIndex; + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER EndOfFile; + LARGE_INTEGER AllocationSize; + ULONG FileAttributes; + ULONG FileNameLength; + ULONG EaSize; + ULONG ReparsePointTag; + FILE_ID_128 FileId; + WCHAR FileName[ANYSIZE_ARRAY]; +} FILE_ID_EXTD_DIRECTORY_INFORMATION, *PFILE_ID_EXTD_DIRECTORY_INFORMATION; + typedef struct _FILE_ID_EXTD_BOTH_DIRECTORY_INFORMATION { ULONG NextEntryOffset; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10105
From: Elizabeth Figura <zfigura@codeweavers.com> --- dlls/kernel32/tests/file.c | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/dlls/kernel32/tests/file.c b/dlls/kernel32/tests/file.c index d66ab2efe66..aa3f25910bd 100644 --- a/dlls/kernel32/tests/file.c +++ b/dlls/kernel32/tests/file.c @@ -2740,8 +2740,12 @@ static void test_FindFirstFileA(void) int err; char buffer[5] = "C:\\"; char buffer2[100]; - char nonexistent[MAX_PATH]; + FILE_FULL_EA_INFORMATION *ea_info = (void *)buffer2; + char nonexistent[MAX_PATH], temp[MAX_PATH]; + IO_STATUS_BLOCK io; BOOL found = FALSE; + NTSTATUS status; + BOOL ret; /* try FindFirstFileA on "C:\" */ buffer[0] = get_windows_drive(); @@ -2931,6 +2935,33 @@ static void test_FindFirstFileA(void) err = GetLastError(); ok ( handle == INVALID_HANDLE_VALUE, "FindFirstFile on %s should fail\n", buffer2 ); ok ( err == ERROR_PATH_NOT_FOUND, "Bad Error number %d\n", err ); + + /* Test a file with EA. */ + GetTempPathA(ARRAY_SIZE(temp), temp); + strcat(temp, "winetest_ea"); + handle = CreateFileA(temp, GENERIC_ALL, 0, NULL, CREATE_ALWAYS, 0, 0); + ok(handle != INVALID_HANDLE_VALUE, "failed to create %s, error %lu\n", + debugstr_a(temp), GetLastError()); + + ea_info->NextEntryOffset = 0; + ea_info->Flags = 0; + ea_info->EaNameLength = 3; + ea_info->EaValueLength = 3; + strcpy(ea_info->EaName, "foo"); + strcpy(ea_info->EaName + 4, "bar"); + status = NtSetEaFile(handle, &io, ea_info, offsetof(FILE_FULL_EA_INFORMATION, EaName[8])); + todo_wine ok(!status, "got %#lx\n", status); + CloseHandle(handle); + + handle = FindFirstFileA(temp, &data); + ok(handle != INVALID_HANDLE_VALUE, "got error %lu\n", GetLastError()); + ok((data.dwFileAttributes & ~FILE_ATTRIBUTE_NOT_CONTENT_INDEXED) == FILE_ATTRIBUTE_ARCHIVE, + "got attributes %#lx\n", data.dwFileAttributes); + ok(!data.dwReserved0, "got reserved0 %#lx\n", data.dwReserved0); + FindClose(handle); + + ret = DeleteFileA(temp); + ok(ret == TRUE, "got error %lu\n", GetLastError()); } static void test_FindNextFileA(void) -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10105
From: Elizabeth Figura <zfigura@codeweavers.com> --- dlls/ntdll/tests/directory.c | 2 +- dlls/ntdll/unix/file.c | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/dlls/ntdll/tests/directory.c b/dlls/ntdll/tests/directory.c index 9349f846a28..8ca34a24ab7 100644 --- a/dlls/ntdll/tests/directory.c +++ b/dlls/ntdll/tests/directory.c @@ -1687,7 +1687,7 @@ static void test_info_classes(void) { const FILE_ID_EXTD_BOTH_DIRECTORY_INFORMATION *info = (void *)buffer; todo_wine ok( info->EaSize >= 8 && info->EaSize <= 16, "got %#lx\n", info->EaSize ); - todo_wine ok( !info->ReparsePointTag, "got tag %#lx\n", info->ReparsePointTag ); + ok( !info->ReparsePointTag, "got tag %#lx\n", info->ReparsePointTag ); check_string( info->FileName, info->FileNameLength, L"file2" ); break; } diff --git a/dlls/ntdll/unix/file.c b/dlls/ntdll/unix/file.c index 4c36bec9e7c..90267736db7 100644 --- a/dlls/ntdll/unix/file.c +++ b/dlls/ntdll/unix/file.c @@ -1784,6 +1784,7 @@ static int get_file_info( const char *path, struct stat *st, ULONG *attr, ULONG *attr = 0; ret = lstat( path, st ); if (ret == -1) return ret; + if (reparse_tag) *reparse_tag = 0; if (S_ISLNK( st->st_mode )) { ret = stat( path, st ); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10105
From: Elizabeth Figura <zfigura@codeweavers.com> --- dlls/ntdll/tests/directory.c | 8 ++++---- dlls/ntdll/unix/file.c | 10 ++++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/dlls/ntdll/tests/directory.c b/dlls/ntdll/tests/directory.c index 8ca34a24ab7..a9c4a447d50 100644 --- a/dlls/ntdll/tests/directory.c +++ b/dlls/ntdll/tests/directory.c @@ -1566,14 +1566,14 @@ static void test_info_classes(void) case FileFullDirectoryInformation: { const FILE_FULL_DIRECTORY_INFORMATION *info = (void *)buffer; - todo_wine ok( info->EaSize == 0xbeef, "got %#lx\n", info->EaSize ); + ok( info->EaSize == 0xbeef, "got %#lx\n", info->EaSize ); check_string( info->FileName, info->FileNameLength, L"file" ); break; } case FileBothDirectoryInformation: { const FILE_BOTH_DIRECTORY_INFORMATION *info = (void *)buffer; - todo_wine ok( info->EaSize == 0xbeef, "got %#lx\n", info->EaSize ); + ok( info->EaSize == 0xbeef, "got %#lx\n", info->EaSize ); check_string( info->ShortName, info->ShortNameLength, L"" ); check_string( info->FileName, info->FileNameLength, L"file" ); break; @@ -1581,7 +1581,7 @@ static void test_info_classes(void) case FileIdBothDirectoryInformation: { const FILE_ID_BOTH_DIRECTORY_INFORMATION *info = (void *)buffer; - todo_wine ok( info->EaSize == 0xbeef, "got %#lx\n", info->EaSize ); + ok( info->EaSize == 0xbeef, "got %#lx\n", info->EaSize ); ok( !memcmp( &info->FileId, &id_info.FileId, sizeof( info->FileId )), "expected ID %#I64x, got %#I64x\n", *(ULONGLONG *)&id_info.FileId, info->FileId.QuadPart); check_string( info->ShortName, info->ShortNameLength, L"" ); @@ -1591,7 +1591,7 @@ static void test_info_classes(void) case FileIdFullDirectoryInformation: { const FILE_ID_FULL_DIRECTORY_INFORMATION *info = (void *)buffer; - todo_wine ok( info->EaSize == 0xbeef, "got %#lx\n", info->EaSize ); + ok( info->EaSize == 0xbeef, "got %#lx\n", info->EaSize ); ok( !memcmp( &info->FileId, &id_info.FileId, sizeof( info->FileId )), "expected ID %#I64x, got %#I64x\n", *(ULONGLONG *)&id_info.FileId, info->FileId.QuadPart); check_string( info->FileName, info->FileNameLength, L"file" ); diff --git a/dlls/ntdll/unix/file.c b/dlls/ntdll/unix/file.c index 90267736db7..3ed49a46274 100644 --- a/dlls/ntdll/unix/file.c +++ b/dlls/ntdll/unix/file.c @@ -2499,30 +2499,32 @@ static NTSTATUS get_dir_data_entry( struct dir_data *dir_data, void *info_ptr, I break; case FileFullDirectoryInformation: - info->full.EaSize = 0; /* FIXME */ + /* non-Extd classes return the reparse tag in EaSize if there is one */ + info->full.EaSize = reparse_tag; info->full.FileNameLength = name_len; break; case FileIdFullDirectoryInformation: - info->id_full.EaSize = 0; /* FIXME */ + info->id_full.EaSize = reparse_tag; info->id_full.FileNameLength = name_len; break; case FileBothDirectoryInformation: - info->both.EaSize = 0; /* FIXME */ + info->both.EaSize = reparse_tag; info->both.ShortNameLength = wcslen( names->short_name ) * sizeof(WCHAR); memcpy( info->both.ShortName, names->short_name, info->both.ShortNameLength ); info->both.FileNameLength = name_len; break; case FileIdBothDirectoryInformation: - info->id_both.EaSize = 0; /* FIXME */ + info->id_both.EaSize = reparse_tag; info->id_both.ShortNameLength = wcslen( names->short_name ) * sizeof(WCHAR); memcpy( info->id_both.ShortName, names->short_name, info->id_both.ShortNameLength ); info->id_both.FileNameLength = name_len; break; case FileIdExtdBothDirectoryInformation: + /* Extd classes do *not* return the reparse tag in EaSize */ info->extd_both.EaSize = 0; /* FIXME */ info->extd_both.ReparsePointTag = reparse_tag; info->extd_both.ShortNameLength = wcslen( names->short_name ) * sizeof(WCHAR); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10105
From: Elizabeth Figura <zfigura@codeweavers.com> Using FileIdExtdBothDirectoryInformation introduced a regression in the game "Monster Hunter Stories 3: Twisted Reflection Trial" distributed via Steam, which prevents DLSS from being enabled. The game calls FindFirstFileEx() while hotpatching NtQueryDirectoryFile(). The hook's purpose is unclear, but using FileIdExtdBothDirectoryInformation or FileIdExtdDirectoryInformation causes it to essentially reject almost all of the files enumerated, returning as if the directory were empty, except for the first and last calls made. Fixes: 221f03e447d5c583412d0288a6200045ba84d800 --- dlls/kernelbase/file.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/dlls/kernelbase/file.c b/dlls/kernelbase/file.c index 7c079260af9..57bfe7fe871 100644 --- a/dlls/kernelbase/file.c +++ b/dlls/kernelbase/file.c @@ -63,7 +63,7 @@ typedef struct #define FIND_FIRST_MAGIC 0xc0ffee11 -static const UINT max_entry_size = offsetof( FILE_ID_EXTD_BOTH_DIRECTORY_INFORMATION, FileName[256] ); +static const UINT max_entry_size = offsetof( FILE_BOTH_DIRECTORY_INFORMATION, FileName[256] ); const WCHAR windows_dir[] = L"C:\\windows"; const WCHAR system_dir[] = L"C:\\windows\\system32"; @@ -1406,7 +1406,7 @@ HANDLE WINAPI DECLSPEC_HOTPATCH FindFirstFileExW( LPCWSTR filename, FINDEX_INFO_ { RtlInitUnicodeString( &mask_str, fixedup_mask ); status = NtQueryDirectoryFile( info->handle, 0, NULL, NULL, &io, info->data, info->data_size, - FileIdExtdBothDirectoryInformation, FALSE, &mask_str, TRUE ); + FileBothDirectoryInformation, FALSE, &mask_str, TRUE ); } if (fixedup_mask != mask) HeapFree( GetProcessHeap(), 0, fixedup_mask ); if (status) @@ -1506,7 +1506,7 @@ BOOL WINAPI DECLSPEC_HOTPATCH FindNextFileA( HANDLE handle, WIN32_FIND_DATAA *da BOOL WINAPI DECLSPEC_HOTPATCH FindNextFileW( HANDLE handle, WIN32_FIND_DATAW *data ) { FIND_FIRST_INFO *info = handle; - FILE_ID_EXTD_BOTH_DIRECTORY_INFORMATION *dir_info; + FILE_BOTH_DIR_INFORMATION *dir_info; BOOL ret = FALSE; NTSTATUS status; @@ -1529,7 +1529,7 @@ BOOL WINAPI DECLSPEC_HOTPATCH FindNextFileW( HANDLE handle, WIN32_FIND_DATAW *da if (info->data_size) status = NtQueryDirectoryFile( info->handle, 0, NULL, NULL, &io, info->data, info->data_size, - FileIdExtdBothDirectoryInformation, FALSE, NULL, FALSE ); + FileBothDirectoryInformation, FALSE, NULL, FALSE ); else status = STATUS_NO_MORE_FILES; @@ -1546,7 +1546,7 @@ BOOL WINAPI DECLSPEC_HOTPATCH FindNextFileW( HANDLE handle, WIN32_FIND_DATAW *da info->data_pos = 0; } - dir_info = (FILE_ID_EXTD_BOTH_DIRECTORY_INFORMATION *)(info->data + info->data_pos); + dir_info = (FILE_BOTH_DIR_INFORMATION *)(info->data + info->data_pos); if (dir_info->NextEntryOffset) info->data_pos += dir_info->NextEntryOffset; else info->data_pos = info->data_len; @@ -1566,7 +1566,12 @@ BOOL WINAPI DECLSPEC_HOTPATCH FindNextFileW( HANDLE handle, WIN32_FIND_DATAW *da data->ftLastWriteTime = *(FILETIME *)&dir_info->LastWriteTime; data->nFileSizeHigh = dir_info->EndOfFile.QuadPart >> 32; data->nFileSizeLow = (DWORD)dir_info->EndOfFile.QuadPart; - data->dwReserved0 = dir_info->ReparsePointTag; + /* We intentionally do not use FileIdExtdBothDirectoryInformation here; + * it breaks certain hotpatchers. Windows returns the reparse tag in + * EaSize for FileBothDirectoryInformation. */ + data->dwReserved0 = 0; + if (dir_info->FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) + data->dwReserved0 = dir_info->EaSize; data->dwReserved1 = 0; memcpy( data->cFileName, dir_info->FileName, dir_info->FileNameLength ); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10105
From: Elizabeth Figura <zfigura@codeweavers.com> --- dlls/ntdll/tests/directory.c | 2 +- dlls/ntdll/unix/file.c | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/dlls/ntdll/tests/directory.c b/dlls/ntdll/tests/directory.c index a9c4a447d50..2ebf0ca8254 100644 --- a/dlls/ntdll/tests/directory.c +++ b/dlls/ntdll/tests/directory.c @@ -1623,7 +1623,7 @@ static void test_info_classes(void) const FILE_ID_EXTD_BOTH_DIRECTORY_INFORMATION *info = (void *)buffer; ok( !info->EaSize, "got %#lx\n", info->EaSize ); ok( info->ReparsePointTag == 0xbeef, "got tag %#lx\n", info->ReparsePointTag ); - todo_wine ok( !memcmp( &info->FileId, &id_info.FileId, sizeof( info->FileId )), "ID didn't match\n" ); + ok( !memcmp( &info->FileId, &id_info.FileId, sizeof( info->FileId )), "ID didn't match\n" ); check_string( info->ShortName, info->ShortNameLength, L"" ); check_string( info->FileName, info->FileNameLength, L"file" ); break; diff --git a/dlls/ntdll/unix/file.c b/dlls/ntdll/unix/file.c index 3ed49a46274..d006cb0c215 100644 --- a/dlls/ntdll/unix/file.c +++ b/dlls/ntdll/unix/file.c @@ -2090,6 +2090,7 @@ static NTSTATUS fill_file_info( const struct stat *st, ULONG attr, void *ptr, case FileIdExtdBothDirectoryInformation: { FILE_ID_EXTD_BOTH_DIRECTORY_INFORMATION *info = ptr; + memset( &info->FileId, 0, sizeof(info->FileId) ); *(ULONGLONG *)&info->FileId = st->st_ino; fill_file_info( st, attr, info, FileDirectoryInformation ); } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10105
From: Elizabeth Figura <zfigura@codeweavers.com> --- dlls/ntdll/tests/directory.c | 2 +- dlls/ntdll/unix/file.c | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/dlls/ntdll/tests/directory.c b/dlls/ntdll/tests/directory.c index 2ebf0ca8254..34101ce9947 100644 --- a/dlls/ntdll/tests/directory.c +++ b/dlls/ntdll/tests/directory.c @@ -1603,7 +1603,7 @@ static void test_info_classes(void) static const GUID zero_guid; ok( !memcmp( &info->FileId, &id_info.FileId, sizeof( info->FileId )), "expected ID %#I64x, got %#I64x\n", *(ULONGLONG *)&id_info.FileId, info->FileId.QuadPart); - todo_wine ok( !memcmp( &info->LockingTransactionId, &zero_guid, sizeof( info->LockingTransactionId )), + ok( !memcmp( &info->LockingTransactionId, &zero_guid, sizeof( info->LockingTransactionId )), "GUID didn't match\n" ); ok( !info->TxInfoFlags, "got %lu\n", info->TxInfoFlags ); check_string( info->FileName, info->FileNameLength, L"file" ); diff --git a/dlls/ntdll/unix/file.c b/dlls/ntdll/unix/file.c index d006cb0c215..5177e35c7a8 100644 --- a/dlls/ntdll/unix/file.c +++ b/dlls/ntdll/unix/file.c @@ -2535,6 +2535,7 @@ static NTSTATUS get_dir_data_entry( struct dir_data *dir_data, void *info_ptr, I case FileIdGlobalTxDirectoryInformation: info->id_tx.TxInfoFlags = 0; + memset( &info->id_tx.LockingTransactionId, 0, sizeof(GUID) ); info->id_tx.FileNameLength = name_len; break; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10105
participants (2)
-
Elizabeth Figura -
Elizabeth Figura (@zfigura)