[PATCH v7 4/4] ntdll: Allow renaming a file/directory to a different casing of itself.
Gabriel Ivăncescu
gabrielopcode at gmail.com
Fri Apr 23 10:52:29 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/kernel32/tests/file.c | 4 +-
dlls/ntdll/tests/file.c | 4 +-
dlls/ntdll/unix/file.c | 58 ++++++++++++++++++++--
server/fd.c | 98 +++++++++++++++++++++++++-------------
server/protocol.def | 2 +
5 files changed, 126 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 d469b44..49f4e34 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 e32de57..908beb6 100644
--- a/dlls/ntdll/unix/file.c
+++ b/dlls/ntdll/unix/file.c
@@ -3400,6 +3400,50 @@ NTSTATUS nt_to_unix_file_name( const OBJECT_ATTRIBUTES *attr, char **name_ret, U
}
+/***********************************************************************
+ * nt_to_unix_file_name_with_casing
+ *
+ * Same as nt_to_unix_file_name, but additionally return unix file name
+ * without path, with the original casing from the NT file name.
+ */
+static NTSTATUS nt_to_unix_file_name_with_casing( const OBJECT_ATTRIBUTES *attr, char **name_ret,
+ char **casing_ret, UINT disposition )
+{
+ int len = attr->ObjectName->Length / sizeof(WCHAR);
+ const WCHAR *nt_filename;
+ NTSTATUS status;
+ char *casing;
+
+ /* NT name may not be NUL terminated; look for last \ character */
+ for (nt_filename = attr->ObjectName->Buffer + len; nt_filename != attr->ObjectName->Buffer; nt_filename--)
+ if (nt_filename[-1] == '\\')
+ break;
+ len = attr->ObjectName->Buffer + len - nt_filename;
+
+ if (!(casing = 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( casing );
+ return status;
+ }
+
+ len = ntdll_wcstoumbs( nt_filename, len, casing, len * 3, TRUE );
+ if (len > 0)
+ casing[len] = 0;
+ else
+ {
+ char *p = strrchr( *name_ret, '/' );
+ p = p ? p + 1 : *name_ret;
+ strcpy( casing, p );
+ }
+
+ *casing_ret = casing;
+ return status;
+}
+
+
/******************************************************************************
* wine_nt_to_unix_file_name
*
@@ -4522,8 +4566,8 @@ NTSTATUS WINAPI NtSetInformationFile( HANDLE handle, IO_STATUS_BLOCK *io,
{
FILE_RENAME_INFORMATION *info = ptr;
UNICODE_STRING name_str, redir;
+ char *unix_name, *casing;
OBJECT_ATTRIBUTES attr;
- char *unix_name;
name_str.Buffer = info->FileName;
name_str.Length = info->FileNameLength;
@@ -4531,7 +4575,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_casing( &attr, &unix_name, &casing, FILE_OPEN_IF );
if (io->u.Status == STATUS_SUCCESS || io->u.Status == STATUS_NO_SUCH_FILE)
{
SERVER_START_REQ( set_fd_name_info )
@@ -4539,15 +4583,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 = strlen( casing );
req->link = FALSE;
req->replace = info->ReplaceIfExists;
wine_server_add_data( req, attr.ObjectName->Buffer, attr.ObjectName->Length );
+ wine_server_add_data( req, casing, 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( casing );
}
free( redir.Buffer );
}
@@ -4559,8 +4606,8 @@ NTSTATUS WINAPI NtSetInformationFile( HANDLE handle, IO_STATUS_BLOCK *io,
{
FILE_LINK_INFORMATION *info = ptr;
UNICODE_STRING name_str, redir;
+ char *unix_name, *casing;
OBJECT_ATTRIBUTES attr;
- char *unix_name;
name_str.Buffer = info->FileName;
name_str.Length = info->FileNameLength;
@@ -4568,7 +4615,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_casing( &attr, &unix_name, &casing, FILE_OPEN_IF );
if (io->u.Status == STATUS_SUCCESS || io->u.Status == STATUS_NO_SUCH_FILE)
{
SERVER_START_REQ( set_fd_name_info )
@@ -4576,15 +4623,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 = strlen( casing );
req->link = TRUE;
req->replace = info->ReplaceIfExists;
wine_server_add_data( req, attr.ObjectName->Buffer, attr.ObjectName->Length );
+ wine_server_add_data( req, casing, 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( casing );
}
free( redir.Buffer );
}
diff --git a/server/fd.c b/server/fd.c
index 481e9a8..0f7067d 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,58 @@ 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_casing)
+ {
+ free( name );
+ return;
+ }
- if (!replace)
- {
- set_error( STATUS_OBJECT_NAME_COLLISION );
- goto failed;
+ /* creating a link with a different casing 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_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 +2910,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 +2936,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