[PATCH v4 1/2] ntdll: Allow renaming a file/directory to a different casing of itself.
Gabriel Ivăncescu
gabrielopcode at gmail.com
Tue Apr 20 10:04:37 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 `casinglen` which is the
length of this data.
dlls/ntdll/unix/file.c | 59 ++++++++++++++++++++------
server/fd.c | 95 +++++++++++++++++++++++++++++-------------
server/protocol.def | 2 +
3 files changed, 114 insertions(+), 42 deletions(-)
diff --git a/dlls/ntdll/unix/file.c b/dlls/ntdll/unix/file.c
index 488f748..0cf1a7f 100644
--- a/dlls/ntdll/unix/file.c
+++ b/dlls/ntdll/unix/file.c
@@ -2800,6 +2800,31 @@ void init_files(void)
}
+/***********************************************************************
+ * get_nt_file_name_unix_casing
+ *
+ * Get the Unix file name with the same casing as the NT file name (without path).
+ */
+static unsigned int get_nt_file_name_unix_casing(const OBJECT_ATTRIBUTES *attr, char **casing)
+{
+ const WCHAR *filename = attr->ObjectName->Buffer + attr->ObjectName->Length / sizeof(WCHAR);
+ int len, casinglen = 0;
+
+ /* NT name may not be NUL terminated; look for last \ character */
+ for (; filename != attr->ObjectName->Buffer; filename--)
+ if (filename[-1] == '\\')
+ break;
+ len = attr->ObjectName->Buffer + attr->ObjectName->Length / sizeof(WCHAR) - filename;
+
+ if ((*casing = malloc( len * 3 )))
+ {
+ casinglen = ntdll_wcstoumbs( filename, len, *casing, len * 3, TRUE );
+ if (casinglen < 0) casinglen = 0;
+ }
+ return casinglen;
+}
+
+
/******************************************************************************
* get_dos_device
*
@@ -4521,8 +4546,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;
@@ -4533,20 +4558,25 @@ NTSTATUS WINAPI NtSetInformationFile( HANDLE handle, IO_STATUS_BLOCK *io,
io->u.Status = nt_to_unix_file_name( &attr, &unix_name, FILE_OPEN_IF );
if (io->u.Status == STATUS_SUCCESS || io->u.Status == STATUS_NO_SUCH_FILE)
{
+ unsigned int casinglen = get_nt_file_name_unix_casing( &attr, &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->link = FALSE;
- req->replace = info->ReplaceIfExists;
+ req->handle = wine_server_obj_handle( handle );
+ req->rootdir = wine_server_obj_handle( attr.RootDirectory );
+ req->namelen = attr.ObjectName->Length;
+ req->casinglen = 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 );
}
SERVER_END_REQ;
free( unix_name );
+ free( casing );
}
free( redir.Buffer );
}
@@ -4558,8 +4588,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;
@@ -4570,20 +4600,25 @@ NTSTATUS WINAPI NtSetInformationFile( HANDLE handle, IO_STATUS_BLOCK *io,
io->u.Status = nt_to_unix_file_name( &attr, &unix_name, FILE_OPEN_IF );
if (io->u.Status == STATUS_SUCCESS || io->u.Status == STATUS_NO_SUCH_FILE)
{
+ unsigned int casinglen = get_nt_file_name_unix_casing( &attr, &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->link = TRUE;
- req->replace = info->ReplaceIfExists;
+ req->handle = wine_server_obj_handle( handle );
+ req->rootdir = wine_server_obj_handle( attr.RootDirectory );
+ req->namelen = attr.ObjectName->Length;
+ req->casinglen = 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 );
}
SERVER_END_REQ;
free( unix_name );
+ free( casing );
}
free( redir.Buffer );
}
diff --git a/server/fd.c b/server/fd.c
index 481e9a8..0b4c0a1 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 )
{
+ char *name, *filename;
+ int different_casing;
struct inode *inode;
struct stat st, st2;
- char *name;
+ size_t filenamelen;
if (!fd->inode || !fd->unix_name)
{
@@ -2533,50 +2536,79 @@ static void set_fd_name( struct fd *fd, struct fd *root, const char *nameptr, da
goto failed;
}
+ filename = strrchr( name, '/' );
+ filename = filename ? filename + 1 : name;
+ filenamelen = strlen( filename );
+ different_casing = casinglen && (filenamelen != casinglen || memcmp( filename, casing, casinglen ));
+
if (!stat( name, &st ))
{
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;
}
+
+ /* 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)
+ {
+ 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;
+ }
+ }
}
+ }
- /* link() expects that the target doesn't exist */
- /* rename() cannot replace files with directories */
- if (create_link || S_ISDIR( st2.st_mode ))
+ if (different_casing)
+ {
+ size_t pathlen = filename - name;
+ if (filenamelen < casinglen)
{
- if (unlink( name ))
+ char *new_name = mem_alloc( pathlen + casinglen + 1 );
+ if (!new_name)
{
- file_set_error();
+ set_error( STATUS_NO_MEMORY );
goto failed;
}
+ memcpy( new_name, name, pathlen );
+ free( name );
+ name = new_name;
}
+ memcpy(name + pathlen, casing, casinglen);
+ name[pathlen + casinglen] = 0;
}
if (create_link)
@@ -2879,16 +2911,19 @@ DECL_HANDLER(set_fd_disp_info)
/* set fd name information */
DECL_HANDLER(set_fd_name_info)
{
+ const char *filename, *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->casinglen)
{
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;
+ filename = casing + req->casinglen;
if (req->rootdir)
{
@@ -2902,8 +2937,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, filename, (const char *)get_req_data() + get_req_data_size() - filename,
+ casing, req->casinglen, 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..8560469 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 casinglen; /* 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,casinglen); /* 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