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

Rémi Bernon rbernon at codeweavers.com
Thu May 20 06:49:04 CDT 2021


On 4/30/21 3:58 PM, Gabriel Ivăncescu wrote:
> +/***********************************************************************
> + *           nt_to_unix_file_name_with_actual_case
> + *
> + * Same as nt_to_unix_file_name, but additionally return unix file name
> + * without path, with the actual case from the NT file name.
> + */
> +static NTSTATUS nt_to_unix_file_name_with_actual_case( const OBJECT_ATTRIBUTES *attr, char **name_ret,
> +                                                       char **actual_case_ret, UINT disposition )
> +{
> +    const WCHAR *nt_filename = attr->ObjectName->Buffer;
> +    char *actual_case, *p;
> +    NTSTATUS status;
> +    int len;
> +
> +    /* strip off trailing backslashes; we also accept '/' for unix namespaces */
> +    for (len = attr->ObjectName->Length / sizeof(WCHAR); len != 0; len--)
> +        if (nt_filename[len - 1] != '\\' && nt_filename[len - 1] != '/')
> +            break;
> +
> +    /* get the last component */
> +    for (nt_filename += len; nt_filename != attr->ObjectName->Buffer; nt_filename--)
> +        if (nt_filename[-1] == '\\' || nt_filename[-1] == '/')
> +            break;
> +    len = attr->ObjectName->Buffer + len - nt_filename;
> +
> +    if (!(actual_case = malloc( len * 3 + 1 ))) return STATUS_NO_MEMORY;
> +
> +    status = nt_to_unix_file_name( attr, name_ret, disposition );
> +    if (status != STATUS_SUCCESS && status != STATUS_NO_SUCH_FILE)
> +    {
> +        free( actual_case );
> +        return status;
> +    }
> +
> +    /* special case for '/' root itself, as it has no named components */
> +    for (p = *name_ret; *p == '/'; p++) { }
> +    if (!*p)
> +        strcpy( actual_case, "/" );
> +    else
> +    {
> +        len = ntdll_wcstoumbs( nt_filename, len, actual_case, len * 3, TRUE );
> +        if (len > 0)
> +            actual_case[len] = 0;
> +        else
> +        {
> +            char *p = strrchr( *name_ret, '/' );
> +            p = p ? p + 1 : *name_ret;
> +            strcpy( actual_case, p );
> +        }
> +    }
> +
> +    *actual_case_ret = actual_case;
> +    return status;
> +}
> +
> +

I feel like this is more complicated than it needs to be and that it 
will still miss some cases. For instance, the strrchr is incorrect if 
name_ret has trailing slashes.

Since 405666b736f7e471e301f051cfbe68bcbef7e0f6 there's checks in 
nt_to_unix_file_name to prevent names to have more than one trailing 
slash. However, when the unix name shortcut matches, it may still return 
it with a trailing slash, while when it does it component by component, 
it won't.

So in my opinion, to get the case of the last path component in a way 
that is consistent with the matched unix path, it all should be done in 
lookup_unix_name instead, as in the attached patch (although I did it 
quickly and I'm not sure it's completely correct).

The patch doesn't try to normalize the returned paths (stripping the 
last / when the shortcut is taken), and maybe it would be better to do 
so (for the part below).

> diff --git a/server/fd.c b/server/fd.c
> index 481e9a8..d73ea56 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 *file_case, data_size_t caselen, struct unicode_str nt_name,
> +                         int create_link, int replace )
>   {
> +    size_t pathlen, filenamelen;
>       struct inode *inode;
>       struct stat st, st2;
> -    char *name;
> +    int different_case;
> +    char *name, *p;
>   
>       if (!fd->inode || !fd->unix_name)
>       {
> @@ -2526,6 +2529,29 @@ static void set_fd_name( struct fd *fd, struct fd *root, const char *nameptr, da
>           name = combined_name;
>       }
>   
> +    if (!(p = strrchr( name, '/' ))) p = name;
> +    else if (!*(++p))
> +    {
> +        /* get the last slash that's not trailing, but treat '/' root as a filename */
> +        while (p > name && p[-1] == '/') p--;
> +        p[p <= name] = 0;
> +        while (p > name && p[-1] != '/') p--;
> +    }
> +    pathlen = p - name;
> +    filenamelen = strlen( p );
> +    different_case = (filenamelen != caselen || memcmp( p, file_case, caselen ));
> +

Here I'm not completely sure. Looking for the last path component could 
be made simpler with something like that:

     p = name; pathlen = 0;
     while (*p) if (*p++ == '/' && *p) pathlen = p - name;
     filenamelen = p - name - pathlen;
     p = name + pathlen;

But then I don't know what we can assume, and whether we can trust 
client input to have both name and file_case be consistent together.

Cheers,
-- 
Rémi Bernon <rbernon at codeweavers.com>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: 0001-ntdll-Introduce-new-nt_to_unix_file_name_with_case-h.patch
Type: text/x-patch
Size: 7733 bytes
Desc: not available
URL: <http://www.winehq.org/pipermail/wine-devel/attachments/20210520/06283878/attachment.bin>


More information about the wine-devel mailing list