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