[PATCH 1/1] ntdll: Allow renaming a file/directory to a different case of itself.

Gabriel Ivăncescu wine at gitlab.winehq.org
Tue Jun 14 15:12:53 CDT 2022


From: Gabriel Ivăncescu <gabrielopcode at gmail.com>

Renaming a file or directory from e.g. foobar to FooBar (or any other case
change) should work, like on Windows, instead of being a no-op. Clobbering
an existing file must also respect the new case.

Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=46203
Signed-off-by: Gabriel Ivăncescu <gabrielopcode at gmail.com>
---
 dlls/kernel32/tests/file.c |   4 +-
 dlls/ntdll/tests/file.c    |   4 +-
 dlls/ntdll/unix/file.c     |  58 +++++++++++++++++++-
 server/fd.c                | 108 ++++++++++++++++++++++++++-----------
 server/protocol.def        |   2 +
 5 files changed, 138 insertions(+), 38 deletions(-)

diff --git a/dlls/kernel32/tests/file.c b/dlls/kernel32/tests/file.c
index bd8d8c91b2f..9b7066f428d 100644
--- a/dlls/kernel32/tests/file.c
+++ b/dlls/kernel32/tests/file.c
@@ -2049,7 +2049,7 @@ static void test_MoveFileA(void)
     ok(hfile != INVALID_HANDLE_VALUE, "FindFirstFileA: failed, error %ld\n", GetLastError());
     if (hfile != INVALID_HANDLE_VALUE)
     {
-        todo_wine ok(!lstrcmpA(strrchr(tempdir, '\\') + 1, find_data.cFileName),
+        ok(!lstrcmpA(strrchr(tempdir, '\\') + 1, find_data.cFileName),
            "MoveFile failed to change casing on same file: got %s\n", find_data.cFileName);
     }
     CloseHandle(hfile);
@@ -2094,7 +2094,7 @@ static void test_MoveFileA(void)
     ok(hfile != INVALID_HANDLE_VALUE, "FindFirstFileA: failed, error %ld\n", GetLastError());
     if (hfile != INVALID_HANDLE_VALUE)
     {
-        todo_wine ok(!lstrcmpA(strrchr(tempdir, '\\') + 1, find_data.cFileName),
+        ok(!lstrcmpA(strrchr(tempdir, '\\') + 1, find_data.cFileName),
            "MoveFile failed to change casing on same directory: got %s\n", find_data.cFileName);
     }
     CloseHandle(hfile);
diff --git a/dlls/ntdll/tests/file.c b/dlls/ntdll/tests/file.c
index 106413a83fc..f7f103b0589 100644
--- a/dlls/ntdll/tests/file.c
+++ b/dlls/ntdll/tests/file.c
@@ -2315,7 +2315,7 @@ static void test_file_link_information(void)
     ok(handle != INVALID_HANDLE_VALUE, "FindFirstFileW: failed, error %ld\n", GetLastError());
     if (handle != INVALID_HANDLE_VALUE)
     {
-        todo_wine ok(!lstrcmpW(wcsrchr(newpath, '\\') + 1, find_data.cFileName),
+        ok(!lstrcmpW(wcsrchr(newpath, '\\') + 1, find_data.cFileName),
            "Link did not change casing on existing target file: got %s\n", wine_dbgstr_w(find_data.cFileName));
     }
 
@@ -2900,7 +2900,7 @@ static void test_file_link_information(void)
     ok(handle != INVALID_HANDLE_VALUE, "FindFirstFileW: failed, error %ld\n", GetLastError());
     if (handle != INVALID_HANDLE_VALUE)
     {
-        todo_wine ok(!lstrcmpW(wcsrchr(oldpath, '\\') + 1, find_data.cFileName),
+        ok(!lstrcmpW(wcsrchr(oldpath, '\\') + 1, find_data.cFileName),
            "Link did not change casing on same file: got %s\n", wine_dbgstr_w(find_data.cFileName));
     }
 
diff --git a/dlls/ntdll/unix/file.c b/dlls/ntdll/unix/file.c
index cc8bf0c6e82..7460d20646a 100644
--- a/dlls/ntdll/unix/file.c
+++ b/dlls/ntdll/unix/file.c
@@ -3594,6 +3594,52 @@ NTSTATUS WINAPI wine_unix_to_nt_file_name( const char *name, WCHAR *buffer, ULON
 }
 
 
+/***********************************************************************
+ *           get_filename_case
+ *
+ * Get the unix filename, with the case from NT name's last component.
+ */
+static char *get_filename_case( const OBJECT_ATTRIBUTES *attr )
+{
+    const WCHAR *p, *nt_filename = attr->ObjectName->Buffer;
+    int len = attr->ObjectName->Length / sizeof(WCHAR);
+    char *file_case;
+
+    /* skip the device and prefix (allow slashes for unix namespace) */
+    if (!attr->RootDirectory)
+    {
+        int pos = get_dos_prefix_len( attr->ObjectName );
+        while (pos < len)
+        {
+            WCHAR c = nt_filename[pos++];
+            if (c == '\\' || c == '/') break;
+        }
+        nt_filename += pos;
+        len -= pos;
+    }
+
+    /* strip off trailing backslashes */
+    for (; len; len--)
+        if (nt_filename[len - 1] != '\\' && nt_filename[len - 1] != '/')
+            break;
+
+    /* get the last component */
+    for (p = nt_filename + len; p != nt_filename; p--)
+        if (p[-1] == '\\' || p[-1] == '/')
+            break;
+    len -= p - nt_filename;
+    nt_filename = p;
+
+    if ((file_case = malloc( len * 3 + 1 )))
+    {
+        len = ntdll_wcstoumbs( nt_filename, len, file_case, len * 3, TRUE );
+        if (len < 0 || len > MAX_DIR_ENTRY_LEN) len = 0;
+        file_case[len] = 0;
+    }
+    return file_case;
+}
+
+
 /***********************************************************************
  *           get_full_path
  *
@@ -4534,8 +4580,8 @@ NTSTATUS WINAPI NtSetInformationFile( HANDLE handle, IO_STATUS_BLOCK *io,
         {
             FILE_RENAME_INFORMATION *info = ptr;
             UNICODE_STRING name_str, redir;
+            char *unix_name, *file_case;
             OBJECT_ATTRIBUTES attr;
-            char *unix_name;
 
             name_str.Buffer = info->FileName;
             name_str.Length = info->FileNameLength;
@@ -4546,20 +4592,24 @@ NTSTATUS WINAPI NtSetInformationFile( HANDLE handle, IO_STATUS_BLOCK *io,
             status = nt_to_unix_file_name( &attr, &unix_name, FILE_OPEN_IF );
             if (status == STATUS_SUCCESS || status == STATUS_NO_SUCH_FILE)
             {
+                file_case = get_filename_case( &attr );
                 SERVER_START_REQ( set_fd_name_info )
                 {
                     req->handle   = wine_server_obj_handle( handle );
                     req->rootdir  = wine_server_obj_handle( attr.RootDirectory );
                     req->namelen  = attr.ObjectName->Length;
+                    req->caselen  = file_case ? strlen( file_case ) : 0;
                     req->link     = FALSE;
                     req->replace  = info->ReplaceIfExists;
                     wine_server_add_data( req, attr.ObjectName->Buffer, attr.ObjectName->Length );
+                    wine_server_add_data( req, file_case, req->caselen );
                     wine_server_add_data( req, unix_name, strlen(unix_name) );
                     status = wine_server_call( req );
                 }
                 SERVER_END_REQ;
 
                 free( unix_name );
+                free( file_case );
             }
             free( redir.Buffer );
         }
@@ -4571,8 +4621,8 @@ NTSTATUS WINAPI NtSetInformationFile( HANDLE handle, IO_STATUS_BLOCK *io,
         {
             FILE_LINK_INFORMATION *info = ptr;
             UNICODE_STRING name_str, redir;
+            char *unix_name, *file_case;
             OBJECT_ATTRIBUTES attr;
-            char *unix_name;
 
             name_str.Buffer = info->FileName;
             name_str.Length = info->FileNameLength;
@@ -4583,20 +4633,24 @@ NTSTATUS WINAPI NtSetInformationFile( HANDLE handle, IO_STATUS_BLOCK *io,
             status = nt_to_unix_file_name( &attr, &unix_name, FILE_OPEN_IF );
             if (status == STATUS_SUCCESS || status == STATUS_NO_SUCH_FILE)
             {
+                file_case = get_filename_case( &attr );
                 SERVER_START_REQ( set_fd_name_info )
                 {
                     req->handle   = wine_server_obj_handle( handle );
                     req->rootdir  = wine_server_obj_handle( attr.RootDirectory );
                     req->namelen  = attr.ObjectName->Length;
+                    req->caselen  = file_case ? strlen( file_case ) : 0;
                     req->link     = TRUE;
                     req->replace  = info->ReplaceIfExists;
                     wine_server_add_data( req, attr.ObjectName->Buffer, attr.ObjectName->Length );
+                    wine_server_add_data( req, file_case, req->caselen );
                     wine_server_add_data( req, unix_name, strlen(unix_name) );
                     status  = wine_server_call( req );
                 }
                 SERVER_END_REQ;
 
                 free( unix_name );
+                free( file_case );
             }
             free( redir.Buffer );
         }
diff --git a/server/fd.c b/server/fd.c
index 1b4b98b0e76..7101cec401e 100644
--- a/server/fd.c
+++ b/server/fd.c
@@ -2527,11 +2527,14 @@ static void set_fd_disposition( struct fd *fd, int unlink )
 
 /* set new name for the fd */
 static void set_fd_name( struct fd *fd, struct fd *root, const char *nameptr, data_size_t len,
-                         struct unicode_str nt_name, int create_link, int replace )
+                         const char *file_case, data_size_t caselen, struct unicode_str nt_name,
+                         int create_link, int replace )
 {
+    size_t pathlen = 0, filenamelen;
     struct inode *inode;
     struct stat st, st2;
-    char *name;
+    int different_case;
+    char *name, *p;
 
     if (!fd->inode || !fd->unix_name)
     {
@@ -2565,6 +2568,30 @@ static void set_fd_name( struct fd *fd, struct fd *root, const char *nameptr, da
         name = combined_name;
     }
 
+    /* get path len up to last component, ignoring trailing slashes */
+    for (p = name; *p;)
+        if (*p++ == '/' && *p)
+            pathlen = p - name;
+
+    /* get filename ptr and len, without trailing slashes */
+    filenamelen = p - name - pathlen;
+    p = name + pathlen;
+    while (filenamelen && p[filenamelen - 1] == '/')
+        filenamelen--;
+
+    different_case = caselen && (filenamelen != caselen || memcmp( p, file_case, caselen ));
+
+    if (filenamelen < caselen)
+    {
+        p = realloc( name, pathlen + caselen + 1 );
+        if (!p)
+        {
+            set_error( STATUS_NO_MEMORY );
+            goto failed;
+        }
+        name = p;
+    }
+
     /* when creating a hard link, source cannot be a dir */
     if (create_link && !fstat( fd->unix_fd, &st ) && S_ISDIR( st.st_mode ))
     {
@@ -2577,47 +2604,61 @@ static void set_fd_name( struct fd *fd, struct fd *root, const char *nameptr, da
         if (!fstat( fd->unix_fd, &st2 ) && st.st_ino == st2.st_ino && st.st_dev == st2.st_dev)
         {
             if (create_link && !replace) set_error( STATUS_OBJECT_NAME_COLLISION );
-            free( name );
-            return;
-        }
+            if (!different_case)
+            {
+                free( name );
+                return;
+            }
 
-        if (!replace)
-        {
-            set_error( STATUS_OBJECT_NAME_COLLISION );
-            goto failed;
+            /* creating a link with a different case on itself renames the file */
+            create_link = 0;
         }
-
-        /* can't replace directories or special files */
-        if (!S_ISREG( st.st_mode ))
+        else
         {
-            set_error( STATUS_ACCESS_DENIED );
-            goto failed;
-        }
+            if (!replace)
+            {
+                set_error( STATUS_OBJECT_NAME_COLLISION );
+                goto failed;
+            }
 
-        /* can't replace an opened file */
-        if ((inode = get_inode( st.st_dev, st.st_ino, -1 )))
-        {
-            int is_empty = list_empty( &inode->open );
-            release_object( inode );
-            if (!is_empty)
+            /* can't replace directories or special files */
+            if (!S_ISREG( st.st_mode ))
             {
                 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 ))
-        {
-            if (unlink( name ))
+            /* can't replace an opened file */
+            if ((inode = get_inode( st.st_dev, st.st_ino, -1 )))
             {
-                file_set_error();
-                goto failed;
+                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 ) || different_case)
+            {
+                if (unlink( name ))
+                {
+                    file_set_error();
+                    goto failed;
+                }
             }
         }
     }
 
+    if (different_case)
+    {
+        memcpy( name + pathlen, file_case, caselen );
+        name[pathlen + caselen] = 0;
+    }
+
     if (create_link)
     {
         if (link( fd->unix_name, name ))
@@ -2959,16 +3000,19 @@ DECL_HANDLER(set_fd_disp_info)
 /* set fd name information */
 DECL_HANDLER(set_fd_name_info)
 {
+    const char *fullname, *file_case;
     struct fd *fd, *root_fd = NULL;
     struct unicode_str nt_name;
 
-    if (req->namelen > get_req_data_size())
+    if (req->namelen > get_req_data_size() || get_req_data_size() - req->namelen < req->caselen)
     {
         set_error( STATUS_INVALID_PARAMETER );
         return;
     }
     nt_name.str = get_req_data();
     nt_name.len = (req->namelen / sizeof(WCHAR)) * sizeof(WCHAR);
+    file_case   = (const char *)get_req_data() + req->namelen;
+    fullname    = file_case + req->caselen;
 
     if (req->rootdir)
     {
@@ -2982,8 +3026,8 @@ DECL_HANDLER(set_fd_name_info)
 
     if ((fd = get_handle_fd_obj( current->process, req->handle, 0 )))
     {
-        set_fd_name( fd, root_fd, (const char *)get_req_data() + req->namelen,
-                     get_req_data_size() - req->namelen, nt_name, req->link, req->replace );
+        set_fd_name( fd, root_fd, fullname, (const char *)get_req_data() + get_req_data_size() - fullname,
+                     file_case, req->caselen, nt_name, req->link, req->replace );
         release_object( fd );
     }
     if (root_fd) release_object( root_fd );
diff --git a/server/protocol.def b/server/protocol.def
index 2be1658fca2..61d72ae5551 100644
--- a/server/protocol.def
+++ b/server/protocol.def
@@ -3559,9 +3559,11 @@ struct handle_info
     obj_handle_t handle;          /* handle to a file or directory */
     obj_handle_t rootdir;         /* root directory */
     data_size_t  namelen;         /* length of NT name in bytes */
+    data_size_t  caselen;         /* length of the actual case filename */
     int          link;            /* link instead of renaming */
     int          replace;         /* replace an existing file? */
     VARARG(name,unicode_str,namelen); /* NT name */
+    VARARG(actual_case,string,caselen); /* new file name's actual case (without path) */
     VARARG(filename,string);      /* new file name */
 @END
 
-- 
GitLab

https://gitlab.winehq.org/wine/wine/-/merge_requests/246



More information about the wine-devel mailing list