Renaming a file or directory from e.g. foobar to FooBar (or any other case change) should work, like on Windows, instead of being a no-op. Clobbering an existing file must also respect the new case.
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=46203 Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/kernel32/tests/file.c | 4 +- dlls/ntdll/tests/file.c | 4 +- dlls/ntdll/unix/file.c | 14 ++++-- server/fd.c | 100 +++++++++++++++++++++++++------------ server/protocol.def | 2 + 5 files changed, 84 insertions(+), 40 deletions(-)
diff --git a/dlls/kernel32/tests/file.c b/dlls/kernel32/tests/file.c index 8560524..9ca56e1 100644 --- a/dlls/kernel32/tests/file.c +++ b/dlls/kernel32/tests/file.c @@ -2040,7 +2040,7 @@ static void test_MoveFileA(void) ok(hfile != INVALID_HANDLE_VALUE, "FindFirstFileA: failed, error %d\n", GetLastError()); if (hfile != INVALID_HANDLE_VALUE) { - todo_wine ok(!lstrcmpA(strrchr(tempdir, '\') + 1, find_data.cFileName), + ok(!lstrcmpA(strrchr(tempdir, '\') + 1, find_data.cFileName), "MoveFile failed to change casing on same file: got %s\n", find_data.cFileName); } CloseHandle(hfile); @@ -2085,7 +2085,7 @@ static void test_MoveFileA(void) ok(hfile != INVALID_HANDLE_VALUE, "FindFirstFileA: failed, error %d\n", GetLastError()); if (hfile != INVALID_HANDLE_VALUE) { - todo_wine ok(!lstrcmpA(strrchr(tempdir, '\') + 1, find_data.cFileName), + ok(!lstrcmpA(strrchr(tempdir, '\') + 1, find_data.cFileName), "MoveFile failed to change casing on same directory: got %s\n", find_data.cFileName); } CloseHandle(hfile); diff --git a/dlls/ntdll/tests/file.c b/dlls/ntdll/tests/file.c index b7b4636..591f425 100644 --- a/dlls/ntdll/tests/file.c +++ b/dlls/ntdll/tests/file.c @@ -2315,7 +2315,7 @@ static void test_file_link_information(void) ok(handle != INVALID_HANDLE_VALUE, "FindFirstFileW: failed, error %d\n", GetLastError()); if (handle != INVALID_HANDLE_VALUE) { - todo_wine ok(!lstrcmpW(wcsrchr(newpath, '\') + 1, find_data.cFileName), + ok(!lstrcmpW(wcsrchr(newpath, '\') + 1, find_data.cFileName), "Link did not change casing on existing target file: got %s\n", wine_dbgstr_w(find_data.cFileName)); }
@@ -2900,7 +2900,7 @@ static void test_file_link_information(void) ok(handle != INVALID_HANDLE_VALUE, "FindFirstFileW: failed, error %d\n", GetLastError()); if (handle != INVALID_HANDLE_VALUE) { - todo_wine ok(!lstrcmpW(wcsrchr(oldpath, '\') + 1, find_data.cFileName), + ok(!lstrcmpW(wcsrchr(oldpath, '\') + 1, find_data.cFileName), "Link did not change casing on same file: got %s\n", wine_dbgstr_w(find_data.cFileName)); }
diff --git a/dlls/ntdll/unix/file.c b/dlls/ntdll/unix/file.c index e649745..9b106c7 100644 --- a/dlls/ntdll/unix/file.c +++ b/dlls/ntdll/unix/file.c @@ -4547,8 +4547,8 @@ NTSTATUS WINAPI NtSetInformationFile( HANDLE handle, IO_STATUS_BLOCK *io, { FILE_RENAME_INFORMATION *info = ptr; UNICODE_STRING name_str, redir; + char *unix_name, *file_case; OBJECT_ATTRIBUTES attr; - char *unix_name;
name_str.Buffer = info->FileName; name_str.Length = info->FileNameLength; @@ -4556,7 +4556,7 @@ 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 ); + io->u.Status = nt_to_unix_file_name_with_case( &attr, &unix_name, &file_case, FILE_OPEN_IF ); if (io->u.Status == STATUS_SUCCESS || io->u.Status == STATUS_NO_SUCH_FILE) { SERVER_START_REQ( set_fd_name_info ) @@ -4564,15 +4564,18 @@ NTSTATUS WINAPI NtSetInformationFile( HANDLE handle, IO_STATUS_BLOCK *io, req->handle = wine_server_obj_handle( handle ); req->rootdir = wine_server_obj_handle( attr.RootDirectory ); req->namelen = attr.ObjectName->Length; + req->caselen = file_case ? strlen( file_case ) : 0; req->link = FALSE; req->replace = info->ReplaceIfExists; wine_server_add_data( req, attr.ObjectName->Buffer, attr.ObjectName->Length ); + wine_server_add_data( req, file_case, req->caselen ); wine_server_add_data( req, unix_name, strlen(unix_name) ); io->u.Status = wine_server_call( req ); } SERVER_END_REQ;
free( unix_name ); + free( file_case ); } free( redir.Buffer ); } @@ -4584,8 +4587,8 @@ NTSTATUS WINAPI NtSetInformationFile( HANDLE handle, IO_STATUS_BLOCK *io, { FILE_LINK_INFORMATION *info = ptr; UNICODE_STRING name_str, redir; + char *unix_name, *file_case; OBJECT_ATTRIBUTES attr; - char *unix_name;
name_str.Buffer = info->FileName; name_str.Length = info->FileNameLength; @@ -4593,7 +4596,7 @@ 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 ); + io->u.Status = nt_to_unix_file_name_with_case( &attr, &unix_name, &file_case, FILE_OPEN_IF ); if (io->u.Status == STATUS_SUCCESS || io->u.Status == STATUS_NO_SUCH_FILE) { SERVER_START_REQ( set_fd_name_info ) @@ -4601,15 +4604,18 @@ NTSTATUS WINAPI NtSetInformationFile( HANDLE handle, IO_STATUS_BLOCK *io, req->handle = wine_server_obj_handle( handle ); req->rootdir = wine_server_obj_handle( attr.RootDirectory ); req->namelen = attr.ObjectName->Length; + req->caselen = file_case ? strlen( file_case ) : 0; req->link = TRUE; req->replace = info->ReplaceIfExists; wine_server_add_data( req, attr.ObjectName->Buffer, attr.ObjectName->Length ); + wine_server_add_data( req, file_case, req->caselen ); wine_server_add_data( req, unix_name, strlen(unix_name) ); io->u.Status = wine_server_call( req ); } SERVER_END_REQ;
free( unix_name ); + free( file_case ); } free( redir.Buffer ); } diff --git a/server/fd.c b/server/fd.c index e7253ec..dcb7361 100644 --- a/server/fd.c +++ b/server/fd.c @@ -2485,11 +2485,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 = 0, filenamelen; struct inode *inode; struct stat st, st2; - char *name; + int different_case; + char *name, *p;
if (!fd->inode || !fd->unix_name) { @@ -2523,6 +2526,22 @@ static void set_fd_name( struct fd *fd, struct fd *root, const char *nameptr, da name = combined_name; }
+ for (p = name; *p;) if (*p++ == '/' && *p) pathlen = p - name; + filenamelen = p - name - pathlen; + for (p = name + pathlen; filenamelen && p[filenamelen - 1] == '/'; filenamelen--) { } + different_case = caselen && (filenamelen != caselen || memcmp( p, file_case, caselen )); + + if (filenamelen < caselen) + { + p = realloc( name, pathlen + caselen + 1 ); + if (!p) + { + set_error( STATUS_NO_MEMORY ); + goto failed; + } + name = p; + } + /* when creating a hard link, source cannot be a dir */ if (create_link && !fstat( fd->unix_fd, &st ) && S_ISDIR( st.st_mode )) { @@ -2535,47 +2554,61 @@ 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 (!different_case) + { + free( name ); + return; + }
- if (!replace) - { - set_error( STATUS_OBJECT_NAME_COLLISION ); - goto failed; + /* creating a link with a different case on itself renames the file */ + create_link = 0; } - - /* 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_case) + { + if (unlink( name )) + { + file_set_error(); + goto failed; + } } } }
+ if (different_case) + { + memcpy( name + pathlen, file_case, caselen ); + name[pathlen + caselen] = 0; + } + if (create_link) { if (link( fd->unix_name, name )) @@ -2876,16 +2909,19 @@ DECL_HANDLER(set_fd_disp_info) /* set fd name information */ DECL_HANDLER(set_fd_name_info) { + const char *fullname, *file_case; 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); + file_case = (const char *)get_req_data() + req->namelen; + fullname = file_case + req->caselen;
if (req->rootdir) { @@ -2899,8 +2935,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, + file_case, 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 6d8208b..fdf491d 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 actual case filename */ int link; /* link instead of renaming */ int replace; /* replace an existing file? */ VARARG(name,unicode_str,namelen); /* NT name */ + VARARG(actual_case,string,caselen); /* new file name's actual case (without path) */ VARARG(filename,string); /* new file name */ @END