[PATCH v5 0/3] MR4457: Add tests and implementation of FILE_{RENAME,LINK}_POSIX_SEMANTICS.
Signed-off-by: Joel Holdsworth <joel@airwebreathe.org.uk> -- v5: ntdll: Add support for FILE_{RENAME,LINK}_POSIX_SEMANTICS. ntdll/test: Add tests for FILE_LINK_POSIX_SEMANTICS. ntdll/test: Add tests for FILE_RENAME_POSIX_SEMANTICS. https://gitlab.winehq.org/wine/wine/-/merge_requests/4457
From: Joel Holdsworth <joel@airwebreathe.org.uk> Signed-off-by: Joel Holdsworth <joel@airwebreathe.org.uk> --- dlls/ntdll/tests/file.c | 190 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 189 insertions(+), 1 deletion(-) diff --git a/dlls/ntdll/tests/file.c b/dlls/ntdll/tests/file.c index 6268870bc08..2a383099eba 100644 --- a/dlls/ntdll/tests/file.c +++ b/dlls/ntdll/tests/file.c @@ -2305,7 +2305,7 @@ static void test_file_rename_information_ex(void) static const WCHAR fooW[] = {'f','o','o',0}; WCHAR tmp_path[MAX_PATH], oldpath[MAX_PATH + 16], newpath[MAX_PATH + 16]; FILE_RENAME_INFORMATION *fri; - BOOL fileDeleted; + BOOL success, fileDeleted; UNICODE_STRING name_str; HANDLE handle, handle2; IO_STATUS_BLOCK io; @@ -2396,6 +2396,194 @@ static void test_file_rename_information_ex(void) HeapFree( GetProcessHeap(), 0, fri ); delete_object( oldpath ); delete_object( newpath ); + + /* oldpath is a file, newpath is a file, with FILE_RENAME_REPLACE_IF_EXISTS, with + * FILE_RENAME_POSIX_SEMANTICS, target file opened without sharing mode */ + res = GetTempFileNameW( tmp_path, fooW, 0, oldpath ); + ok( res != 0, "failed 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, fooW, 0, newpath ); + ok( res != 0, "failed 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 ); + fri = HeapAlloc( GetProcessHeap(), 0, sizeof(FILE_RENAME_INFORMATION) + name_str.Length ); + fri->Flags = FILE_RENAME_REPLACE_IF_EXISTS | FILE_RENAME_POSIX_SEMANTICS; + fri->RootDirectory = NULL; + fri->FileNameLength = name_str.Length; + memcpy( fri->FileName, name_str.Buffer, name_str.Length ); + pRtlFreeUnicodeString( &name_str ); + + io.Status = 0xdeadbeef; + res = pNtSetInformationFile( handle, &io, fri, sizeof(FILE_RENAME_INFORMATION) + fri->FileNameLength, FileRenameInformationEx ); + todo_wine ok( io.Status == 0xdeadbeef, "io.Status expected 0xdeadbeef, got %lx\n", io.Status ); + ok( res == STATUS_SHARING_VIOLATION, "res expected STATUS_SHARING_VIOLATION, got %lx\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, fri ); + delete_object( oldpath ); + delete_object( newpath ); + + /* oldpath is a file, newpath is a file, with FILE_RENAME_REPLACE_IF_EXISTS, with + * FILE_RENAME_POSIX_SEMANTICS, target file opened with sharing mode */ + res = GetTempFileNameW( tmp_path, fooW, 0, oldpath ); + ok( res != 0, "failed 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, fooW, 0, newpath ); + ok( res != 0, "failed 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 ); + fri = HeapAlloc( GetProcessHeap(), 0, sizeof(FILE_RENAME_INFORMATION) + name_str.Length ); + fri->Flags = FILE_RENAME_REPLACE_IF_EXISTS | FILE_RENAME_POSIX_SEMANTICS; + fri->RootDirectory = NULL; + fri->FileNameLength = name_str.Length; + memcpy( fri->FileName, name_str.Buffer, name_str.Length ); + pRtlFreeUnicodeString( &name_str ); + + io.Status = 0xdeadbeef; + res = pNtSetInformationFile( handle, &io, fri, sizeof(FILE_RENAME_INFORMATION) + fri->FileNameLength, FileRenameInformationEx ); + todo_wine ok( io.Status == STATUS_SUCCESS, "io.Status expected STATUS_SUCCESS, got %lx\n", io.Status ); + todo_wine ok( res == STATUS_SUCCESS, "res expected STATUS_SUCCESS, got %lx\n", res ); + fileDeleted = GetFileAttributesW( oldpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; + todo_wine ok( fileDeleted, "file should not 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, fri ); + delete_object( oldpath ); + delete_object( newpath ); + + /* oldpath is a directory, newpath is a file, with FILE_RENAME_REPLACE_IF_EXISTS, with + * FILE_RENAME_POSIX_SEMANTICS, target file opened */ + res = GetTempFileNameW( tmp_path, fooW, 0, oldpath ); + ok( res != 0, "failed 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, fooW, 0, newpath ); + ok( res != 0, "failed 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 ); + fri = HeapAlloc( GetProcessHeap(), 0, sizeof(FILE_RENAME_INFORMATION) + name_str.Length ); + fri->Flags = FILE_RENAME_REPLACE_IF_EXISTS | FILE_RENAME_POSIX_SEMANTICS; + fri->RootDirectory = NULL; + fri->FileNameLength = name_str.Length; + memcpy( fri->FileName, name_str.Buffer, name_str.Length ); + pRtlFreeUnicodeString( &name_str ); + + io.Status = 0xdeadbeef; + res = pNtSetInformationFile( handle, &io, fri, sizeof(FILE_RENAME_INFORMATION) + fri->FileNameLength, FileRenameInformationEx ); + todo_wine ok( io.Status == 0xdeadbeef, "io.Status expected 0xdeadbeef, got %lx\n", io.Status ); + ok( res == STATUS_NOT_A_DIRECTORY, "res expected STATUS_NOT_A_DIRECTORY, got %lx\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, fri ); + delete_object( oldpath ); + delete_object( newpath ); + + /* oldpath is a directory, newpath is a directory, with FILE_RENAME_REPLACE_IF_EXISTS, with + * FILE_RENAME_POSIX_SEMANTICS, target file opened */ + res = GetTempFileNameW( tmp_path, fooW, 0, oldpath ); + ok( res != 0, "failed 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, fooW, 0, newpath ); + ok( res != 0, "failed 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, FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0 ); + ok( handle2 != INVALID_HANDLE_VALUE, "CreateFileW failed\n" ); + + pRtlDosPathNameToNtPathName_U( newpath, &name_str, NULL, NULL ); + fri = HeapAlloc( GetProcessHeap(), 0, sizeof(FILE_RENAME_INFORMATION) + name_str.Length ); + fri->Flags = FILE_RENAME_REPLACE_IF_EXISTS | FILE_RENAME_POSIX_SEMANTICS; + fri->RootDirectory = NULL; + fri->FileNameLength = name_str.Length; + memcpy( fri->FileName, name_str.Buffer, name_str.Length ); + pRtlFreeUnicodeString( &name_str ); + + io.Status = 0xdeadbeef; + res = pNtSetInformationFile( handle, &io, fri, sizeof(FILE_RENAME_INFORMATION) + fri->FileNameLength, FileRenameInformationEx ); + ok( io.Status == STATUS_SUCCESS || io.Status == 0xdeadbeef, + "io.Status expected STATUS_SUCCESS or 0xdeadbeef, got %lx\n", io.Status ); + ok( res == STATUS_SUCCESS, "res expected STATUS_SUCCESS, got %lx\n", res ); + fileDeleted = GetFileAttributesW( oldpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; + ok( fileDeleted, "file should not 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, fri ); + delete_object( oldpath ); + delete_object( newpath ); + + /* oldpath is a file, newpath is a directory, with FILE_RENAME_REPLACE_IF_EXISTS, with + * FILE_RENAME_POSIX_SEMANTICS */ + res = GetTempFileNameW( tmp_path, fooW, 0, oldpath ); + ok( res != 0, "failed 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, fooW, 0, newpath ); + ok( res != 0, "failed 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 ); + fri = HeapAlloc( GetProcessHeap(), 0, sizeof(FILE_RENAME_INFORMATION) + name_str.Length ); + fri->Flags = FILE_RENAME_REPLACE_IF_EXISTS | FILE_RENAME_POSIX_SEMANTICS; + fri->RootDirectory = NULL; + fri->FileNameLength = name_str.Length; + memcpy( fri->FileName, name_str.Buffer, name_str.Length ); + pRtlFreeUnicodeString( &name_str ); + + io.Status = 0xdeadbeef; + res = pNtSetInformationFile( handle, &io, fri, sizeof(FILE_RENAME_INFORMATION) + fri->FileNameLength, FileRenameInformationEx ); + todo_wine ok( io.Status == 0xdeadbeef, "io.Status expected 0xdeadbeef, got %lx\n", io.Status ); + ok( res == STATUS_FILE_IS_A_DIRECTORY, "res expected STATUS_FILE_IS_A_DIRECTORY, got %lx\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, fri ); + delete_object( oldpath ); + delete_object( newpath ); } static void test_file_link_information(FILE_INFORMATION_CLASS class) -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/4457
From: Joel Holdsworth <joel@airwebreathe.org.uk> Signed-off-by: Joel Holdsworth <joel@airwebreathe.org.uk> --- dlls/ntdll/tests/file.c | 199 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 198 insertions(+), 1 deletion(-) diff --git a/dlls/ntdll/tests/file.c b/dlls/ntdll/tests/file.c index 2a383099eba..02dbc0dfe15 100644 --- a/dlls/ntdll/tests/file.c +++ b/dlls/ntdll/tests/file.c @@ -3350,7 +3350,7 @@ static void test_file_link_information_ex(void) static const WCHAR fooW[] = {'f','o','o',0}; WCHAR tmp_path[MAX_PATH], oldpath[MAX_PATH + 16], newpath[MAX_PATH + 16]; FILE_LINK_INFORMATION *fli; - BOOL fileDeleted; + BOOL success, fileDeleted; UNICODE_STRING name_str; HANDLE handle, handle2; IO_STATUS_BLOCK io; @@ -3435,6 +3435,203 @@ static void test_file_link_information_ex(void) HeapFree( GetProcessHeap(), 0, fli ); delete_object( oldpath ); delete_object( newpath ); + + /* oldpath is a file, newpath is a file, with FILE_LINK_REPLACE_IF_EXISTS, with + * FILE_LINK_POSIX_SEMANTICS, target file opened without sharing mode */ + res = GetTempFileNameW( tmp_path, fooW, 0, oldpath ); + ok( res != 0, "failed 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, fooW, 0, newpath ); + ok( res != 0, "failed 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 ); + fli = HeapAlloc( GetProcessHeap(), 0, sizeof(FILE_LINK_INFORMATION) + name_str.Length ); + fli->Flags = FILE_LINK_REPLACE_IF_EXISTS | FILE_LINK_POSIX_SEMANTICS; + fli->RootDirectory = NULL; + fli->FileNameLength = name_str.Length; + memcpy( fli->FileName, name_str.Buffer, name_str.Length ); + pRtlFreeUnicodeString( &name_str ); + + io.Status = 0xdeadbeef; + res = pNtSetInformationFile( handle, &io, fli, sizeof(FILE_LINK_INFORMATION) + fli->FileNameLength, FileLinkInformationEx ); + todo_wine ok( io.Status == 0xdeadbeef, "io.Status expected 0xdeadbeef, got %lx\n", io.Status ); + todo_wine ok( res == STATUS_SHARING_VIOLATION || res == STATUS_NOT_SUPPORTED, + "res expected STATUS_SHARING_VIOLATION or STATUS_NOT_SUPPORTED, got %lx\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, fli ); + delete_object( oldpath ); + delete_object( newpath ); + + /* oldpath is a file, newpath is a file, with FILE_LINK_REPLACE_IF_EXISTS, with + * FILE_LINK_POSIX_SEMANTICS, target file opened with sharing mode */ + res = GetTempFileNameW( tmp_path, fooW, 0, oldpath ); + ok( res != 0, "failed 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, fooW, 0, newpath ); + ok( res != 0, "failed 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 ); + fli = HeapAlloc( GetProcessHeap(), 0, sizeof(FILE_LINK_INFORMATION) + name_str.Length ); + fli->Flags = FILE_LINK_REPLACE_IF_EXISTS | FILE_LINK_POSIX_SEMANTICS; + fli->RootDirectory = NULL; + fli->FileNameLength = name_str.Length; + memcpy( fli->FileName, name_str.Buffer, name_str.Length ); + pRtlFreeUnicodeString( &name_str ); + + io.Status = 0xdeadbeef; + res = pNtSetInformationFile( handle, &io, fli, sizeof(FILE_LINK_INFORMATION) + fli->FileNameLength, FileLinkInformationEx ); + todo_wine ok( io.Status == 0xdeadbeef || io.Status == STATUS_SUCCESS, + "io.Status expected 0xdeadbeef or STATUS_SUCCESS, got %lx\n", io.Status ); + todo_wine ok( res == STATUS_SUCCESS || res == STATUS_NOT_SUPPORTED, + "res expected STATUS_SUCCESS or STATUS_NOT_SUPPORTED, got %lx\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, fli ); + delete_object( oldpath ); + delete_object( newpath ); + + /* oldpath is a directory, newpath is a file, with FILE_LINK_REPLACE_IF_EXISTS, with + * FILE_LINK_POSIX_SEMANTICS, target file opened */ + res = GetTempFileNameW( tmp_path, fooW, 0, oldpath ); + ok( res != 0, "failed 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, fooW, 0, newpath ); + ok( res != 0, "failed 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 ); + fli = HeapAlloc( GetProcessHeap(), 0, sizeof(FILE_LINK_INFORMATION) + name_str.Length ); + fli->Flags = FILE_LINK_REPLACE_IF_EXISTS | FILE_LINK_POSIX_SEMANTICS; + fli->RootDirectory = NULL; + fli->FileNameLength = name_str.Length; + memcpy( fli->FileName, name_str.Buffer, name_str.Length ); + pRtlFreeUnicodeString( &name_str ); + + io.Status = 0xdeadbeef; + res = pNtSetInformationFile( handle, &io, fli, sizeof(FILE_LINK_INFORMATION) + fli->FileNameLength, FileLinkInformationEx ); + todo_wine ok( io.Status == 0xdeadbeef, "io.Status expected 0xdeadbeef, got %lx\n", io.Status ); + ok( res == STATUS_FILE_IS_A_DIRECTORY || res == STATUS_NOT_SUPPORTED, + "res expected STATUS_FILE_IS_A_DIRECTORY or STATUS_NOT_SUPPORTED, got %lx\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, fli ); + delete_object( oldpath ); + delete_object( newpath ); + + /* oldpath is a directory, newpath is a directory, with FILE_LINK_REPLACE_IF_EXISTS, with + * FILE_LINK_POSIX_SEMANTICS, target file opened */ + res = GetTempFileNameW( tmp_path, fooW, 0, oldpath ); + ok( res != 0, "failed 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, fooW, 0, newpath ); + ok( res != 0, "failed 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, FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0 ); + ok( handle2 != INVALID_HANDLE_VALUE, "CreateFileW failed\n" ); + + pRtlDosPathNameToNtPathName_U( newpath, &name_str, NULL, NULL ); + fli = HeapAlloc( GetProcessHeap(), 0, sizeof(FILE_LINK_INFORMATION) + name_str.Length ); + fli->Flags = FILE_LINK_REPLACE_IF_EXISTS | FILE_LINK_POSIX_SEMANTICS; + fli->RootDirectory = NULL; + fli->FileNameLength = name_str.Length; + memcpy( fli->FileName, name_str.Buffer, name_str.Length ); + pRtlFreeUnicodeString( &name_str ); + + io.Status = 0xdeadbeef; + res = pNtSetInformationFile( handle, &io, fli, sizeof(FILE_LINK_INFORMATION) + fli->FileNameLength, FileLinkInformationEx ); + todo_wine ok( io.Status == 0xdeadbeef, "io.Status expected 0xdeadbeef, got %lx\n", io.Status ); + ok( res == STATUS_FILE_IS_A_DIRECTORY || res == STATUS_NOT_SUPPORTED, + "res expected STATUS_FILE_IS_A_DIRECTORY or STATUS_NOT_SUPPORTED, got %lx\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, fli ); + delete_object( oldpath ); + delete_object( newpath ); + + /* oldpath is a file, newpath is a directory, with FILE_LINK_REPLACE_IF_EXISTS, with + * FILE_LINK_POSIX_SEMANTICS */ + res = GetTempFileNameW( tmp_path, fooW, 0, oldpath ); + ok( res != 0, "failed 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, fooW, 0, newpath ); + ok( res != 0, "failed 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 ); + fli = HeapAlloc( GetProcessHeap(), 0, sizeof(FILE_LINK_INFORMATION) + name_str.Length ); + fli->Flags = FILE_LINK_REPLACE_IF_EXISTS | FILE_LINK_POSIX_SEMANTICS; + fli->RootDirectory = NULL; + fli->FileNameLength = name_str.Length; + memcpy( fli->FileName, name_str.Buffer, name_str.Length ); + pRtlFreeUnicodeString( &name_str ); + + io.Status = 0xdeadbeef; + res = pNtSetInformationFile( handle, &io, fli, sizeof(FILE_LINK_INFORMATION) + fli->FileNameLength, FileLinkInformationEx ); + todo_wine ok( io.Status == 0xdeadbeef, "io.Status expected 0xdeadbeef, got %lx\n", io.Status ); + todo_wine ok( res == STATUS_FILE_IS_A_DIRECTORY || res == STATUS_NOT_SUPPORTED, + "res expected STATUS_FILE_IS_A_DIRECTORY or STATUS_NOT_SUPPORTED, got %lx\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, fli ); + delete_object( oldpath ); + delete_object( newpath ); } static void test_file_both_information(void) -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/4457
From: Joel Holdsworth <joel@airwebreathe.org.uk> Signed-off-by: Joel Holdsworth <joel@airwebreathe.org.uk> --- dlls/ntdll/tests/file.c | 22 +++++------ dlls/ntdll/unix/file.c | 8 +++- server/fd.c | 85 ++++++++++++++++++++++++++++++++--------- 3 files changed, 84 insertions(+), 31 deletions(-) diff --git a/dlls/ntdll/tests/file.c b/dlls/ntdll/tests/file.c index 02dbc0dfe15..c0cb733b099 100644 --- a/dlls/ntdll/tests/file.c +++ b/dlls/ntdll/tests/file.c @@ -2455,10 +2455,10 @@ static void test_file_rename_information_ex(void) io.Status = 0xdeadbeef; res = pNtSetInformationFile( handle, &io, fri, sizeof(FILE_RENAME_INFORMATION) + fri->FileNameLength, FileRenameInformationEx ); - todo_wine ok( io.Status == STATUS_SUCCESS, "io.Status expected STATUS_SUCCESS, got %lx\n", io.Status ); - todo_wine ok( res == STATUS_SUCCESS, "res expected STATUS_SUCCESS, got %lx\n", res ); + ok( io.Status == STATUS_SUCCESS, "io.Status expected STATUS_SUCCESS, got %lx\n", io.Status ); + ok( res == STATUS_SUCCESS, "res expected STATUS_SUCCESS, got %lx\n", res ); fileDeleted = GetFileAttributesW( oldpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; - todo_wine ok( fileDeleted, "file should not exist\n" ); + ok( fileDeleted, "file should not exist\n" ); fileDeleted = GetFileAttributesW( newpath ) == INVALID_FILE_ATTRIBUTES && GetLastError() == ERROR_FILE_NOT_FOUND; ok( !fileDeleted, "file should exist\n" ); @@ -3461,8 +3461,8 @@ static void test_file_link_information_ex(void) io.Status = 0xdeadbeef; res = pNtSetInformationFile( handle, &io, fli, sizeof(FILE_LINK_INFORMATION) + fli->FileNameLength, FileLinkInformationEx ); todo_wine ok( io.Status == 0xdeadbeef, "io.Status expected 0xdeadbeef, got %lx\n", io.Status ); - todo_wine ok( res == STATUS_SHARING_VIOLATION || res == STATUS_NOT_SUPPORTED, - "res expected STATUS_SHARING_VIOLATION or STATUS_NOT_SUPPORTED, got %lx\n", res ); + ok( res == STATUS_SHARING_VIOLATION || res == STATUS_NOT_SUPPORTED, + "res expected STATUS_SHARING_VIOLATION or STATUS_NOT_SUPPORTED, got %lx\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; @@ -3498,10 +3498,10 @@ static void test_file_link_information_ex(void) io.Status = 0xdeadbeef; res = pNtSetInformationFile( handle, &io, fli, sizeof(FILE_LINK_INFORMATION) + fli->FileNameLength, FileLinkInformationEx ); - todo_wine ok( io.Status == 0xdeadbeef || io.Status == STATUS_SUCCESS, - "io.Status expected 0xdeadbeef or STATUS_SUCCESS, got %lx\n", io.Status ); - todo_wine ok( res == STATUS_SUCCESS || res == STATUS_NOT_SUPPORTED, - "res expected STATUS_SUCCESS or STATUS_NOT_SUPPORTED, got %lx\n", res ); + ok( io.Status == 0xdeadbeef || io.Status == STATUS_SUCCESS, + "io.Status expected 0xdeadbeef or STATUS_SUCCESS, got %lx\n", io.Status ); + ok( res == STATUS_SUCCESS || res == STATUS_NOT_SUPPORTED, + "res expected STATUS_SUCCESS or STATUS_NOT_SUPPORTED, got %lx\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; @@ -3621,8 +3621,8 @@ static void test_file_link_information_ex(void) io.Status = 0xdeadbeef; res = pNtSetInformationFile( handle, &io, fli, sizeof(FILE_LINK_INFORMATION) + fli->FileNameLength, FileLinkInformationEx ); todo_wine ok( io.Status == 0xdeadbeef, "io.Status expected 0xdeadbeef, got %lx\n", io.Status ); - todo_wine ok( res == STATUS_FILE_IS_A_DIRECTORY || res == STATUS_NOT_SUPPORTED, - "res expected STATUS_FILE_IS_A_DIRECTORY or STATUS_NOT_SUPPORTED, got %lx\n", res ); + ok( res == STATUS_FILE_IS_A_DIRECTORY || res == STATUS_NOT_SUPPORTED, + "res expected STATUS_FILE_IS_A_DIRECTORY or STATUS_NOT_SUPPORTED, got %lx\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; diff --git a/dlls/ntdll/unix/file.c b/dlls/ntdll/unix/file.c index b5add7aa501..5bd06529970 100644 --- a/dlls/ntdll/unix/file.c +++ b/dlls/ntdll/unix/file.c @@ -5430,7 +5430,9 @@ NTSTATUS WINAPI NtSetInformationFile( HANDLE handle, IO_STATUS_BLOCK *io, else flags = info->Flags; - if (flags & ~(FILE_RENAME_REPLACE_IF_EXISTS | FILE_RENAME_IGNORE_READONLY_ATTRIBUTE)) + if (flags & ~(FILE_RENAME_REPLACE_IF_EXISTS | + FILE_RENAME_IGNORE_READONLY_ATTRIBUTE | + FILE_RENAME_POSIX_SEMANTICS)) FIXME( "unsupported flags: %#x\n", flags ); name_str.Buffer = info->FileName; @@ -5475,7 +5477,9 @@ NTSTATUS WINAPI NtSetInformationFile( HANDLE handle, IO_STATUS_BLOCK *io, else flags = info->Flags; - if (flags & ~(FILE_LINK_REPLACE_IF_EXISTS | FILE_LINK_IGNORE_READONLY_ATTRIBUTE)) + if (flags & ~(FILE_LINK_REPLACE_IF_EXISTS | + FILE_LINK_IGNORE_READONLY_ATTRIBUTE | + FILE_LINK_POSIX_SEMANTICS)) FIXME( "unsupported flags: %#x\n", flags ); name_str.Buffer = info->FileName; diff --git a/server/fd.c b/server/fd.c index efb6e8e76b9..222025f29f8 100644 --- a/server/fd.c +++ b/server/fd.c @@ -2911,13 +2911,6 @@ static void set_fd_name( struct fd *fd, struct fd *root, const char *nameptr, da goto failed; } - /* can't replace directories or special files */ - if (!S_ISREG( st.st_mode )) - { - set_error( STATUS_ACCESS_DENIED ); - goto failed; - } - /* read-only files cannot be replaced */ if (!(st.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH)) && !(flags & FILE_RENAME_IGNORE_READONLY_ATTRIBUTE)) @@ -2926,23 +2919,79 @@ static void set_fd_name( struct fd *fd, struct fd *root, const char *nameptr, da goto failed; } - /* can't replace an opened file */ - if ((inode = get_inode( st.st_dev, st.st_ino, -1 ))) + if (flags & FILE_RENAME_POSIX_SEMANTICS) { - int is_empty = list_empty( &inode->open ); - release_object( inode ); - if (!is_empty) + /* can't replace a file with a directory with FILE_RENAME_POSIX_SEMANTICS */ + if (S_ISDIR( st2.st_mode ) && !S_ISDIR( st.st_mode )) { - set_error( STATUS_ACCESS_DENIED ); + set_error( STATUS_NOT_A_DIRECTORY ); goto failed; } - } - /* link() expects that the target doesn't exist */ - /* rename() cannot replace files with directories */ - if (create_link || S_ISDIR( st2.st_mode )) + /* can't replace an open file that was not opened with FILE_SHARE_DELETE */ + if ((inode = get_inode( st.st_dev, st.st_ino, -1 ))) + { + unsigned int sharing = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; + struct fd *fd_ptr; + + LIST_FOR_EACH_ENTRY( fd_ptr, &inode->open, struct fd, inode_entry ) + { + /* if access mode is 0, sharing mode is ignored */ + if (fd_ptr->access & + (FILE_READ_DATA | FILE_EXECUTE | FILE_WRITE_DATA | FILE_APPEND_DATA | DELETE)) + { + sharing &= fd_ptr->sharing; + } + } + + release_object( inode ); + + if (!(sharing & FILE_SHARE_DELETE)) { + set_error( STATUS_SHARING_VIOLATION ); + goto failed; + } + } + + /* link() expects that the target doesn't exist */ + /* rename() cannot replace files with directories */ + if (S_ISDIR( st2.st_mode ) && S_ISDIR( st.st_mode )) + { + if (rmdir( name )) + { + file_set_error(); + goto failed; + } + } + else if ((create_link || S_ISDIR( st2.st_mode )) && unlink(name)) + { + file_set_error(); + goto failed; + } + } + else { - if (unlink( name )) + /* can't replace directories or special files */ + if (!S_ISREG( st.st_mode )) + { + set_error( STATUS_ACCESS_DENIED ); + goto failed; + } + + /* can't replace an opened file without FILE_RENAME_POSIX_SEMANTICS */ + 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 failed; + } + } + + /* link() expects that the target doesn't exist */ + /* rename() cannot replace files with directories */ + if ((create_link || S_ISDIR( st2.st_mode )) && unlink( name )) { file_set_error(); goto failed; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/4457
On Wed May 13 22:19:10 2026 +0000, Alexandre Julliard wrote:
I'm not convinced that msys2 is a valid use case, but anyway... That `set_fd_name` function could use some helpers, but `get_inode_open_sharing` is not a very good helper, there's very little in common between the different use cases. Hi @julliard . Thank you for having a look.
That `set_fd_name` function could use some helpers, but `get_inode_open_sharing` is not a very good helper, there's very little in common between the different use cases.
Regarding this merge request, I have removed the `get_inode_open_sharing` helper and open-coded it. I agree that the helper was marginal.
That `set_fd_name` function could use some helpers, but `get_inode_open_sharing` is not a very good helper, there's very little in common between the different use cases.
I don't think it's for me to say whether the use case is valid or not. I can only say that there are a fair few people who find value in it. It is the [top FAQ item](https://www.msys2.org/docs/faq/) on the MSYS2 website. The associated [tracking bug in GitHub](https://github.com/msys2/MSYS2-packages/issues/682) has a large number of interested participants. I maintain a long-running patch series, the latest release of which is [`msys2-hacks-23`](https://gitlab.winehq.org/jhol/wine/-/commits/msys2-hacks-23), and I have been working on a multi-year effort to get full support including contributions from multiple developers, particularly from @bernhardu . The primary consumer of these branches is [msys2-docker](https://github.com/msys2/msys2-docker) which packages Wine and MSYS2 inside a docker image. Regarding functionality, our project is nearly complete. My original use case for this is to get a msys2/mingw package-building process fully working in a linux-based CI system. In `msys2-hacks-23`, I have been able, for the first time, to get full end-to-end support for our build process. This includes exentensive use of pacman (with GnuPG), as well as the build and packaging of customized builds of GCC and Binutils. This use case greatly stresses the MSYS2/Wine stack. We also have near-complete support for [msys2-runtime winsup test suite](https://github.com/msys2/msys2-runtime/tree/msys2-3.6.9/winsup/testsuite), with only one known remaining Wine bug [#33576](https://bugs.winehq.org/show_bug.cgi?id=33576) preventing a clean run. All this to say, that in my opinion these changes *do* have value, and that the requirements are bounded, and that we not far from having full support. The remaining change-set that I have are: * This one. * [!8030: wineserver: Allow NtOpenFile with read-only files.](https://gitlab.winehq.org/wine/wine/-/merge_requests/8030) * [!10866: Report FILE_SUPPORTS_OPEN_BY_FILE_ID](https://gitlab.winehq.org/wine/wine/-/merge_requests/10866) And from @bernhardu are: * [!10044: server: Clear connection failure events in IOCTL_AFD_WINE_CONNECT.](https://gitlab.winehq.org/wine/wine/-/merge_requests/10044) * [patch: ntdll: Add read and write to commited guard pages.](https://gitlab.winehq.org/jhol/wine/-/commit/3ac28dca1710d12dfe290e264c3f57c...) In my opinion, full support for MSYS2 in main-line Wine is within reach. Therefore, if you have a chance to review any of these patches, I would be enormously grateful. Thank you for your time. Please let me know if there is anything I can do to make the process easier. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/4457#note_139743
participants (2)
-
Joel Holdsworth -
Joel Holdsworth (@jhol)