diff --git a/README.md b/README.md index cefbb732..aab12cad 100644 --- a/README.md +++ b/README.md @@ -39,12 +39,13 @@ Wine. All those differences are also documented on the Included bug fixes and improvements ----------------------------------- -**Bug fixes and features included in the next upcoming release [5]:** +**Bug fixes and features included in the next upcoming release [6]:** * Add implementation for kernel32.GetNumaProcessorNode ([Wine Bug #38660](https://bugs.winehq.org/show_bug.cgi?id=38660)) * Implement mscoree._CorValidateImage for mono runtime ([Wine Bug #38662](https://bugs.winehq.org/show_bug.cgi?id=38662)) * Implement proper handling of CLI .NET images in Wine library loader ([Wine Bug #38661](https://bugs.winehq.org/show_bug.cgi?id=38661)) * Multiple applications needs better NtQueryInformationJobObject stub ([Wine Bug #38658](https://bugs.winehq.org/show_bug.cgi?id=38658)) +* Support for NtSetInformationFile class FileLinkInformation * Support for NtSetInformationFile class FileRenameInformation ([Wine Bug #30399](https://bugs.winehq.org/show_bug.cgi?id=30399)) diff --git a/debian/changelog b/debian/changelog index c7524275..c47bcb00 100644 --- a/debian/changelog +++ b/debian/changelog @@ -11,6 +11,8 @@ wine-staging (1.7.45) UNRELEASED; urgency=low * Added patch to fix opening a readonly file with FILE_WRITE_ATTRIBUTES access (fixes Wine Staging Bug #298). * Added patch to silence test failures in ntdll/directory tests. + * Added patches for support of FileLinkInformation (fixes Wine Staging Bug + #297). * Removed patch to fix NULL pointer dereference in get_frame_by_name (identical patch accepted upstream). -- Sebastian Lackner Sun, 31 May 2015 14:46:37 +0200 diff --git a/patches/ntdll-FileDispositionInformation/0015-include-Add-declaration-for-FILE_LINK_INFORMATION.patch b/patches/ntdll-FileDispositionInformation/0015-include-Add-declaration-for-FILE_LINK_INFORMATION.patch new file mode 100644 index 00000000..61e3bc12 --- /dev/null +++ b/patches/ntdll-FileDispositionInformation/0015-include-Add-declaration-for-FILE_LINK_INFORMATION.patch @@ -0,0 +1,30 @@ +From b4838252600fd8b4325b1a7d5753a84df40ef131 Mon Sep 17 00:00:00 2001 +From: Zhaonan Liang +Date: Tue, 28 Apr 2015 19:06:31 +0800 +Subject: include: Add declaration for FILE_LINK_INFORMATION. + +--- + include/winternl.h | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/include/winternl.h b/include/winternl.h +index a84c6d4..d208537 100644 +--- a/include/winternl.h ++++ b/include/winternl.h +@@ -567,6 +567,13 @@ typedef struct _FILE_RENAME_INFORMATION { + WCHAR FileName[1]; + } FILE_RENAME_INFORMATION, *PFILE_RENAME_INFORMATION; + ++typedef struct _FILE_LINK_INFORMATION { ++ BOOLEAN ReplaceIfExists; ++ HANDLE RootDirectory; ++ ULONG FileNameLength; ++ WCHAR FileName[1]; ++} FILE_LINK_INFORMATION, *PFILE_LINK_INFORMATION; ++ + typedef struct _FILE_NAMES_INFORMATION { + ULONG NextEntryOffset; + ULONG FileIndex; +-- +2.4.2 + diff --git a/patches/ntdll-FileDispositionInformation/0016-ntdll-tests-Add-tests-for-FileLinkInformation-class.patch b/patches/ntdll-FileDispositionInformation/0016-ntdll-tests-Add-tests-for-FileLinkInformation-class.patch new file mode 100644 index 00000000..1851cefa --- /dev/null +++ b/patches/ntdll-FileDispositionInformation/0016-ntdll-tests-Add-tests-for-FileLinkInformation-class.patch @@ -0,0 +1,730 @@ +From 3049a4cb5eaa0e35e2ce1621a4c090c1fdc13a57 Mon Sep 17 00:00:00 2001 +From: Qian Hong +Date: Wed, 3 Jun 2015 17:41:59 +0800 +Subject: ntdll/tests: Add tests for FileLinkInformation class. + +Mostly copy and paste from Sebastian Lackner's FileNameInformation class +tests, credit to Sebastian. +--- + dlls/ntdll/tests/file.c | 698 ++++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 698 insertions(+) + +diff --git a/dlls/ntdll/tests/file.c b/dlls/ntdll/tests/file.c +index 8072a15..c985a03 100644 +--- a/dlls/ntdll/tests/file.c ++++ b/dlls/ntdll/tests/file.c +@@ -2149,6 +2149,703 @@ static void test_file_rename_information(void) + delete_object( newpath ); + } + ++static void test_file_link_information(void) ++{ ++ static const WCHAR foo_txt[] = {'\\','f','o','o','.','t','x','t',0}; ++ static const WCHAR foo[] = {'f','o','o',0}; ++ WCHAR tmp_path[MAX_PATH], oldpath[MAX_PATH + 16], newpath[MAX_PATH + 16], *filename, *p; ++ UNICODE_STRING name_str; ++ HANDLE handle, handle2; ++ NTSTATUS res; ++ IO_STATUS_BLOCK io; ++ FILE_LINK_INFORMATION *pfli; ++ FILE_NAME_INFORMATION *pfni; ++ BOOL success, fileDeleted; ++ ++ GetTempPathW( MAX_PATH, tmp_path ); ++ ++ /* oldpath is a file, newpath doesn't exist */ ++ res = GetTempFileNameW( tmp_path, foo, 0, oldpath ); ++ ok( res != 0, "fail to create temp file\n" ); ++ handle = CreateFileW( oldpath, GENERIC_WRITE | DELETE, 0, NULL, CREATE_ALWAYS, 0, 0 ); ++ ok( handle != INVALID_HANDLE_VALUE, "CreateFileW failed\n" ); ++ ++ res = GetTempFileNameW( tmp_path, foo, 0, newpath ); ++ ok( res != 0, "fail to create temp file\n" ); ++ pRtlDosPathNameToNtPathName_U( newpath, &name_str, NULL, NULL ); ++ DeleteFileW( newpath ); ++ pfli = HeapAlloc( GetProcessHeap(), 0, sizeof(FILE_LINK_INFORMATION) + name_str.Length ); ++ pfli->ReplaceIfExists = FALSE; ++ pfli->RootDirectory = NULL; ++ pfli->FileNameLength = name_str.Length; ++ memcpy( pfli->FileName, name_str.Buffer, name_str.Length ); ++ pRtlFreeUnicodeString( &name_str ); ++ ++ U(io).Status = 0xdeadbeef; ++ res = pNtSetInformationFile( handle, &io, pfli, sizeof(FILE_LINK_INFORMATION) + pfli->FileNameLength, FileLinkInformation ); ++ todo_wine ok( U(io).Status == STATUS_SUCCESS, "io.Status expected STATUS_SUCCESS, got %x\n", U(io).Status ); ++ todo_wine ok( res == STATUS_SUCCESS, "res expected STATUS_SUCCESS, got %x\n", res ); ++ fileDeleted = GetFileAttributesW( oldpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; ++ ok( !fileDeleted, "File should exist\n" ); ++ fileDeleted = GetFileAttributesW( newpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; ++ todo_wine ok( !fileDeleted, "File should exist\n" ); ++ ++ pfni = HeapAlloc( GetProcessHeap(), 0, sizeof(FILE_NAME_INFORMATION) + MAX_PATH * sizeof(WCHAR) ); ++ res = pNtQueryInformationFile( handle, &io, pfni, sizeof(FILE_NAME_INFORMATION) + MAX_PATH * sizeof(WCHAR), FileNameInformation ); ++ ok( res == STATUS_SUCCESS, "res expected STATUS_SUCCESS, got %x\n", res ); ++ pfni->FileName[ pfni->FileNameLength / sizeof(WCHAR) ] = 0; ++ ok( !lstrcmpW(pfni->FileName, oldpath + 2), "FileName expected %s, got %s\n", ++ wine_dbgstr_w(oldpath + 2), wine_dbgstr_w(pfni->FileName) ); ++ HeapFree( GetProcessHeap(), 0, pfni ); ++ ++ CloseHandle( handle ); ++ HeapFree( GetProcessHeap(), 0, pfli ); ++ delete_object( oldpath ); ++ delete_object( newpath ); ++ ++ /* oldpath is a file, newpath is a file, ReplaceIfExists = FALSE */ ++ res = GetTempFileNameW( tmp_path, foo, 0, oldpath ); ++ ok( res != 0, "fail to create temp file\n" ); ++ handle = CreateFileW( oldpath, GENERIC_WRITE | DELETE, 0, NULL, CREATE_ALWAYS, 0, 0 ); ++ ok( handle != INVALID_HANDLE_VALUE, "CreateFileW failed\n" ); ++ ++ res = GetTempFileNameW( tmp_path, foo, 0, newpath ); ++ ok( res != 0, "fail to create temp file\n" ); ++ pRtlDosPathNameToNtPathName_U( newpath, &name_str, NULL, NULL ); ++ pfli = HeapAlloc( GetProcessHeap(), 0, sizeof(FILE_LINK_INFORMATION) + name_str.Length ); ++ pfli->ReplaceIfExists = FALSE; ++ pfli->RootDirectory = NULL; ++ pfli->FileNameLength = name_str.Length; ++ memcpy( pfli->FileName, name_str.Buffer, name_str.Length ); ++ pRtlFreeUnicodeString( &name_str ); ++ ++ U(io).Status = 0xdeadbeef; ++ res = pNtSetInformationFile( handle, &io, pfli, sizeof(FILE_LINK_INFORMATION) + pfli->FileNameLength, FileLinkInformation ); ++ todo_wine ok( U(io).Status == 0xdeadbeef, "shouldn't be able to set FileLinkInformation, io.Status is %x\n", U(io).Status ); ++ todo_wine ok( res == STATUS_OBJECT_NAME_COLLISION, "expect STATUS_OBJECT_NAME_COLLISION, res is %x\n", res ); ++ fileDeleted = GetFileAttributesW( oldpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; ++ ok( !fileDeleted, "File should exist\n" ); ++ fileDeleted = GetFileAttributesW( newpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; ++ ok( !fileDeleted, "File should exist\n" ); ++ ++ CloseHandle( handle ); ++ HeapFree( GetProcessHeap(), 0, pfli ); ++ delete_object( oldpath ); ++ delete_object( newpath ); ++ ++ /* oldpath is a file, newpath is a file, ReplaceIfExists = FALSE, target file opened */ ++ res = GetTempFileNameW( tmp_path, foo, 0, oldpath ); ++ ok( res != 0, "fail to create temp file\n" ); ++ handle = CreateFileW( oldpath, GENERIC_WRITE | DELETE, 0, NULL, CREATE_ALWAYS, 0, 0 ); ++ ok( handle != INVALID_HANDLE_VALUE, "CreateFileW failed\n" ); ++ ++ res = GetTempFileNameW( tmp_path, foo, 0, newpath ); ++ ok( res != 0, "fail to create temp file\n" ); ++ handle2 = CreateFileW( newpath, GENERIC_WRITE | DELETE, 0, NULL, CREATE_ALWAYS, 0, 0 ); ++ ok( handle2 != INVALID_HANDLE_VALUE, "CreateFileW failed\n" ); ++ ++ pRtlDosPathNameToNtPathName_U( newpath, &name_str, NULL, NULL ); ++ pfli = HeapAlloc( GetProcessHeap(), 0, sizeof(FILE_LINK_INFORMATION) + name_str.Length ); ++ pfli->ReplaceIfExists = FALSE; ++ pfli->RootDirectory = NULL; ++ pfli->FileNameLength = name_str.Length; ++ memcpy( pfli->FileName, name_str.Buffer, name_str.Length ); ++ pRtlFreeUnicodeString( &name_str ); ++ ++ U(io).Status = 0xdeadbeef; ++ res = pNtSetInformationFile( handle, &io, pfli, sizeof(FILE_LINK_INFORMATION) + pfli->FileNameLength, FileLinkInformation ); ++ todo_wine ok( U(io).Status == 0xdeadbeef, "io.Status expect 0xdeadbeef, io.Status is %x\n", U(io).Status ); ++ todo_wine ok( res == STATUS_OBJECT_NAME_COLLISION, "res expect STATUS_OBJECT_NAME_COLLISION, res is %x\n", res ); ++ fileDeleted = GetFileAttributesW( oldpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; ++ ok( !fileDeleted, "File should exist\n" ); ++ fileDeleted = GetFileAttributesW( newpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; ++ ok( !fileDeleted, "File should exist\n" ); ++ ++ CloseHandle( handle ); ++ CloseHandle( handle2 ); ++ HeapFree( GetProcessHeap(), 0, pfli ); ++ delete_object( oldpath ); ++ delete_object( newpath ); ++ ++ /* oldpath is a file, newpath is a file, ReplaceIfExists = TRUE */ ++ res = GetTempFileNameW( tmp_path, foo, 0, oldpath ); ++ ok( res != 0, "fail to create temp file\n" ); ++ handle = CreateFileW( oldpath, GENERIC_WRITE | DELETE, 0, NULL, CREATE_ALWAYS, 0, 0 ); ++ ok( handle != INVALID_HANDLE_VALUE, "CreateFileW failed\n" ); ++ ++ res = GetTempFileNameW( tmp_path, foo, 0, newpath ); ++ ok( res != 0, "fail to create temp file\n" ); ++ pRtlDosPathNameToNtPathName_U( newpath, &name_str, NULL, NULL ); ++ pfli = HeapAlloc( GetProcessHeap(), 0, sizeof(FILE_LINK_INFORMATION) + name_str.Length ); ++ pfli->ReplaceIfExists = TRUE; ++ pfli->RootDirectory = NULL; ++ pfli->FileNameLength = name_str.Length; ++ memcpy( pfli->FileName, name_str.Buffer, name_str.Length ); ++ pRtlFreeUnicodeString( &name_str ); ++ ++ U(io).Status = 0xdeadbeef; ++ res = pNtSetInformationFile( handle, &io, pfli, sizeof(FILE_LINK_INFORMATION) + pfli->FileNameLength, FileLinkInformation ); ++ todo_wine ok( U(io).Status == STATUS_SUCCESS, "io.Status expect STATUS_SUCCESS, io.Status is %x\n", U(io).Status ); ++ todo_wine ok( res == STATUS_SUCCESS, "res expect STATUS_SUCCESS, res is %x\n", res ); ++ fileDeleted = GetFileAttributesW( oldpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; ++ ok( !fileDeleted, "File should exist\n" ); ++ fileDeleted = GetFileAttributesW( newpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; ++ ok( !fileDeleted, "File should exist\n" ); ++ ++ CloseHandle( handle ); ++ HeapFree( GetProcessHeap(), 0, pfli ); ++ delete_object( oldpath ); ++ delete_object( newpath ); ++ ++ /* oldpath is a file, newpath is a file, ReplaceIfExists = TRUE, target file opened */ ++ res = GetTempFileNameW( tmp_path, foo, 0, oldpath ); ++ ok( res != 0, "fail to create temp file\n" ); ++ handle = CreateFileW( oldpath, GENERIC_WRITE | DELETE, 0, NULL, CREATE_ALWAYS, 0, 0 ); ++ ok( handle != INVALID_HANDLE_VALUE, "CreateFileW failed\n" ); ++ ++ res = GetTempFileNameW( tmp_path, foo, 0, newpath ); ++ ok( res != 0, "fail to create temp file\n" ); ++ handle2 = CreateFileW( newpath, GENERIC_WRITE | DELETE, 0, NULL, CREATE_ALWAYS, 0, 0 ); ++ ok( handle2 != INVALID_HANDLE_VALUE, "CreateFileW failed\n" ); ++ ++ pRtlDosPathNameToNtPathName_U( newpath, &name_str, NULL, NULL ); ++ pfli = HeapAlloc( GetProcessHeap(), 0, sizeof(FILE_LINK_INFORMATION) + name_str.Length ); ++ pfli->ReplaceIfExists = TRUE; ++ pfli->RootDirectory = NULL; ++ pfli->FileNameLength = name_str.Length; ++ memcpy( pfli->FileName, name_str.Buffer, name_str.Length ); ++ pRtlFreeUnicodeString( &name_str ); ++ ++ U(io).Status = 0xdeadbeef; ++ res = pNtSetInformationFile( handle, &io, pfli, sizeof(FILE_LINK_INFORMATION) + pfli->FileNameLength, FileLinkInformation ); ++ todo_wine ok( U(io).Status == 0xdeadbeef, "io.Status expect 0xdeadbeef, io.Status is %x\n", U(io).Status ); ++ todo_wine ok( res == STATUS_ACCESS_DENIED, "res expect STATUS_ACCESS_DENIED, res is %x\n", res ); ++ fileDeleted = GetFileAttributesW( oldpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; ++ ok( !fileDeleted, "File should exist\n" ); ++ fileDeleted = GetFileAttributesW( newpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; ++ ok( !fileDeleted, "File should exist\n" ); ++ ++ CloseHandle( handle ); ++ CloseHandle( handle2 ); ++ HeapFree( GetProcessHeap(), 0, pfli ); ++ delete_object( oldpath ); ++ delete_object( newpath ); ++ ++ /* oldpath is a file, newpath is a file, ReplaceIfExists = TRUE, target file opened with FILE_SHARE_DELETE */ ++ res = GetTempFileNameW( tmp_path, foo, 0, oldpath ); ++ ok( res != 0, "fail to create temp file\n" ); ++ handle = CreateFileW( oldpath, GENERIC_WRITE | DELETE, 0, NULL, CREATE_ALWAYS, 0, 0 ); ++ ok( handle != INVALID_HANDLE_VALUE, "CreateFileW failed\n" ); ++ ++ res = GetTempFileNameW( tmp_path, foo, 0, newpath ); ++ ok( res != 0, "fail to create temp file\n" ); ++ handle2 = CreateFileW( newpath, GENERIC_WRITE | DELETE, FILE_SHARE_DELETE, NULL, CREATE_ALWAYS, 0, 0 ); ++ ok( handle2 != INVALID_HANDLE_VALUE, "CreateFileW failed\n" ); ++ ++ pRtlDosPathNameToNtPathName_U( newpath, &name_str, NULL, NULL ); ++ pfli = HeapAlloc( GetProcessHeap(), 0, sizeof(FILE_LINK_INFORMATION) + name_str.Length ); ++ pfli->ReplaceIfExists = TRUE; ++ pfli->RootDirectory = NULL; ++ pfli->FileNameLength = name_str.Length; ++ memcpy( pfli->FileName, name_str.Buffer, name_str.Length ); ++ pRtlFreeUnicodeString( &name_str ); ++ ++ U(io).Status = 0xdeadbeef; ++ res = pNtSetInformationFile( handle, &io, pfli, sizeof(FILE_LINK_INFORMATION) + pfli->FileNameLength, FileLinkInformation ); ++ todo_wine ok( U(io).Status == 0xdeadbeef, "io.Status expect 0xdeadbeef, io.Status is %x\n", U(io).Status ); ++ todo_wine ok( res == STATUS_ACCESS_DENIED, "res expect STATUS_ACCESS_DENIED, res is %x\n", res ); ++ fileDeleted = GetFileAttributesW( oldpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; ++ ok( !fileDeleted, "File should exist\n" ); ++ fileDeleted = GetFileAttributesW( newpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; ++ ok( !fileDeleted, "File should exist\n" ); ++ ++ CloseHandle( handle ); ++ CloseHandle( handle2 ); ++ HeapFree( GetProcessHeap(), 0, pfli ); ++ delete_object( oldpath ); ++ delete_object( newpath ); ++ ++ /* oldpath is a file, newpath is a file, ReplaceIfExists = TRUE, target file opened with FILE_SHARE_DELETE | FILE_SHARE_WRITE */ ++ res = GetTempFileNameW( tmp_path, foo, 0, oldpath ); ++ ok( res != 0, "fail to create temp file\n" ); ++ handle = CreateFileW( oldpath, GENERIC_WRITE | DELETE, 0, NULL, CREATE_ALWAYS, 0, 0 ); ++ ok( handle != INVALID_HANDLE_VALUE, "CreateFileW failed\n" ); ++ ++ res = GetTempFileNameW( tmp_path, foo, 0, newpath ); ++ ok( res != 0, "fail to create temp file\n" ); ++ handle2 = CreateFileW( newpath, GENERIC_WRITE | DELETE, FILE_SHARE_DELETE | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, 0 ); ++ ok( handle2 != INVALID_HANDLE_VALUE, "CreateFileW failed\n" ); ++ ++ pRtlDosPathNameToNtPathName_U( newpath, &name_str, NULL, NULL ); ++ pfli = HeapAlloc( GetProcessHeap(), 0, sizeof(FILE_LINK_INFORMATION) + name_str.Length ); ++ pfli->ReplaceIfExists = TRUE; ++ pfli->RootDirectory = NULL; ++ pfli->FileNameLength = name_str.Length; ++ memcpy( pfli->FileName, name_str.Buffer, name_str.Length ); ++ pRtlFreeUnicodeString( &name_str ); ++ ++ U(io).Status = 0xdeadbeef; ++ res = pNtSetInformationFile( handle, &io, pfli, sizeof(FILE_LINK_INFORMATION) + pfli->FileNameLength, FileLinkInformation ); ++ todo_wine ok( U(io).Status == 0xdeadbeef, "io.Status expect 0xdeadbeef, io.Status is %x\n", U(io).Status ); ++ todo_wine ok( res == STATUS_ACCESS_DENIED, "res expect STATUS_ACCESS_DENIED, res is %x\n", res ); ++ fileDeleted = GetFileAttributesW( oldpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; ++ ok( !fileDeleted, "File should exist\n" ); ++ fileDeleted = GetFileAttributesW( newpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; ++ ok( !fileDeleted, "File should exist\n" ); ++ ++ CloseHandle( handle ); ++ CloseHandle( handle2 ); ++ HeapFree( GetProcessHeap(), 0, pfli ); ++ delete_object( oldpath ); ++ delete_object( newpath ); ++ ++ /* oldpath is a directory, newpath doesn't exist, ReplaceIfExists = FALSE */ ++ res = GetTempFileNameW( tmp_path, foo, 0, oldpath ); ++ ok( res != 0, "fail to create temp file\n" ); ++ DeleteFileW( oldpath ); ++ success = CreateDirectoryW( oldpath, NULL ); ++ ok( success != 0, "failed to create temp directory\n" ); ++ handle = CreateFileW( oldpath, GENERIC_WRITE | DELETE, 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0 ); ++ ok( handle != INVALID_HANDLE_VALUE, "CreateFileW failed\n" ); ++ ++ res = GetTempFileNameW( tmp_path, foo, 0, newpath ); ++ ok( res != 0, "fail to create temp file\n" ); ++ pRtlDosPathNameToNtPathName_U( newpath, &name_str, NULL, NULL ); ++ DeleteFileW( newpath ); ++ pfli = HeapAlloc( GetProcessHeap(), 0, sizeof(FILE_LINK_INFORMATION) + name_str.Length ); ++ pfli->ReplaceIfExists = FALSE; ++ pfli->RootDirectory = NULL; ++ pfli->FileNameLength = name_str.Length; ++ memcpy( pfli->FileName, name_str.Buffer, name_str.Length ); ++ pRtlFreeUnicodeString( &name_str ); ++ ++ U(io).Status = 0xdeadbeef; ++ res = pNtSetInformationFile( handle, &io, pfli, sizeof(FILE_LINK_INFORMATION) + pfli->FileNameLength, FileLinkInformation ); ++ todo_wine ok( U(io).Status == 0xdeadbeef , "io.Status expected 0xdeadbeef, got %x\n", U(io).Status ); ++ todo_wine ok( res == STATUS_FILE_IS_A_DIRECTORY, "res expected STATUS_FILE_IS_A_DIRECTORY, got %x\n", res ); ++ fileDeleted = GetFileAttributesW( oldpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; ++ ok( !fileDeleted, "File should exist\n" ); ++ fileDeleted = GetFileAttributesW( newpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; ++ ok( fileDeleted, "File should have been deleted\n" ); ++ ++ pfni = HeapAlloc( GetProcessHeap(), 0, sizeof(FILE_NAME_INFORMATION) + MAX_PATH * sizeof(WCHAR) ); ++ res = pNtQueryInformationFile( handle, &io, pfni, sizeof(FILE_NAME_INFORMATION) + MAX_PATH * sizeof(WCHAR), FileNameInformation ); ++ ok( res == STATUS_SUCCESS, "res expected STATUS_SUCCESS, got %x\n", res ); ++ pfni->FileName[ pfni->FileNameLength / sizeof(WCHAR) ] = 0; ++ ok( !lstrcmpW(pfni->FileName, oldpath + 2), "FileName expected %s, got %s\n", ++ wine_dbgstr_w(oldpath + 2), wine_dbgstr_w(pfni->FileName) ); ++ HeapFree( GetProcessHeap(), 0, pfni ); ++ ++ CloseHandle( handle ); ++ HeapFree( GetProcessHeap(), 0, pfli ); ++ delete_object( oldpath ); ++ delete_object( newpath ); ++ ++ /* oldpath is a directory (but child object opened), newpath doesn't exist, ReplaceIfExists = FALSE */ ++ res = GetTempFileNameW( tmp_path, foo, 0, oldpath ); ++ ok( res != 0, "fail to create temp file\n" ); ++ DeleteFileW( oldpath ); ++ success = CreateDirectoryW( oldpath, NULL ); ++ ok( success != 0, "failed to create temp directory\n" ); ++ handle = CreateFileW( oldpath, GENERIC_WRITE | DELETE, 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0 ); ++ ok( handle != INVALID_HANDLE_VALUE, "CreateFileW failed\n" ); ++ ++ lstrcpyW( newpath, oldpath ); ++ lstrcatW( newpath, foo_txt ); ++ handle2 = CreateFileW( newpath, GENERIC_WRITE | DELETE, 0, NULL, CREATE_ALWAYS, FILE_FLAG_DELETE_ON_CLOSE, 0 ); ++ ok( handle2 != INVALID_HANDLE_VALUE, "CreateFileW failed\n" ); ++ ++ res = GetTempFileNameW( tmp_path, foo, 0, newpath ); ++ ok( res != 0, "fail to create temp file\n" ); ++ pRtlDosPathNameToNtPathName_U( newpath, &name_str, NULL, NULL ); ++ DeleteFileW( newpath ); ++ pfli = HeapAlloc( GetProcessHeap(), 0, sizeof(FILE_LINK_INFORMATION) + name_str.Length ); ++ pfli->ReplaceIfExists = FALSE; ++ pfli->RootDirectory = NULL; ++ pfli->FileNameLength = name_str.Length; ++ memcpy( pfli->FileName, name_str.Buffer, name_str.Length ); ++ pRtlFreeUnicodeString( &name_str ); ++ ++ U(io).Status = 0xdeadbeef; ++ res = pNtSetInformationFile( handle, &io, pfli, sizeof(FILE_LINK_INFORMATION) + pfli->FileNameLength, FileLinkInformation ); ++ todo_wine ok( U(io).Status == 0xdeadbeef, "io.Status expected 0xdeadbeef, got %x\n", U(io).Status ); ++ todo_wine ok( res == STATUS_FILE_IS_A_DIRECTORY, "res expected STATUS_FILE_IS_A_DIRECTORY, got %x\n", res ); ++ fileDeleted = GetFileAttributesW( oldpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; ++ ok( !fileDeleted, "File should exist\n" ); ++ fileDeleted = GetFileAttributesW( newpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; ++ ok( fileDeleted, "File should not exist\n" ); ++ ++ CloseHandle( handle ); ++ CloseHandle( handle2 ); ++ HeapFree( GetProcessHeap(), 0, pfli ); ++ delete_object( oldpath ); ++ delete_object( newpath ); ++ ++ /* oldpath is a directory, newpath is a file, ReplaceIfExists = FALSE */ ++ res = GetTempFileNameW( tmp_path, foo, 0, oldpath ); ++ ok( res != 0, "fail to create temp file\n" ); ++ DeleteFileW( oldpath ); ++ success = CreateDirectoryW( oldpath, NULL ); ++ ok( success != 0, "failed to create temp directory\n" ); ++ handle = CreateFileW( oldpath, GENERIC_WRITE | DELETE, 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0 ); ++ ok( handle != INVALID_HANDLE_VALUE, "CreateFileW failed\n" ); ++ ++ res = GetTempFileNameW( tmp_path, foo, 0, newpath ); ++ ok( res != 0, "fail to create temp file\n" ); ++ pRtlDosPathNameToNtPathName_U( newpath, &name_str, NULL, NULL ); ++ pfli = HeapAlloc( GetProcessHeap(), 0, sizeof(FILE_LINK_INFORMATION) + name_str.Length ); ++ pfli->ReplaceIfExists = FALSE; ++ pfli->RootDirectory = NULL; ++ pfli->FileNameLength = name_str.Length; ++ memcpy( pfli->FileName, name_str.Buffer, name_str.Length ); ++ pRtlFreeUnicodeString( &name_str ); ++ ++ U(io).Status = 0xdeadbeef; ++ res = pNtSetInformationFile( handle, &io, pfli, sizeof(FILE_LINK_INFORMATION) + pfli->FileNameLength, FileLinkInformation ); ++ todo_wine ok( U(io).Status == 0xdeadbeef, "shouldn't be able to set FileLinkInformation, io.Status is %x\n", U(io).Status ); ++ todo_wine ok( res == STATUS_OBJECT_NAME_COLLISION || broken(res == STATUS_FILE_IS_A_DIRECTORY) /* > Win XP */, ++ "expect STATUS_OBJECT_NAME_COLLISION or STATUS_FILE_IS_A_DIRECTORY, res is %x\n", res ); ++ fileDeleted = GetFileAttributesW( oldpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; ++ ok( !fileDeleted, "File should exist\n" ); ++ fileDeleted = GetFileAttributesW( newpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; ++ ok( !fileDeleted, "File should exist\n" ); ++ ++ CloseHandle( handle ); ++ HeapFree( GetProcessHeap(), 0, pfli ); ++ delete_object( oldpath ); ++ delete_object( newpath ); ++ ++ /* oldpath is a directory, newpath is a file, ReplaceIfExists = FALSE, target file opened */ ++ res = GetTempFileNameW( tmp_path, foo, 0, oldpath ); ++ ok( res != 0, "fail to create temp file\n" ); ++ DeleteFileW( oldpath ); ++ success = CreateDirectoryW( oldpath, NULL ); ++ ok( success != 0, "failed to create temp directory\n" ); ++ handle = CreateFileW( oldpath, GENERIC_WRITE | DELETE, 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0 ); ++ ok( handle != INVALID_HANDLE_VALUE, "CreateFileW failed\n" ); ++ ++ res = GetTempFileNameW( tmp_path, foo, 0, newpath ); ++ ok( res != 0, "fail to create temp file\n" ); ++ handle2 = CreateFileW( newpath, GENERIC_WRITE | DELETE, 0, NULL, CREATE_ALWAYS, 0, 0 ); ++ ok( handle2 != INVALID_HANDLE_VALUE, "CreateFileW failed\n" ); ++ ++ pRtlDosPathNameToNtPathName_U( newpath, &name_str, NULL, NULL ); ++ pfli = HeapAlloc( GetProcessHeap(), 0, sizeof(FILE_LINK_INFORMATION) + name_str.Length ); ++ pfli->ReplaceIfExists = FALSE; ++ pfli->RootDirectory = NULL; ++ pfli->FileNameLength = name_str.Length; ++ memcpy( pfli->FileName, name_str.Buffer, name_str.Length ); ++ pRtlFreeUnicodeString( &name_str ); ++ ++ U(io).Status = 0xdeadbeef; ++ res = pNtSetInformationFile( handle, &io, pfli, sizeof(FILE_LINK_INFORMATION) + pfli->FileNameLength, FileLinkInformation ); ++ todo_wine ok( U(io).Status == 0xdeadbeef, "shouldn't be able to set FileLinkInformation, io.Status is %x\n", U(io).Status ); ++ todo_wine ok( res == STATUS_OBJECT_NAME_COLLISION || broken(res == STATUS_FILE_IS_A_DIRECTORY) /* > Win XP */, ++ "expect STATUS_OBJECT_NAME_COLLISION or STATUS_FILE_IS_A_DIRECTORY, res is %x\n", res ); ++ fileDeleted = GetFileAttributesW( oldpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; ++ ok( !fileDeleted, "File should exist\n" ); ++ fileDeleted = GetFileAttributesW( newpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; ++ ok( !fileDeleted, "File should exist\n" ); ++ ++ CloseHandle( handle ); ++ CloseHandle( handle2 ); ++ HeapFree( GetProcessHeap(), 0, pfli ); ++ delete_object( oldpath ); ++ delete_object( newpath ); ++ ++ /* oldpath is a directory, newpath is a file, ReplaceIfExists = TRUE */ ++ res = GetTempFileNameW( tmp_path, foo, 0, oldpath ); ++ ok( res != 0, "fail to create temp file\n" ); ++ DeleteFileW( oldpath ); ++ success = CreateDirectoryW( oldpath, NULL ); ++ ok( success != 0, "failed to create temp directory\n" ); ++ handle = CreateFileW( oldpath, GENERIC_WRITE | DELETE, 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0 ); ++ ok( handle != INVALID_HANDLE_VALUE, "CreateFileW failed\n" ); ++ ++ res = GetTempFileNameW( tmp_path, foo, 0, newpath ); ++ ok( res != 0, "fail to create temp file\n" ); ++ pRtlDosPathNameToNtPathName_U( newpath, &name_str, NULL, NULL ); ++ pfli = HeapAlloc( GetProcessHeap(), 0, sizeof(FILE_LINK_INFORMATION) + name_str.Length ); ++ pfli->ReplaceIfExists = TRUE; ++ pfli->RootDirectory = NULL; ++ pfli->FileNameLength = name_str.Length; ++ memcpy( pfli->FileName, name_str.Buffer, name_str.Length ); ++ pRtlFreeUnicodeString( &name_str ); ++ ++ U(io).Status = 0xdeadbeef; ++ res = pNtSetInformationFile( handle, &io, pfli, sizeof(FILE_LINK_INFORMATION) + pfli->FileNameLength, FileLinkInformation ); ++ todo_wine ok( U(io).Status == 0xdeadbeef, "io.Status expect 0xdeadbeef, io.Status is %x\n", U(io).Status ); ++ todo_wine ok( res == STATUS_FILE_IS_A_DIRECTORY, "res expect STATUS_FILE_IS_A_DIRECTORY, res is %x\n", res ); ++ fileDeleted = GetFileAttributesW( oldpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; ++ ok( !fileDeleted, "File should exist\n" ); ++ fileDeleted = GetFileAttributesW( newpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; ++ ok( !fileDeleted, "File should exist\n" ); ++ ++ CloseHandle( handle ); ++ HeapFree( GetProcessHeap(), 0, pfli ); ++ delete_object( oldpath ); ++ delete_object( newpath ); ++ ++ /* oldpath is a directory, newpath is a file, ReplaceIfExists = TRUE, target file opened */ ++ res = GetTempFileNameW( tmp_path, foo, 0, oldpath ); ++ ok( res != 0, "fail to create temp file\n" ); ++ DeleteFileW( oldpath ); ++ success = CreateDirectoryW( oldpath, NULL ); ++ ok( success != 0, "failed to create temp directory\n" ); ++ handle = CreateFileW( oldpath, GENERIC_WRITE | DELETE, 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0 ); ++ ok( handle != INVALID_HANDLE_VALUE, "CreateFileW failed\n" ); ++ ++ res = GetTempFileNameW( tmp_path, foo, 0, newpath ); ++ ok( res != 0, "fail to create temp file\n" ); ++ handle2 = CreateFileW( newpath, GENERIC_WRITE | DELETE, 0, NULL, CREATE_ALWAYS, 0, 0 ); ++ ok( handle2 != INVALID_HANDLE_VALUE, "CreateFileW failed\n" ); ++ ++ pRtlDosPathNameToNtPathName_U( newpath, &name_str, NULL, NULL ); ++ pfli = HeapAlloc( GetProcessHeap(), 0, sizeof(FILE_LINK_INFORMATION) + name_str.Length ); ++ pfli->ReplaceIfExists = TRUE; ++ pfli->RootDirectory = NULL; ++ pfli->FileNameLength = name_str.Length; ++ memcpy( pfli->FileName, name_str.Buffer, name_str.Length ); ++ pRtlFreeUnicodeString( &name_str ); ++ ++ U(io).Status = 0xdeadbeef; ++ res = pNtSetInformationFile( handle, &io, pfli, sizeof(FILE_LINK_INFORMATION) + pfli->FileNameLength, FileLinkInformation ); ++ todo_wine ok( U(io).Status == 0xdeadbeef, "io.Status expect 0xdeadbeef, io.Status is %x\n", U(io).Status ); ++ todo_wine ok( res == STATUS_FILE_IS_A_DIRECTORY, "res expect STATUS_FILE_IS_A_DIRECTORY, res is %x\n", res ); ++ fileDeleted = GetFileAttributesW( oldpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; ++ ok( !fileDeleted, "File should exist\n" ); ++ fileDeleted = GetFileAttributesW( newpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; ++ ok( !fileDeleted, "File should exist\n" ); ++ ++ CloseHandle( handle ); ++ CloseHandle( handle2 ); ++ HeapFree( GetProcessHeap(), 0, pfli ); ++ delete_object( oldpath ); ++ delete_object( newpath ); ++ ++ /* oldpath is a directory, newpath is a directory, ReplaceIfExists = FALSE */ ++ res = GetTempFileNameW( tmp_path, foo, 0, oldpath ); ++ ok( res != 0, "fail to create temp file\n" ); ++ DeleteFileW( oldpath ); ++ success = CreateDirectoryW( oldpath, NULL ); ++ ok( success != 0, "failed to create temp directory\n" ); ++ handle = CreateFileW( oldpath, GENERIC_WRITE | DELETE, 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0 ); ++ ok( handle != INVALID_HANDLE_VALUE, "CreateFileW failed\n" ); ++ ++ res = GetTempFileNameW( tmp_path, foo, 0, newpath ); ++ ok( res != 0, "fail to create temp file\n" ); ++ DeleteFileW( newpath ); ++ success = CreateDirectoryW( newpath, NULL ); ++ ok( success != 0, "failed to create temp directory\n" ); ++ pRtlDosPathNameToNtPathName_U( newpath, &name_str, NULL, NULL ); ++ pfli = HeapAlloc( GetProcessHeap(), 0, sizeof(FILE_LINK_INFORMATION) + name_str.Length ); ++ pfli->ReplaceIfExists = FALSE; ++ pfli->RootDirectory = NULL; ++ pfli->FileNameLength = name_str.Length; ++ memcpy( pfli->FileName, name_str.Buffer, name_str.Length ); ++ pRtlFreeUnicodeString( &name_str ); ++ ++ U(io).Status = 0xdeadbeef; ++ res = pNtSetInformationFile( handle, &io, pfli, sizeof(FILE_LINK_INFORMATION) + pfli->FileNameLength, FileLinkInformation ); ++ todo_wine ok( U(io).Status == 0xdeadbeef, "shouldn't be able to set FileLinkInformation, io.Status is %x\n", U(io).Status ); ++ todo_wine ok( res == STATUS_OBJECT_NAME_COLLISION || broken(res == STATUS_FILE_IS_A_DIRECTORY) /* > Win XP */, ++ "expect STATUS_OBJECT_NAME_COLLISION or STATUS_FILE_IS_A_DIRECTORY, res is %x\n", res ); ++ fileDeleted = GetFileAttributesW( oldpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; ++ ok( !fileDeleted, "File should exist\n" ); ++ fileDeleted = GetFileAttributesW( newpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; ++ ok( !fileDeleted, "File should exist\n" ); ++ ++ CloseHandle( handle ); ++ HeapFree( GetProcessHeap(), 0, pfli ); ++ delete_object( oldpath ); ++ delete_object( newpath ); ++ ++ /* oldpath is a directory, newpath is a directory, ReplaceIfExists = TRUE */ ++ res = GetTempFileNameW( tmp_path, foo, 0, oldpath ); ++ ok( res != 0, "fail to create temp file\n" ); ++ DeleteFileW( oldpath ); ++ success = CreateDirectoryW( oldpath, NULL ); ++ ok( success != 0, "failed to create temp directory\n" ); ++ handle = CreateFileW( oldpath, GENERIC_WRITE | DELETE, 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0 ); ++ ok( handle != INVALID_HANDLE_VALUE, "CreateFileW failed\n" ); ++ ++ res = GetTempFileNameW( tmp_path, foo, 0, newpath ); ++ ok( res != 0, "fail to create temp file\n" ); ++ DeleteFileW( newpath ); ++ success = CreateDirectoryW( newpath, NULL ); ++ ok( success != 0, "failed to create temp directory\n" ); ++ pRtlDosPathNameToNtPathName_U( newpath, &name_str, NULL, NULL ); ++ pfli = HeapAlloc( GetProcessHeap(), 0, sizeof(FILE_LINK_INFORMATION) + name_str.Length ); ++ pfli->ReplaceIfExists = TRUE; ++ pfli->RootDirectory = NULL; ++ pfli->FileNameLength = name_str.Length; ++ memcpy( pfli->FileName, name_str.Buffer, name_str.Length ); ++ pRtlFreeUnicodeString( &name_str ); ++ ++ U(io).Status = 0xdeadbeef; ++ res = pNtSetInformationFile( handle, &io, pfli, sizeof(FILE_LINK_INFORMATION) + pfli->FileNameLength, FileLinkInformation ); ++ todo_wine ok( U(io).Status == 0xdeadbeef, "io.Status expect 0xdeadbeef, io.Status is %x\n", U(io).Status ); ++ todo_wine ok( res == STATUS_FILE_IS_A_DIRECTORY, "res expect STATUS_FILE_IS_A_DIRECTORY, res is %x\n", res ); ++ fileDeleted = GetFileAttributesW( oldpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; ++ ok( !fileDeleted, "File should exist\n" ); ++ fileDeleted = GetFileAttributesW( newpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; ++ ok( !fileDeleted, "File should exist\n" ); ++ ++ CloseHandle( handle ); ++ HeapFree( GetProcessHeap(), 0, pfli ); ++ delete_object( oldpath ); ++ delete_object( newpath ); ++ ++ /* oldpath is a directory, newpath is a directory, ReplaceIfExists = TRUE, target file opened */ ++ res = GetTempFileNameW( tmp_path, foo, 0, oldpath ); ++ ok( res != 0, "fail to create temp file\n" ); ++ DeleteFileW( oldpath ); ++ success = CreateDirectoryW( oldpath, NULL ); ++ ok( success != 0, "failed to create temp directory\n" ); ++ handle = CreateFileW( oldpath, GENERIC_WRITE | DELETE, 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0 ); ++ ok( handle != INVALID_HANDLE_VALUE, "CreateFileW failed\n" ); ++ ++ res = GetTempFileNameW( tmp_path, foo, 0, newpath ); ++ ok( res != 0, "fail to create temp file\n" ); ++ DeleteFileW( newpath ); ++ success = CreateDirectoryW( newpath, NULL ); ++ ok( success != 0, "failed to create temp directory\n" ); ++ handle2 = CreateFileW( newpath, GENERIC_WRITE | DELETE, 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0 ); ++ ok( handle2 != INVALID_HANDLE_VALUE, "CreateFileW failed\n" ); ++ ++ pRtlDosPathNameToNtPathName_U( newpath, &name_str, NULL, NULL ); ++ pfli = HeapAlloc( GetProcessHeap(), 0, sizeof(FILE_LINK_INFORMATION) + name_str.Length ); ++ pfli->ReplaceIfExists = TRUE; ++ pfli->RootDirectory = NULL; ++ pfli->FileNameLength = name_str.Length; ++ memcpy( pfli->FileName, name_str.Buffer, name_str.Length ); ++ pRtlFreeUnicodeString( &name_str ); ++ ++ U(io).Status = 0xdeadbeef; ++ res = pNtSetInformationFile( handle, &io, pfli, sizeof(FILE_LINK_INFORMATION) + pfli->FileNameLength, FileLinkInformation ); ++ todo_wine ok( U(io).Status == 0xdeadbeef, "io.Status expect 0xdeadbeef, io.Status is %x\n", U(io).Status ); ++ todo_wine ok( res == STATUS_FILE_IS_A_DIRECTORY, "res expect STATUS_FILE_IS_A_DIRECTORY, res is %x\n", res ); ++ fileDeleted = GetFileAttributesW( oldpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; ++ ok( !fileDeleted, "File should exist\n" ); ++ fileDeleted = GetFileAttributesW( newpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; ++ ok( !fileDeleted, "File should exist\n" ); ++ ++ CloseHandle( handle ); ++ CloseHandle( handle2 ); ++ HeapFree( GetProcessHeap(), 0, pfli ); ++ delete_object( oldpath ); ++ delete_object( newpath ); ++ ++ /* oldpath is a file, newpath is a directory, ReplaceIfExists = FALSE */ ++ res = GetTempFileNameW( tmp_path, foo, 0, oldpath ); ++ ok( res != 0, "fail to create temp file\n" ); ++ handle = CreateFileW( oldpath, GENERIC_WRITE | DELETE, 0, NULL, CREATE_ALWAYS, 0, 0 ); ++ ok( handle != INVALID_HANDLE_VALUE, "CreateFileW failed\n" ); ++ ++ res = GetTempFileNameW( tmp_path, foo, 0, newpath ); ++ ok( res != 0, "fail to create temp file\n" ); ++ DeleteFileW( newpath ); ++ success = CreateDirectoryW( newpath, NULL ); ++ ok( success != 0, "failed to create temp directory\n" ); ++ pRtlDosPathNameToNtPathName_U( newpath, &name_str, NULL, NULL ); ++ pfli = HeapAlloc( GetProcessHeap(), 0, sizeof(FILE_LINK_INFORMATION) + name_str.Length ); ++ pfli->ReplaceIfExists = FALSE; ++ pfli->RootDirectory = NULL; ++ pfli->FileNameLength = name_str.Length; ++ memcpy( pfli->FileName, name_str.Buffer, name_str.Length ); ++ pRtlFreeUnicodeString( &name_str ); ++ ++ U(io).Status = 0xdeadbeef; ++ res = pNtSetInformationFile( handle, &io, pfli, sizeof(FILE_LINK_INFORMATION) + pfli->FileNameLength, FileLinkInformation ); ++ todo_wine ok( U(io).Status == 0xdeadbeef, "shouldn't be able to set FileLinkInformation, io.Status is %x\n", U(io).Status ); ++ todo_wine ok( res == STATUS_OBJECT_NAME_COLLISION, "expect STATUS_OBJECT_NAME_COLLISION, res is %x\n", res ); ++ fileDeleted = GetFileAttributesW( oldpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; ++ ok( !fileDeleted, "File should exist\n" ); ++ fileDeleted = GetFileAttributesW( newpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; ++ ok( !fileDeleted, "File should exist\n" ); ++ ++ CloseHandle( handle ); ++ HeapFree( GetProcessHeap(), 0, pfli ); ++ delete_object( oldpath ); ++ delete_object( newpath ); ++ ++ /* oldpath is a file, newpath is a directory, ReplaceIfExists = TRUE */ ++ res = GetTempFileNameW( tmp_path, foo, 0, oldpath ); ++ ok( res != 0, "fail to create temp file\n" ); ++ handle = CreateFileW( oldpath, GENERIC_WRITE | DELETE, 0, NULL, CREATE_ALWAYS, 0, 0 ); ++ ok( handle != INVALID_HANDLE_VALUE, "CreateFileW failed\n" ); ++ ++ res = GetTempFileNameW( tmp_path, foo, 0, newpath ); ++ ok( res != 0, "fail to create temp file\n" ); ++ DeleteFileW( newpath ); ++ success = CreateDirectoryW( newpath, NULL ); ++ ok( success != 0, "failed to create temp directory\n" ); ++ pRtlDosPathNameToNtPathName_U( newpath, &name_str, NULL, NULL ); ++ pfli = HeapAlloc( GetProcessHeap(), 0, sizeof(FILE_LINK_INFORMATION) + name_str.Length ); ++ pfli->ReplaceIfExists = TRUE; ++ pfli->RootDirectory = NULL; ++ pfli->FileNameLength = name_str.Length; ++ memcpy( pfli->FileName, name_str.Buffer, name_str.Length ); ++ pRtlFreeUnicodeString( &name_str ); ++ ++ U(io).Status = 0xdeadbeef; ++ res = pNtSetInformationFile( handle, &io, pfli, sizeof(FILE_LINK_INFORMATION) + pfli->FileNameLength, FileLinkInformation ); ++ todo_wine ok( U(io).Status == 0xdeadbeef, "io.Status expect 0xdeadbeef, io.Status is %x\n", U(io).Status ); ++ todo_wine ok( res == STATUS_ACCESS_DENIED, "res expect STATUS_ACCESS_DENIED, res is %x\n", res ); ++ fileDeleted = GetFileAttributesW( oldpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; ++ ok( !fileDeleted, "File should exist\n" ); ++ fileDeleted = GetFileAttributesW( newpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; ++ ok( !fileDeleted, "File should exist\n" ); ++ ++ CloseHandle( handle ); ++ HeapFree( GetProcessHeap(), 0, pfli ); ++ delete_object( oldpath ); ++ delete_object( newpath ); ++ ++ /* oldpath is a file, newpath doesn't exist, test with RootDirectory != NULL */ ++ res = GetTempFileNameW( tmp_path, foo, 0, oldpath ); ++ ok( res != 0, "fail to create temp file\n" ); ++ handle = CreateFileW( oldpath, GENERIC_WRITE | DELETE, 0, NULL, CREATE_ALWAYS, 0, 0 ); ++ ok( handle != INVALID_HANDLE_VALUE, "CreateFileW failed\n" ); ++ ++ res = GetTempFileNameW( tmp_path, foo, 0, newpath ); ++ ok( res != 0, "fail to create temp file\n" ); ++ DeleteFileW( newpath ); ++ for (filename = newpath, p = newpath; *p; p++) ++ if (*p == '\\') filename = p + 1; ++ handle2 = CreateFileW( tmp_path, 0, 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0 ); ++ ok( handle2 != INVALID_HANDLE_VALUE, "CreateFileW failed\n" ); ++ ++ pfli = HeapAlloc( GetProcessHeap(), 0, sizeof(FILE_LINK_INFORMATION) + lstrlenW(filename) * sizeof(WCHAR) ); ++ pfli->ReplaceIfExists = FALSE; ++ pfli->RootDirectory = handle2; ++ pfli->FileNameLength = lstrlenW(filename) * sizeof(WCHAR); ++ memcpy( pfli->FileName, filename, pfli->FileNameLength ); ++ ++ U(io).Status = 0xdeadbeef; ++ res = pNtSetInformationFile( handle, &io, pfli, sizeof(FILE_LINK_INFORMATION) + pfli->FileNameLength, FileLinkInformation ); ++ todo_wine ok( U(io).Status == STATUS_SUCCESS, "io.Status expected STATUS_SUCCESS, got %x\n", U(io).Status ); ++ todo_wine ok( res == STATUS_SUCCESS, "res expected STATUS_SUCCESS, got %x\n", res ); ++ fileDeleted = GetFileAttributesW( oldpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; ++ ok( !fileDeleted, "File should exist\n" ); ++ fileDeleted = GetFileAttributesW( newpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; ++ todo_wine ok( !fileDeleted, "File should exist\n" ); ++ ++ pfni = HeapAlloc( GetProcessHeap(), 0, sizeof(FILE_NAME_INFORMATION) + MAX_PATH * sizeof(WCHAR) ); ++ res = pNtQueryInformationFile( handle, &io, pfni, sizeof(FILE_NAME_INFORMATION) + MAX_PATH * sizeof(WCHAR), FileNameInformation ); ++ ok( res == STATUS_SUCCESS, "res expected STATUS_SUCCESS, got %x\n", res ); ++ pfni->FileName[ pfni->FileNameLength / sizeof(WCHAR) ] = 0; ++ ok( !lstrcmpW(pfni->FileName, oldpath + 2), "FileName expected %s, got %s\n", ++ wine_dbgstr_w(oldpath + 2), wine_dbgstr_w(pfni->FileName) ); ++ HeapFree( GetProcessHeap(), 0, pfni ); ++ ++ CloseHandle( handle ); ++ CloseHandle( handle2 ); ++ HeapFree( GetProcessHeap(), 0, pfli ); ++ delete_object( oldpath ); ++ delete_object( newpath ); ++} ++ + static void test_file_both_information(void) + { + IO_STATUS_BLOCK io; +@@ -3716,6 +4413,7 @@ START_TEST(file) + test_file_full_size_information(); + test_file_all_name_information(); + test_file_rename_information(); ++ test_file_link_information(); + test_file_disposition_information(); + test_query_volume_information_file(); + test_query_attribute_information_file(); +-- +2.4.2 + diff --git a/patches/ntdll-FileDispositionInformation/0017-server-Implement-support-for-FileLinkInformation-cla.patch b/patches/ntdll-FileDispositionInformation/0017-server-Implement-support-for-FileLinkInformation-cla.patch new file mode 100644 index 00000000..c1fbc70b --- /dev/null +++ b/patches/ntdll-FileDispositionInformation/0017-server-Implement-support-for-FileLinkInformation-cla.patch @@ -0,0 +1,379 @@ +From eb0a9708ab107897cb41a20299f530a8d7bddb4c Mon Sep 17 00:00:00 2001 +From: Sebastian Lackner +Date: Wed, 3 Jun 2015 17:04:23 +0200 +Subject: server: Implement support for FileLinkInformation class in + NtSetInformationFile. + +--- + dlls/ntdll/file.c | 42 ++++++++++++++++++++++++++ + dlls/ntdll/tests/file.c | 54 ++++++++++++++++----------------- + server/fd.c | 79 ++++++++++++++++++++++++++++++++++++++++++++++++- + server/protocol.def | 8 +++++ + 4 files changed, 155 insertions(+), 28 deletions(-) + +diff --git a/dlls/ntdll/file.c b/dlls/ntdll/file.c +index 6aa46f4..1e27afb 100644 +--- a/dlls/ntdll/file.c ++++ b/dlls/ntdll/file.c +@@ -2823,6 +2823,48 @@ NTSTATUS WINAPI NtSetInformationFile(HANDLE handle, PIO_STATUS_BLOCK io, + else io->u.Status = STATUS_INVALID_PARAMETER_3; + break; + ++ case FileLinkInformation: ++ if (len >= sizeof(FILE_LINK_INFORMATION)) ++ { ++ FILE_LINK_INFORMATION *info = ptr; ++ UNICODE_STRING name_str; ++ OBJECT_ATTRIBUTES attr; ++ ANSI_STRING unix_name; ++ ++ name_str.Buffer = info->FileName; ++ name_str.Length = info->FileNameLength; ++ name_str.MaximumLength = info->FileNameLength + sizeof(WCHAR); ++ ++ attr.Length = sizeof(attr); ++ attr.ObjectName = &name_str; ++ attr.RootDirectory = info->RootDirectory; ++ attr.Attributes = OBJ_CASE_INSENSITIVE; ++ ++ io->u.Status = nt_to_unix_file_name_attr( &attr, &unix_name, FILE_OPEN_IF ); ++ if (io->u.Status != STATUS_SUCCESS && io->u.Status != STATUS_NO_SUCH_FILE) ++ break; ++ ++ if (!info->ReplaceIfExists && io->u.Status == STATUS_SUCCESS) ++ { ++ RtlFreeAnsiString( &unix_name ); ++ io->u.Status = STATUS_OBJECT_NAME_COLLISION; ++ break; ++ } ++ ++ SERVER_START_REQ( link_file ) ++ { ++ req->handle = wine_server_obj_handle( handle ); ++ req->rootdir = wine_server_obj_handle( attr.RootDirectory ); ++ wine_server_add_data( req, unix_name.Buffer, unix_name.Length ); ++ io->u.Status = wine_server_call( req ); ++ } ++ SERVER_END_REQ; ++ ++ RtlFreeAnsiString( &unix_name ); ++ } ++ else io->u.Status = STATUS_INVALID_PARAMETER_3; ++ break; ++ + default: + FIXME("Unsupported class (%d)\n", class); + io->u.Status = STATUS_NOT_IMPLEMENTED; +diff --git a/dlls/ntdll/tests/file.c b/dlls/ntdll/tests/file.c +index c985a03..92613d5 100644 +--- a/dlls/ntdll/tests/file.c ++++ b/dlls/ntdll/tests/file.c +@@ -2183,12 +2183,12 @@ static void test_file_link_information(void) + + U(io).Status = 0xdeadbeef; + res = pNtSetInformationFile( handle, &io, pfli, sizeof(FILE_LINK_INFORMATION) + pfli->FileNameLength, FileLinkInformation ); +- todo_wine ok( U(io).Status == STATUS_SUCCESS, "io.Status expected STATUS_SUCCESS, got %x\n", U(io).Status ); +- todo_wine ok( res == STATUS_SUCCESS, "res expected STATUS_SUCCESS, got %x\n", res ); ++ ok( U(io).Status == STATUS_SUCCESS, "io.Status expected STATUS_SUCCESS, got %x\n", U(io).Status ); ++ ok( res == STATUS_SUCCESS, "res expected STATUS_SUCCESS, got %x\n", res ); + fileDeleted = GetFileAttributesW( oldpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; + ok( !fileDeleted, "File should exist\n" ); + fileDeleted = GetFileAttributesW( newpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; +- todo_wine ok( !fileDeleted, "File should exist\n" ); ++ ok( !fileDeleted, "File should exist\n" ); + + pfni = HeapAlloc( GetProcessHeap(), 0, sizeof(FILE_NAME_INFORMATION) + MAX_PATH * sizeof(WCHAR) ); + res = pNtQueryInformationFile( handle, &io, pfni, sizeof(FILE_NAME_INFORMATION) + MAX_PATH * sizeof(WCHAR), FileNameInformation ); +@@ -2222,7 +2222,7 @@ static void test_file_link_information(void) + U(io).Status = 0xdeadbeef; + res = pNtSetInformationFile( handle, &io, pfli, sizeof(FILE_LINK_INFORMATION) + pfli->FileNameLength, FileLinkInformation ); + todo_wine ok( U(io).Status == 0xdeadbeef, "shouldn't be able to set FileLinkInformation, io.Status is %x\n", U(io).Status ); +- todo_wine ok( res == STATUS_OBJECT_NAME_COLLISION, "expect STATUS_OBJECT_NAME_COLLISION, res is %x\n", res ); ++ ok( res == STATUS_OBJECT_NAME_COLLISION, "expect STATUS_OBJECT_NAME_COLLISION, res is %x\n", res ); + fileDeleted = GetFileAttributesW( oldpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; + ok( !fileDeleted, "File should exist\n" ); + fileDeleted = GetFileAttributesW( newpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; +@@ -2255,7 +2255,7 @@ static void test_file_link_information(void) + U(io).Status = 0xdeadbeef; + res = pNtSetInformationFile( handle, &io, pfli, sizeof(FILE_LINK_INFORMATION) + pfli->FileNameLength, FileLinkInformation ); + todo_wine ok( U(io).Status == 0xdeadbeef, "io.Status expect 0xdeadbeef, io.Status is %x\n", U(io).Status ); +- todo_wine ok( res == STATUS_OBJECT_NAME_COLLISION, "res expect STATUS_OBJECT_NAME_COLLISION, res is %x\n", res ); ++ ok( res == STATUS_OBJECT_NAME_COLLISION, "res expect STATUS_OBJECT_NAME_COLLISION, res is %x\n", res ); + fileDeleted = GetFileAttributesW( oldpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; + ok( !fileDeleted, "File should exist\n" ); + fileDeleted = GetFileAttributesW( newpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; +@@ -2285,8 +2285,8 @@ static void test_file_link_information(void) + + U(io).Status = 0xdeadbeef; + res = pNtSetInformationFile( handle, &io, pfli, sizeof(FILE_LINK_INFORMATION) + pfli->FileNameLength, FileLinkInformation ); +- todo_wine ok( U(io).Status == STATUS_SUCCESS, "io.Status expect STATUS_SUCCESS, io.Status is %x\n", U(io).Status ); +- todo_wine ok( res == STATUS_SUCCESS, "res expect STATUS_SUCCESS, res is %x\n", res ); ++ ok( U(io).Status == STATUS_SUCCESS, "io.Status expect STATUS_SUCCESS, io.Status is %x\n", U(io).Status ); ++ ok( res == STATUS_SUCCESS, "res expect STATUS_SUCCESS, res is %x\n", res ); + fileDeleted = GetFileAttributesW( oldpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; + ok( !fileDeleted, "File should exist\n" ); + fileDeleted = GetFileAttributesW( newpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; +@@ -2319,7 +2319,7 @@ static void test_file_link_information(void) + U(io).Status = 0xdeadbeef; + res = pNtSetInformationFile( handle, &io, pfli, sizeof(FILE_LINK_INFORMATION) + pfli->FileNameLength, FileLinkInformation ); + todo_wine ok( U(io).Status == 0xdeadbeef, "io.Status expect 0xdeadbeef, io.Status is %x\n", U(io).Status ); +- todo_wine ok( res == STATUS_ACCESS_DENIED, "res expect STATUS_ACCESS_DENIED, res is %x\n", res ); ++ ok( res == STATUS_ACCESS_DENIED, "res expect STATUS_ACCESS_DENIED, res is %x\n", res ); + fileDeleted = GetFileAttributesW( oldpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; + ok( !fileDeleted, "File should exist\n" ); + fileDeleted = GetFileAttributesW( newpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; +@@ -2353,7 +2353,7 @@ static void test_file_link_information(void) + U(io).Status = 0xdeadbeef; + res = pNtSetInformationFile( handle, &io, pfli, sizeof(FILE_LINK_INFORMATION) + pfli->FileNameLength, FileLinkInformation ); + todo_wine ok( U(io).Status == 0xdeadbeef, "io.Status expect 0xdeadbeef, io.Status is %x\n", U(io).Status ); +- todo_wine ok( res == STATUS_ACCESS_DENIED, "res expect STATUS_ACCESS_DENIED, res is %x\n", res ); ++ ok( res == STATUS_ACCESS_DENIED, "res expect STATUS_ACCESS_DENIED, res is %x\n", res ); + fileDeleted = GetFileAttributesW( oldpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; + ok( !fileDeleted, "File should exist\n" ); + fileDeleted = GetFileAttributesW( newpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; +@@ -2387,7 +2387,7 @@ static void test_file_link_information(void) + U(io).Status = 0xdeadbeef; + res = pNtSetInformationFile( handle, &io, pfli, sizeof(FILE_LINK_INFORMATION) + pfli->FileNameLength, FileLinkInformation ); + todo_wine ok( U(io).Status == 0xdeadbeef, "io.Status expect 0xdeadbeef, io.Status is %x\n", U(io).Status ); +- todo_wine ok( res == STATUS_ACCESS_DENIED, "res expect STATUS_ACCESS_DENIED, res is %x\n", res ); ++ ok( res == STATUS_ACCESS_DENIED, "res expect STATUS_ACCESS_DENIED, res is %x\n", res ); + fileDeleted = GetFileAttributesW( oldpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; + ok( !fileDeleted, "File should exist\n" ); + fileDeleted = GetFileAttributesW( newpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; +@@ -2422,7 +2422,7 @@ static void test_file_link_information(void) + U(io).Status = 0xdeadbeef; + res = pNtSetInformationFile( handle, &io, pfli, sizeof(FILE_LINK_INFORMATION) + pfli->FileNameLength, FileLinkInformation ); + todo_wine ok( U(io).Status == 0xdeadbeef , "io.Status expected 0xdeadbeef, got %x\n", U(io).Status ); +- todo_wine ok( res == STATUS_FILE_IS_A_DIRECTORY, "res expected STATUS_FILE_IS_A_DIRECTORY, got %x\n", res ); ++ ok( res == STATUS_FILE_IS_A_DIRECTORY, "res expected STATUS_FILE_IS_A_DIRECTORY, got %x\n", res ); + fileDeleted = GetFileAttributesW( oldpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; + ok( !fileDeleted, "File should exist\n" ); + fileDeleted = GetFileAttributesW( newpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; +@@ -2469,7 +2469,7 @@ static void test_file_link_information(void) + U(io).Status = 0xdeadbeef; + res = pNtSetInformationFile( handle, &io, pfli, sizeof(FILE_LINK_INFORMATION) + pfli->FileNameLength, FileLinkInformation ); + todo_wine ok( U(io).Status == 0xdeadbeef, "io.Status expected 0xdeadbeef, got %x\n", U(io).Status ); +- todo_wine ok( res == STATUS_FILE_IS_A_DIRECTORY, "res expected STATUS_FILE_IS_A_DIRECTORY, got %x\n", res ); ++ ok( res == STATUS_FILE_IS_A_DIRECTORY, "res expected STATUS_FILE_IS_A_DIRECTORY, got %x\n", res ); + fileDeleted = GetFileAttributesW( oldpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; + ok( !fileDeleted, "File should exist\n" ); + fileDeleted = GetFileAttributesW( newpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; +@@ -2503,8 +2503,8 @@ static void test_file_link_information(void) + U(io).Status = 0xdeadbeef; + res = pNtSetInformationFile( handle, &io, pfli, sizeof(FILE_LINK_INFORMATION) + pfli->FileNameLength, FileLinkInformation ); + todo_wine ok( U(io).Status == 0xdeadbeef, "shouldn't be able to set FileLinkInformation, io.Status is %x\n", U(io).Status ); +- todo_wine ok( res == STATUS_OBJECT_NAME_COLLISION || broken(res == STATUS_FILE_IS_A_DIRECTORY) /* > Win XP */, +- "expect STATUS_OBJECT_NAME_COLLISION or STATUS_FILE_IS_A_DIRECTORY, res is %x\n", res ); ++ ok( res == STATUS_OBJECT_NAME_COLLISION || broken(res == STATUS_FILE_IS_A_DIRECTORY) /* > Win XP */, ++ "expect STATUS_OBJECT_NAME_COLLISION or STATUS_FILE_IS_A_DIRECTORY, res is %x\n", res ); + fileDeleted = GetFileAttributesW( oldpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; + ok( !fileDeleted, "File should exist\n" ); + fileDeleted = GetFileAttributesW( newpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; +@@ -2540,8 +2540,8 @@ static void test_file_link_information(void) + U(io).Status = 0xdeadbeef; + res = pNtSetInformationFile( handle, &io, pfli, sizeof(FILE_LINK_INFORMATION) + pfli->FileNameLength, FileLinkInformation ); + todo_wine ok( U(io).Status == 0xdeadbeef, "shouldn't be able to set FileLinkInformation, io.Status is %x\n", U(io).Status ); +- todo_wine ok( res == STATUS_OBJECT_NAME_COLLISION || broken(res == STATUS_FILE_IS_A_DIRECTORY) /* > Win XP */, +- "expect STATUS_OBJECT_NAME_COLLISION or STATUS_FILE_IS_A_DIRECTORY, res is %x\n", res ); ++ ok( res == STATUS_OBJECT_NAME_COLLISION || broken(res == STATUS_FILE_IS_A_DIRECTORY) /* > Win XP */, ++ "expect STATUS_OBJECT_NAME_COLLISION or STATUS_FILE_IS_A_DIRECTORY, res is %x\n", res ); + fileDeleted = GetFileAttributesW( oldpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; + ok( !fileDeleted, "File should exist\n" ); + fileDeleted = GetFileAttributesW( newpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; +@@ -2575,7 +2575,7 @@ static void test_file_link_information(void) + U(io).Status = 0xdeadbeef; + res = pNtSetInformationFile( handle, &io, pfli, sizeof(FILE_LINK_INFORMATION) + pfli->FileNameLength, FileLinkInformation ); + todo_wine ok( U(io).Status == 0xdeadbeef, "io.Status expect 0xdeadbeef, io.Status is %x\n", U(io).Status ); +- todo_wine ok( res == STATUS_FILE_IS_A_DIRECTORY, "res expect STATUS_FILE_IS_A_DIRECTORY, res is %x\n", res ); ++ ok( res == STATUS_FILE_IS_A_DIRECTORY, "res expect STATUS_FILE_IS_A_DIRECTORY, res is %x\n", res ); + fileDeleted = GetFileAttributesW( oldpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; + ok( !fileDeleted, "File should exist\n" ); + fileDeleted = GetFileAttributesW( newpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; +@@ -2611,7 +2611,7 @@ static void test_file_link_information(void) + U(io).Status = 0xdeadbeef; + res = pNtSetInformationFile( handle, &io, pfli, sizeof(FILE_LINK_INFORMATION) + pfli->FileNameLength, FileLinkInformation ); + todo_wine ok( U(io).Status == 0xdeadbeef, "io.Status expect 0xdeadbeef, io.Status is %x\n", U(io).Status ); +- todo_wine ok( res == STATUS_FILE_IS_A_DIRECTORY, "res expect STATUS_FILE_IS_A_DIRECTORY, res is %x\n", res ); ++ ok( res == STATUS_FILE_IS_A_DIRECTORY, "res expect STATUS_FILE_IS_A_DIRECTORY, res is %x\n", res ); + fileDeleted = GetFileAttributesW( oldpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; + ok( !fileDeleted, "File should exist\n" ); + fileDeleted = GetFileAttributesW( newpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; +@@ -2648,8 +2648,8 @@ static void test_file_link_information(void) + U(io).Status = 0xdeadbeef; + res = pNtSetInformationFile( handle, &io, pfli, sizeof(FILE_LINK_INFORMATION) + pfli->FileNameLength, FileLinkInformation ); + todo_wine ok( U(io).Status == 0xdeadbeef, "shouldn't be able to set FileLinkInformation, io.Status is %x\n", U(io).Status ); +- todo_wine ok( res == STATUS_OBJECT_NAME_COLLISION || broken(res == STATUS_FILE_IS_A_DIRECTORY) /* > Win XP */, +- "expect STATUS_OBJECT_NAME_COLLISION or STATUS_FILE_IS_A_DIRECTORY, res is %x\n", res ); ++ ok( res == STATUS_OBJECT_NAME_COLLISION || broken(res == STATUS_FILE_IS_A_DIRECTORY) /* > Win XP */, ++ "expect STATUS_OBJECT_NAME_COLLISION or STATUS_FILE_IS_A_DIRECTORY, res is %x\n", res ); + fileDeleted = GetFileAttributesW( oldpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; + ok( !fileDeleted, "File should exist\n" ); + fileDeleted = GetFileAttributesW( newpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; +@@ -2685,7 +2685,7 @@ static void test_file_link_information(void) + U(io).Status = 0xdeadbeef; + res = pNtSetInformationFile( handle, &io, pfli, sizeof(FILE_LINK_INFORMATION) + pfli->FileNameLength, FileLinkInformation ); + todo_wine ok( U(io).Status == 0xdeadbeef, "io.Status expect 0xdeadbeef, io.Status is %x\n", U(io).Status ); +- todo_wine ok( res == STATUS_FILE_IS_A_DIRECTORY, "res expect STATUS_FILE_IS_A_DIRECTORY, res is %x\n", res ); ++ ok( res == STATUS_FILE_IS_A_DIRECTORY, "res expect STATUS_FILE_IS_A_DIRECTORY, res is %x\n", res ); + fileDeleted = GetFileAttributesW( oldpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; + ok( !fileDeleted, "File should exist\n" ); + fileDeleted = GetFileAttributesW( newpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; +@@ -2724,7 +2724,7 @@ static void test_file_link_information(void) + U(io).Status = 0xdeadbeef; + res = pNtSetInformationFile( handle, &io, pfli, sizeof(FILE_LINK_INFORMATION) + pfli->FileNameLength, FileLinkInformation ); + todo_wine ok( U(io).Status == 0xdeadbeef, "io.Status expect 0xdeadbeef, io.Status is %x\n", U(io).Status ); +- todo_wine ok( res == STATUS_FILE_IS_A_DIRECTORY, "res expect STATUS_FILE_IS_A_DIRECTORY, res is %x\n", res ); ++ ok( res == STATUS_FILE_IS_A_DIRECTORY, "res expect STATUS_FILE_IS_A_DIRECTORY, res is %x\n", res ); + fileDeleted = GetFileAttributesW( oldpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; + ok( !fileDeleted, "File should exist\n" ); + fileDeleted = GetFileAttributesW( newpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; +@@ -2758,7 +2758,7 @@ static void test_file_link_information(void) + U(io).Status = 0xdeadbeef; + res = pNtSetInformationFile( handle, &io, pfli, sizeof(FILE_LINK_INFORMATION) + pfli->FileNameLength, FileLinkInformation ); + todo_wine ok( U(io).Status == 0xdeadbeef, "shouldn't be able to set FileLinkInformation, io.Status is %x\n", U(io).Status ); +- todo_wine ok( res == STATUS_OBJECT_NAME_COLLISION, "expect STATUS_OBJECT_NAME_COLLISION, res is %x\n", res ); ++ ok( res == STATUS_OBJECT_NAME_COLLISION, "expect STATUS_OBJECT_NAME_COLLISION, res is %x\n", res ); + fileDeleted = GetFileAttributesW( oldpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; + ok( !fileDeleted, "File should exist\n" ); + fileDeleted = GetFileAttributesW( newpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; +@@ -2791,7 +2791,7 @@ static void test_file_link_information(void) + U(io).Status = 0xdeadbeef; + res = pNtSetInformationFile( handle, &io, pfli, sizeof(FILE_LINK_INFORMATION) + pfli->FileNameLength, FileLinkInformation ); + todo_wine ok( U(io).Status == 0xdeadbeef, "io.Status expect 0xdeadbeef, io.Status is %x\n", U(io).Status ); +- todo_wine ok( res == STATUS_ACCESS_DENIED, "res expect STATUS_ACCESS_DENIED, res is %x\n", res ); ++ ok( res == STATUS_ACCESS_DENIED, "res expect STATUS_ACCESS_DENIED, res is %x\n", res ); + fileDeleted = GetFileAttributesW( oldpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; + ok( !fileDeleted, "File should exist\n" ); + fileDeleted = GetFileAttributesW( newpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; +@@ -2824,12 +2824,12 @@ static void test_file_link_information(void) + + U(io).Status = 0xdeadbeef; + res = pNtSetInformationFile( handle, &io, pfli, sizeof(FILE_LINK_INFORMATION) + pfli->FileNameLength, FileLinkInformation ); +- todo_wine ok( U(io).Status == STATUS_SUCCESS, "io.Status expected STATUS_SUCCESS, got %x\n", U(io).Status ); +- todo_wine ok( res == STATUS_SUCCESS, "res expected STATUS_SUCCESS, got %x\n", res ); ++ ok( U(io).Status == STATUS_SUCCESS, "io.Status expected STATUS_SUCCESS, got %x\n", U(io).Status ); ++ ok( res == STATUS_SUCCESS, "res expected STATUS_SUCCESS, got %x\n", res ); + fileDeleted = GetFileAttributesW( oldpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; + ok( !fileDeleted, "File should exist\n" ); + fileDeleted = GetFileAttributesW( newpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; +- todo_wine ok( !fileDeleted, "File should exist\n" ); ++ ok( !fileDeleted, "File should exist\n" ); + + pfni = HeapAlloc( GetProcessHeap(), 0, sizeof(FILE_NAME_INFORMATION) + MAX_PATH * sizeof(WCHAR) ); + res = pNtQueryInformationFile( handle, &io, pfni, sizeof(FILE_NAME_INFORMATION) + MAX_PATH * sizeof(WCHAR), FileNameInformation ); +diff --git a/server/fd.c b/server/fd.c +index 0234fc0..4729170 100644 +--- a/server/fd.c ++++ b/server/fd.c +@@ -2561,7 +2561,6 @@ DECL_HANDLER(rename_file) + goto out; + } + } +- + if (rename( fd->unix_name, path )) + file_set_error(); + else +@@ -2578,3 +2577,81 @@ out: + + free( path ); + } ++ ++/* create hardlink - FIXME: merge with rename_file */ ++DECL_HANDLER(link_file) ++{ ++ struct fd *fd; ++ char *path; ++ ++ if (!(path = mem_alloc( get_req_data_size() + 1 ))) ++ return; ++ memcpy( path, get_req_data(), get_req_data_size() ); ++ path[ get_req_data_size() ] = 0; ++ ++ /* handle rootdir */ ++ if (req->rootdir) ++ { ++ char *combined_path = NULL; ++ struct dir *root; ++ if ((root = get_dir_obj( current->process, req->rootdir, 0 ))) ++ { ++ struct fd *root_fd; ++ if ((root_fd = get_obj_fd( (struct object *)root ))) ++ { ++ combined_path = dup_fd_name( root_fd, path ); ++ release_object( root_fd ); ++ } ++ release_object( root ); ++ } ++ ++ if (!combined_path) return; ++ free( path ); ++ path = combined_path; ++ } ++ ++ /* create a hardlink */ ++ if ((fd = get_handle_fd_obj( current->process, req->handle, 0 ))) ++ { ++ if (fd->unix_name) ++ { ++ struct stat st; ++ if (fd->unix_fd != -1 && !fstat( fd->unix_fd, &st ) && S_ISDIR( st.st_mode )) ++ { ++ set_error( STATUS_FILE_IS_A_DIRECTORY ); ++ goto out; ++ } ++ if (!stat( path, &st )) ++ { ++ struct inode *inode; ++ if (!S_ISREG( st.st_mode )) ++ { ++ set_error( STATUS_ACCESS_DENIED ); ++ goto out; ++ } ++ if ((inode = get_inode( st.st_dev, st.st_ino, -1 ))) ++ { ++ int is_empty = list_empty( &inode->open ); ++ release_object( inode ); ++ if (!is_empty) ++ { ++ set_error( STATUS_ACCESS_DENIED ); ++ goto out; ++ } ++ } ++ if (unlink( path )) ++ { ++ file_set_error(); ++ goto out; ++ } ++ } ++ if (link( fd->unix_name, path )) ++ file_set_error(); ++ } ++ else set_error( STATUS_OBJECT_TYPE_MISMATCH ); ++out: ++ release_object( fd ); ++ } ++ ++ free( path ); ++} +diff --git a/server/protocol.def b/server/protocol.def +index 431d2b9..1b7814b 100644 +--- a/server/protocol.def ++++ b/server/protocol.def +@@ -1215,6 +1215,14 @@ enum server_fd_type + @END + + ++/* Create a hardlink */ ++@REQ(link_file) ++ obj_handle_t handle; /* handle to the file */ ++ obj_handle_t rootdir; /* root directory */ ++ VARARG(unix_name,string); /* file name */ ++@END ++ ++ + /* Create a socket */ + @REQ(create_socket) + unsigned int access; /* wanted access rights */ +-- +2.4.2 + diff --git a/patches/ntdll-FileDispositionInformation/definition b/patches/ntdll-FileDispositionInformation/definition index 509957e3..9f3937ce 100644 --- a/patches/ntdll-FileDispositionInformation/definition +++ b/patches/ntdll-FileDispositionInformation/definition @@ -1,3 +1,4 @@ Fixes: [30397] Support for NtSetInformationFile class FileDispositionInformation Fixes: [30399] Support for NtSetInformationFile class FileRenameInformation +Fixes: Support for NtSetInformationFile class FileLinkInformation Depends: server-File_Permissions diff --git a/patches/patchinstall.sh b/patches/patchinstall.sh index 9c749fc5..c134da96 100755 --- a/patches/patchinstall.sh +++ b/patches/patchinstall.sh @@ -3025,7 +3025,8 @@ fi # | * [#30399] Support for NtSetInformationFile class FileRenameInformation # | # | Modified files: -# | * dlls/ntdll/file.c, dlls/ntdll/tests/file.c, server/fd.c, server/file.c, server/file.h, server/protocol.def +# | * dlls/ntdll/file.c, dlls/ntdll/tests/file.c, include/winternl.h, server/fd.c, server/file.c, server/file.h, +# | server/protocol.def # | if test "$enable_ntdll_FileDispositionInformation" -eq 1; then patch_apply ntdll-FileDispositionInformation/0001-server-Keep-a-pointer-to-parent-s-fd-unix_name-in-th.patch @@ -3042,6 +3043,9 @@ if test "$enable_ntdll_FileDispositionInformation" -eq 1; then patch_apply ntdll-FileDispositionInformation/0012-server-When-combining-root-and-name-make-sure-there-.patch patch_apply ntdll-FileDispositionInformation/0013-server-Reject-rename-when-target-has-opened-file-han.patch patch_apply ntdll-FileDispositionInformation/0014-server-Manually-unlink-dest-when-trying-to-replace-a.patch + patch_apply ntdll-FileDispositionInformation/0015-include-Add-declaration-for-FILE_LINK_INFORMATION.patch + patch_apply ntdll-FileDispositionInformation/0016-ntdll-tests-Add-tests-for-FileLinkInformation-class.patch + patch_apply ntdll-FileDispositionInformation/0017-server-Implement-support-for-FileLinkInformation-cla.patch ( echo '+ { "Dmitry Timoshkov", "server: Keep a pointer to parent'\''s fd unix_name in the closed_fd structure.", 1 },'; echo '+ { "Dmitry Timoshkov", "server: Add support for setting file disposition information.", 1 },'; @@ -3057,6 +3061,9 @@ if test "$enable_ntdll_FileDispositionInformation" -eq 1; then echo '+ { "Sebastian Lackner", "server: When combining root and name, make sure there is only one slash.", 2 },'; echo '+ { "Sebastian Lackner", "server: Reject rename when target has opened file handles.", 1 },'; echo '+ { "Sebastian Lackner", "server: Manually unlink dest when trying to replace a file with directory.", 1 },'; + echo '+ { "Zhaonan Liang", "include: Add declaration for FILE_LINK_INFORMATION.", 1 },'; + echo '+ { "Qian Hong", "ntdll/tests: Add tests for FileLinkInformation class.", 1 },'; + echo '+ { "Sebastian Lackner", "server: Implement support for FileLinkInformation class in NtSetInformationFile.", 1 },'; ) >> "$patchlist" fi