[PATCH resend 1/2] kernel32/path: Allow renaming a file/directory to a different capitalization of itself.

Gabriel Ivăncescu gabrielopcode at gmail.com
Mon Feb 3 08:16:06 CST 2020


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 <gabrielopcode at gmail.com>
---

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




More information about the wine-devel mailing list