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

Gabriel Ivăncescu gabrielopcode at gmail.com
Wed Apr 21 07:42:43 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.

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 a field `caselen` which is the
length of this data.

 dlls/ntdll/unix/file.c | 56 ++++++++++++++++++++++--
 server/fd.c            | 97 ++++++++++++++++++++++++++++--------------
 server/protocol.def    |  2 +
 3 files changed, 118 insertions(+), 37 deletions(-)

diff --git a/dlls/ntdll/unix/file.c b/dlls/ntdll/unix/file.c
index 488f748..d8fdb27 100644
--- a/dlls/ntdll/unix/file.c
+++ b/dlls/ntdll/unix/file.c
@@ -4520,9 +4520,11 @@ NTSTATUS WINAPI NtSetInformationFile( HANDLE handle, IO_STATUS_BLOCK *io,
         if (len >= sizeof(FILE_RENAME_INFORMATION))
         {
             FILE_RENAME_INFORMATION *info = ptr;
+            int nt_filenamelen, casinglen = 0;
             UNICODE_STRING name_str, redir;
+            const WCHAR *nt_filename;
+            char *unix_name, *casing;
             OBJECT_ATTRIBUTES attr;
-            char *unix_name;
 
             name_str.Buffer = info->FileName;
             name_str.Length = info->FileNameLength;
@@ -4530,17 +4532,38 @@ NTSTATUS WINAPI NtSetInformationFile( HANDLE handle, IO_STATUS_BLOCK *io,
             InitializeObjectAttributes( &attr, &name_str, OBJ_CASE_INSENSITIVE, info->RootDirectory, NULL );
             get_redirect( &attr, &redir );
 
-            io->u.Status = nt_to_unix_file_name( &attr, &unix_name, FILE_OPEN_IF );
+            nt_filename = attr.ObjectName->Buffer + attr.ObjectName->Length / sizeof(WCHAR);
+            for (; nt_filename != attr.ObjectName->Buffer; nt_filename--)
+                if (nt_filename[-1] == '\\')
+                    break;
+            nt_filenamelen = attr.ObjectName->Buffer + attr.ObjectName->Length / sizeof(WCHAR) - nt_filename;
+
+            if ((casing = malloc( nt_filenamelen * 3 )))
+            {
+                casinglen = ntdll_wcstoumbs( nt_filename, nt_filenamelen, casing, nt_filenamelen * 3, TRUE );
+                io->u.Status = nt_to_unix_file_name( &attr, &unix_name, FILE_OPEN_IF );
+            }
+            else
+                io->u.Status = STATUS_NO_MEMORY;
+
             if (io->u.Status == STATUS_SUCCESS || io->u.Status == STATUS_NO_SUCH_FILE)
             {
+                if (casinglen <= 0)
+                {
+                    casing = strrchr( unix_name, '/' );
+                    casing = casing ? casing + 1 : unix_name;
+                    casinglen = strlen( casing );
+                }
                 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  = casinglen;
                     req->link     = FALSE;
                     req->replace  = info->ReplaceIfExists;
                     wine_server_add_data( req, attr.ObjectName->Buffer, attr.ObjectName->Length );
+                    wine_server_add_data( req, casing, casinglen );
                     wine_server_add_data( req, unix_name, strlen(unix_name) );
                     io->u.Status = wine_server_call( req );
                 }
@@ -4548,6 +4571,7 @@ NTSTATUS WINAPI NtSetInformationFile( HANDLE handle, IO_STATUS_BLOCK *io,
 
                 free( unix_name );
             }
+            free( casing );
             free( redir.Buffer );
         }
         else io->u.Status = STATUS_INVALID_PARAMETER_3;
@@ -4557,9 +4581,11 @@ NTSTATUS WINAPI NtSetInformationFile( HANDLE handle, IO_STATUS_BLOCK *io,
         if (len >= sizeof(FILE_LINK_INFORMATION))
         {
             FILE_LINK_INFORMATION *info = ptr;
+            int nt_filenamelen, casinglen = 0;
             UNICODE_STRING name_str, redir;
+            const WCHAR *nt_filename;
+            char *unix_name, *casing;
             OBJECT_ATTRIBUTES attr;
-            char *unix_name;
 
             name_str.Buffer = info->FileName;
             name_str.Length = info->FileNameLength;
@@ -4567,17 +4593,38 @@ NTSTATUS WINAPI NtSetInformationFile( HANDLE handle, IO_STATUS_BLOCK *io,
             InitializeObjectAttributes( &attr, &name_str, OBJ_CASE_INSENSITIVE, info->RootDirectory, NULL );
             get_redirect( &attr, &redir );
 
-            io->u.Status = nt_to_unix_file_name( &attr, &unix_name, FILE_OPEN_IF );
+            nt_filename = attr.ObjectName->Buffer + attr.ObjectName->Length / sizeof(WCHAR);
+            for (; nt_filename != attr.ObjectName->Buffer; nt_filename--)
+                if (nt_filename[-1] == '\\')
+                    break;
+            nt_filenamelen = attr.ObjectName->Buffer + attr.ObjectName->Length / sizeof(WCHAR) - nt_filename;
+
+            if ((casing = malloc( nt_filenamelen * 3 )))
+            {
+                casinglen = ntdll_wcstoumbs( nt_filename, nt_filenamelen, casing, nt_filenamelen * 3, TRUE );
+                io->u.Status = nt_to_unix_file_name( &attr, &unix_name, FILE_OPEN_IF );
+            }
+            else
+                io->u.Status = STATUS_NO_MEMORY;
+
             if (io->u.Status == STATUS_SUCCESS || io->u.Status == STATUS_NO_SUCH_FILE)
             {
+                if (casinglen <= 0)
+                {
+                    casing = strrchr( unix_name, '/' );
+                    casing = casing ? casing + 1 : unix_name;
+                    casinglen = strlen( casing );
+                }
                 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  = casinglen;
                     req->link     = TRUE;
                     req->replace  = info->ReplaceIfExists;
                     wine_server_add_data( req, attr.ObjectName->Buffer, attr.ObjectName->Length );
+                    wine_server_add_data( req, casing, casinglen );
                     wine_server_add_data( req, unix_name, strlen(unix_name) );
                     io->u.Status  = wine_server_call( req );
                 }
@@ -4585,6 +4632,7 @@ NTSTATUS WINAPI NtSetInformationFile( HANDLE handle, IO_STATUS_BLOCK *io,
 
                 free( unix_name );
             }
+            free( casing );
             free( redir.Buffer );
         }
         else io->u.Status = STATUS_INVALID_PARAMETER_3;
diff --git a/server/fd.c b/server/fd.c
index 481e9a8..47fab1e 100644
--- a/server/fd.c
+++ b/server/fd.c
@@ -2488,11 +2488,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 *casing, data_size_t casinglen, struct unicode_str nt_name,
+                         int create_link, int replace )
 {
+    size_t pathlen, filenamelen;
+    int different_casing;
     struct inode *inode;
     struct stat st, st2;
-    char *name;
+    char *name, *tmp;
 
     if (!fd->inode || !fd->unix_name)
     {
@@ -2526,6 +2529,23 @@ static void set_fd_name( struct fd *fd, struct fd *root, const char *nameptr, da
         name = combined_name;
     }
 
+    tmp = strrchr( name, '/' );
+    tmp = tmp ? tmp + 1 : name;
+    pathlen = tmp - name;
+    filenamelen = strlen( tmp );
+    different_casing = (filenamelen != casinglen || memcmp( tmp, casing, casinglen ));
+
+    if (filenamelen < casinglen)
+    {
+        tmp = realloc( name, pathlen + casinglen + 1 );
+        if (!tmp)
+        {
+            set_error( STATUS_NO_MEMORY );
+            goto failed;
+        }
+        name = tmp;
+    }
+
     /* when creating a hard link, source cannot be a dir */
     if (create_link && !fstat( fd->unix_fd, &st ) && S_ISDIR( st.st_mode ))
     {
@@ -2538,47 +2558,55 @@ 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 (!different_casing)
+            {
+                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;
             }
-        }
 
-        /* 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_casing)
+            {
+                if (unlink( name ))
+                {
+                    file_set_error();
+                    goto failed;
+                }
             }
         }
     }
 
+    memcpy( name + pathlen, casing, casinglen );
+    name[pathlen + casinglen] = 0;
+
     if (create_link)
     {
         if (link( fd->unix_name, name ))
@@ -2879,16 +2907,19 @@ DECL_HANDLER(set_fd_disp_info)
 /* set fd name information */
 DECL_HANDLER(set_fd_name_info)
 {
+    const char *fullname, *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->caselen)
     {
         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;
+    fullname    = casing + req->caselen;
 
     if (req->rootdir)
     {
@@ -2902,8 +2933,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,
+                     casing, 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 9ea6967..02e5ab0 100644
--- a/server/protocol.def
+++ b/server/protocol.def
@@ -3531,9 +3531,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 casing filename */
     int          link;            /* link instead of renaming */
     int          replace;         /* replace an existing file? */
     VARARG(name,unicode_str,namelen); /* NT name */
+    VARARG(casing,string,caselen);/* 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