From: "Gabriel Ivăncescu" Subject: [PATCH resend 1/2] kernel32/path: Allow renaming a file/directory to a different capitalization of itself. Message-Id: <49cefe2f39ffbb56598868eec024e563dfa6542d.1580739179.git.gabrielopcode@gmail.com> Date: Mon, 3 Feb 2020 16:16:06 +0200 Renaming a file or directory from e.g. foobar to FooBar (or any other caps-only change) should work and capitalize it, like on Windows, instead of being a no-op. Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=46203 Signed-off-by: Gabriel Ivăncescu --- There's been some interest in this fix during the code freeze, affecting other apps' experience as well, so I sent it again now that it's over. dlls/kernel32/path.c | 66 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 61 insertions(+), 5 deletions(-) diff --git a/dlls/kernel32/path.c b/dlls/kernel32/path.c index c50b631..b4756bf 100644 --- a/dlls/kernel32/path.c +++ b/dlls/kernel32/path.c @@ -274,6 +274,16 @@ static BOOL is_same_file(HANDLE h1, HANDLE h2) return ret; } +static ULONG get_handle_attr(HANDLE handle) +{ + FILE_BASIC_INFORMATION info; + IO_STATUS_BLOCK io; + NTSTATUS status; + + status = NtQueryInformationFile(handle, &io, &info, sizeof(info), FileBasicInformation); + return status != STATUS_SUCCESS ? 0 : info.FileAttributes; +} + /************************************************************************** * CopyFileW (KERNEL32.@) */ @@ -455,6 +465,7 @@ BOOL WINAPI MoveFileWithProgressW( LPCWSTR source, LPCWSTR dest, NTSTATUS status; HANDLE source_handle = 0, dest_handle = 0; ANSI_STRING source_unix, dest_unix; + BOOL same_file = FALSE; DWORD options; TRACE("(%s,%s,%p,%p,%04x)\n", @@ -507,18 +518,22 @@ BOOL WINAPI MoveFileWithProgressW( LPCWSTR source, LPCWSTR dest, SetLastError( ERROR_PATH_NOT_FOUND ); goto error; } - options = FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT; + options = FILE_SYNCHRONOUS_IO_NONALERT; if (flag & MOVEFILE_WRITE_THROUGH) options |= FILE_WRITE_THROUGH; status = NtOpenFile( &dest_handle, GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE, &attr, &io, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, options ); if (status == STATUS_SUCCESS) /* destination exists */ { - if (!(flag & MOVEFILE_REPLACE_EXISTING)) + ULONG attrib = 0; + + if (!(flag & MOVEFILE_REPLACE_EXISTING) || + ((attrib = get_handle_attr(dest_handle)) & FILE_ATTRIBUTE_DIRECTORY)) { - if (!is_same_file( source_handle, dest_handle )) + if (!(same_file = is_same_file( source_handle, dest_handle ))) { - SetLastError( ERROR_ALREADY_EXISTS ); + SetLastError( (attrib & FILE_ATTRIBUTE_DIRECTORY) + ? ERROR_ACCESS_DENIED : ERROR_ALREADY_EXISTS ); RtlFreeUnicodeString( &nt_name ); goto error; } @@ -528,6 +543,7 @@ BOOL WINAPI MoveFileWithProgressW( LPCWSTR source, LPCWSTR dest, SetLastError( ERROR_ACCESS_DENIED ); goto error; } + else same_file = is_same_file( source_handle, dest_handle ); NtClose( dest_handle ); dest_handle = NULL; @@ -540,13 +556,53 @@ BOOL WINAPI MoveFileWithProgressW( LPCWSTR source, LPCWSTR dest, } status = wine_nt_to_unix_file_name( &nt_name, &dest_unix, FILE_OPEN_IF, FALSE ); - RtlFreeUnicodeString( &nt_name ); if (status != STATUS_SUCCESS && status != STATUS_NO_SUCH_FILE) { + RtlFreeUnicodeString( &nt_name ); SetLastError( RtlNtStatusToDosError(status) ); goto error; } + /* When it's renaming to the same file, preserve the case sensitivity of the last + component, so that renaming a\b\c\foobar to a\b\c\Foobar works correctly like + on Windows, but note that due to hard links the paths must be exactly identical */ + if (same_file && source_unix.Length == dest_unix.Length && + !memcmp(source_unix.Buffer, dest_unix.Buffer, dest_unix.Length)) + { + size_t nt_filename_len, pathlen; + const WCHAR *nt_filename; + BOOL used_default; + char *tmp; + INT num; + + nt_filename = memrchrW(nt_name.Buffer, '\\', nt_name.Length / sizeof(WCHAR)) + 1; + nt_filename_len = nt_name.Buffer + nt_name.Length / sizeof(WCHAR) - nt_filename; + + /* Build it from path + filename (the latter converted from nt_name) */ + num = WideCharToMultiByte(CP_UNIXCP, 0, nt_filename, nt_filename_len, + NULL, 0, NULL, &used_default); + if (num > 0 && !used_default) + { + for (pathlen = dest_unix.Length; --pathlen > 0;) + if (dest_unix.Buffer[pathlen] == '/') { pathlen++; break; } + + tmp = dest_unix.Buffer; + if (dest_unix.MaximumLength < pathlen + num + 1) + tmp = RtlReAllocateHeap(GetProcessHeap(), 0, tmp, pathlen + num + 1); + if (tmp) + { + dest_unix.Buffer = tmp; + dest_unix.Length = pathlen + num; + dest_unix.MaximumLength = dest_unix.Length + 1; + dest_unix.Buffer[dest_unix.Length] = '\0'; + + WideCharToMultiByte(CP_UNIXCP, 0, nt_filename, nt_filename_len, + dest_unix.Buffer + pathlen, num, NULL, NULL); + } + } + } + RtlFreeUnicodeString( &nt_name ); + /* now perform the rename */ if (rename( source_unix.Buffer, dest_unix.Buffer ) == -1) -- 2.21.0