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

Rémi Bernon rbernon at codeweavers.com
Wed Apr 21 13:41:11 CDT 2021


On 4/21/21 2:42 PM, Gabriel Ivăncescu wrote:
> 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;

This shouldn't free if the fallback path was taken.

> @@ -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;

I'm not sure we want to support a different casing here, I think there 
are some additional consideration to have on the server side code:

We don't want to create link of some file to another casing of itself, 
but instead probably rename the link to another casing if it already exists?

I think we should just use the strrchr fallback all the time here.


However, if we want to support proper casing for FileLinkInformation 
too, it would probably deserve a few tests first to see what this means 
for links.

And the implementation could then come in a different patch to make it 
clearer. In which case, assuming we need the same kind of logic to send 
the proper casing, a separate helper would indeed be useful.


In any case, I think it would also be nice (if possible?) to have the 
tests first in the series with todo_wine, removed later in the patch 
that fixes it.
-- 
Rémi Bernon <rbernon at codeweavers.com>



More information about the wine-devel mailing list