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

Gabriel Ivăncescu gabrielopcode at gmail.com
Fri Apr 9 10:47:52 CDT 2021


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

Note that we only send the different casing (and thus casing_len != 0) if
it's actually different. This is important to preserve atomicity of rename()
in wineserver in all other cases that don't require this special casing
handling. The only extra time rename() is not atomic is when an existing
file is clobbered with a different casing, since we have to unlink the old
file first, then rename the new file into the new casing, but there's no
way around that...

Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=46203
Signed-off-by: Gabriel Ivăncescu <gabrielopcode at gmail.com>
---

We just prepend the unix casing filename (without path) to the wineserver
data before the actual unix filename, and add an extra (optional) field
`casing_len` which is the length of this data. If there's no casing, this
is zero and the behavior is identical to before, which is the default.

 dlls/ntdll/unix/file.c | 46 ++++++++++++++++++---
 server/fd.c            | 94 +++++++++++++++++++++++++++++-------------
 server/protocol.def    |  2 +
 3 files changed, 107 insertions(+), 35 deletions(-)

diff --git a/dlls/ntdll/unix/file.c b/dlls/ntdll/unix/file.c
index 9ee5e84..fa604a1 100644
--- a/dlls/ntdll/unix/file.c
+++ b/dlls/ntdll/unix/file.c
@@ -4508,8 +4508,9 @@ NTSTATUS WINAPI NtSetInformationFile( HANDLE handle, IO_STATUS_BLOCK *io,
         {
             FILE_RENAME_INFORMATION *info = ptr;
             UNICODE_STRING name_str, nt_name = { 0 };
+            char *unix_name, *casing = NULL;
             OBJECT_ATTRIBUTES attr;
-            char *unix_name;
+            int casing_len = 0;
 
             name_str.Buffer = info->FileName;
             name_str.Length = info->FileNameLength;
@@ -4524,19 +4525,52 @@ NTSTATUS WINAPI NtSetInformationFile( HANDLE handle, IO_STATUS_BLOCK *io,
             if (io->u.Status != STATUS_SUCCESS && io->u.Status != STATUS_NO_SUCH_FILE)
                 break;
 
+            /* get the casing of the last component if target exists */
+            if (io->u.Status != STATUS_NO_SUCH_FILE)
+            {
+                const WCHAR *nt_filename = name_str.Buffer + name_str.Length / sizeof(WCHAR);
+                size_t nt_filename_len;
+
+                /* name_str is not NUL terminated; look for last \ character */
+                for (; nt_filename != name_str.Buffer; nt_filename--)
+                    if (nt_filename[-1] == '\\')
+                        break;
+                nt_filename_len = name_str.Buffer + name_str.Length / sizeof(WCHAR) - nt_filename;
+
+                /* see if only casing differs from nt_name and update it */
+                if (nt_name.Length / sizeof(WCHAR) >= nt_filename_len)
+                {
+                    WCHAR *p = nt_name.Buffer + nt_name.Length / sizeof(WCHAR) - nt_filename_len;
+
+                    if ((p == nt_name.Buffer || p[-1] == '\\') &&
+                        memcmp( p, nt_filename, nt_filename_len * sizeof(WCHAR) ) &&
+                        !wcsnicmp( p, nt_filename, nt_filename_len ) &&
+                        (casing = malloc( nt_filename_len * 3 )))
+                    {
+                        casing_len = ntdll_wcstoumbs( nt_filename, nt_filename_len, casing,
+                                                      nt_filename_len * 3, TRUE );
+                        if (casing_len < 0) casing_len = 0;
+                        if (casing_len) memcpy( p, nt_filename, nt_filename_len * sizeof(WCHAR) );
+                    }
+                }
+            }
+
             SERVER_START_REQ( set_fd_name_info )
             {
-                req->handle   = wine_server_obj_handle( handle );
-                req->rootdir  = wine_server_obj_handle( attr.RootDirectory );
-                req->namelen  = nt_name.Length;
-                req->link     = FALSE;
-                req->replace  = info->ReplaceIfExists;
+                req->handle     = wine_server_obj_handle( handle );
+                req->rootdir    = wine_server_obj_handle( attr.RootDirectory );
+                req->namelen    = nt_name.Length;
+                req->casing_len = casing_len;
+                req->link       = FALSE;
+                req->replace    = info->ReplaceIfExists;
                 wine_server_add_data( req, nt_name.Buffer, nt_name.Length );
+                wine_server_add_data( req, casing, casing_len );
                 wine_server_add_data( req, unix_name, strlen(unix_name) );
                 io->u.Status = wine_server_call( req );
             }
             SERVER_END_REQ;
 
+            free( casing );
             free( unix_name );
             free( nt_name.Buffer );
         }
diff --git a/server/fd.c b/server/fd.c
index 481e9a8..502d958 100644
--- a/server/fd.c
+++ b/server/fd.c
@@ -2488,7 +2488,8 @@ 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 *casing, data_size_t casing_len, struct unicode_str nt_name,
+                         int create_link, int replace )
 {
     struct inode *inode;
     struct stat st, st2;
@@ -2538,45 +2539,77 @@ 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 (!replace)
-        {
-            set_error( STATUS_OBJECT_NAME_COLLISION );
-            goto failed;
+            if (!casing_len)
+            {
+                free( name );
+                return;
+            }
         }
-
-        /* 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;
             }
+
+            /* 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)
+                {
+                    set_error( STATUS_ACCESS_DENIED );
+                    goto failed;
+                }
+            }
+
+            /* link() expects that the target doesn't exist */
+            /* rename() cannot replace files with directories */
+            /* we also have to unlink it if target has different casing */
+            if (create_link || S_ISDIR( st2.st_mode ) || casing_len)
+            {
+                if (unlink( name ))
+                {
+                    file_set_error();
+                    goto failed;
+                }
+            }
         }
+    }
+
+    /* replace the last component with its actual casing */
+    if (casing_len)
+    {
+        char *p = strrchr( name, '/' );
+        size_t orig_len, path_len;
+
+        p = p ? p + 1 : name;
+        path_len = p - name;
+        orig_len = strlen(p);
 
-        /* link() expects that the target doesn't exist */
-        /* rename() cannot replace files with directories */
-        if (create_link || S_ISDIR( st2.st_mode ))
+        if (orig_len < casing_len)
         {
-            if (unlink( name ))
+            char *new_name = mem_alloc( path_len + casing_len + 1 );
+            if (!new_name)
             {
-                file_set_error();
+                set_error( STATUS_NO_MEMORY );
                 goto failed;
             }
+            memcpy( new_name, name, path_len );
+            free( name );
+            name = new_name;
         }
+        memcpy(name + path_len, casing, casing_len);
+        name[path_len + casing_len] = 0;
     }
 
     if (create_link)
@@ -2879,16 +2912,19 @@ DECL_HANDLER(set_fd_disp_info)
 /* set fd name information */
 DECL_HANDLER(set_fd_name_info)
 {
+    const char *filename, *casing;
     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->casing_len)
     {
         set_error( STATUS_INVALID_PARAMETER );
         return;
     }
     nt_name.str = get_req_data();
     nt_name.len = (req->namelen / sizeof(WCHAR)) * sizeof(WCHAR);
+    casing      = (const char *)get_req_data() + req->namelen;
+    filename    = casing + req->casing_len;
 
     if (req->rootdir)
     {
@@ -2902,8 +2938,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, filename, (const char *)get_req_data() + get_req_data_size() - filename,
+                     casing, req->casing_len, 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 617818f..d97cd1a 100644
--- a/server/protocol.def
+++ b/server/protocol.def
@@ -3532,9 +3532,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  casing_len;      /* length of the casing filename */
     int          link;            /* link instead of renaming */
     int          replace;         /* replace an existing file? */
     VARARG(name,unicode_str,namelen); /* NT name */
+    VARARG(casing,string,casing_len); /* new file name's actual casing (without path) */
     VARARG(filename,string);      /* new file name */
 @END
 
-- 
2.30.0




More information about the wine-devel mailing list