Fixes a regression with af35741d.
---
The EA Games launcher depends on `GetFinalPathNameByHandleW` returning a path *without* a trailing slash for directories, even if the handle was opened with one. After af35741d, we now use the server's notion of the fd path (via `NtQueryObject(ObjectNameInformation)`), which will include a trailing slash if that's how the path was specified when opened.
A quick test on Windows show that `NtQueryObject(ObjectNameInformation)` for file handles returns a `\Device...` path. (This is why I didn't add tests for its behavior.) For directories, `ObjectNameInformation` will only return a trailing slash for the root of a device - e.g. `\Device\HarddiskVolume3` for `C:`, but `\Device\HarddiskVolume3\Windows` for `C:\Windows`.
Since our `NtQueryObject(ObjectNameInformation)` already doesn't match native (we return `??` paths), arguably this change should be made in `GetFinalPathNameByHandleW`. It seemed cleaner in the server though, as we immediately know whether a path points to a directory.
From: Tim Clem tclem@codeweavers.com
Fixes a regression with af35741d. --- dlls/kernel32/tests/file.c | 24 ++++++++++++++++++++++++ server/fd.c | 22 +++++++++++++++++----- 2 files changed, 41 insertions(+), 5 deletions(-)
diff --git a/dlls/kernel32/tests/file.c b/dlls/kernel32/tests/file.c index db3ffc655ce..f034bd4f60f 100644 --- a/dlls/kernel32/tests/file.c +++ b/dlls/kernel32/tests/file.c @@ -5419,6 +5419,30 @@ static void test_GetFinalPathNameByHandleW(void) wine_dbgstr_w(nt_path), wine_dbgstr_w(result_path));
CloseHandle(file); + + /* Roots of drives should come back with a trailing slash. */ + file = CreateFileW(L"C:\", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0); + ok(file != INVALID_HANDLE_VALUE, "CreateFileW error %lu\n", GetLastError()); + memset(result_path, 0x11, sizeof(result_path)); + count = pGetFinalPathNameByHandleW(file, result_path, MAX_PATH, FILE_NAME_NORMALIZED | VOLUME_NAME_DOS); + lstrcpyW(dos_path, L"\\?\C:\"); + ok(count == lstrlenW(dos_path), "Expected length %u, got %lu\n", lstrlenW(dos_path), count); + ok(lstrcmpiW(dos_path, result_path) == 0, "Expected %s, got %s\n", + wine_dbgstr_w(dos_path), wine_dbgstr_w(result_path)); + CloseHandle(file); + + /* Other directories should not have a trailing slash. */ + file = CreateFileW(L"C:\windows\", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0); + ok(file != INVALID_HANDLE_VALUE, "CreateFileW error %lu\n", GetLastError()); + memset(result_path, 0x11, sizeof(result_path)); + count = pGetFinalPathNameByHandleW(file, result_path, MAX_PATH, FILE_NAME_NORMALIZED | VOLUME_NAME_DOS); + lstrcpyW(dos_path, L"\\?\C:\windows"); + ok(count == lstrlenW(dos_path), "Expected length %u, got %lu\n", lstrlenW(dos_path), count); + ok(lstrcmpiW(dos_path, result_path) == 0, "Expected %s, got %s\n", + wine_dbgstr_w(dos_path), wine_dbgstr_w(result_path)); + CloseHandle(file); }
static void test_SetFileInformationByHandle(void) diff --git a/server/fd.c b/server/fd.c index 6c71336247f..5844b4ba40c 100644 --- a/server/fd.c +++ b/server/fd.c @@ -1879,7 +1879,7 @@ char *dup_fd_name( struct fd *root, const char *name ) return ret; }
-static WCHAR *dup_nt_name( struct fd *root, struct unicode_str name, data_size_t *len ) +static WCHAR *dup_nt_name( struct fd *root, struct unicode_str name, data_size_t *len, int is_dir ) { WCHAR *ptr, *ret;
@@ -1887,7 +1887,8 @@ static WCHAR *dup_nt_name( struct fd *root, struct unicode_str name, data_size_t { *len = name.len; if (!name.len) return NULL; - return memdup( name.str, name.len ); + ret = memdup( name.str, name.len ); + goto done; } if (!root->nt_namelen) return NULL;
@@ -1904,6 +1905,17 @@ static WCHAR *dup_nt_name( struct fd *root, struct unicode_str name, data_size_t ptr = mem_append( ptr, name.str, name.len ); *len = (ptr - ret) * sizeof(WCHAR); } + +done: + /* strip trailing slashes on non-root directories */ + ptr = ret + (*len / sizeof(WCHAR)) - 1; + if (is_dir && *ptr == '\' && + (*len != 7 * sizeof(WCHAR) /* "??\X:" */ || ptr[-1] != ':')) + { + *ptr = 0; + *len -= sizeof(WCHAR); + } + return ret; }
@@ -1993,9 +2005,9 @@ struct fd *open_fd( struct fd *root, const char *name, struct unicode_str nt_nam } }
- fd->nt_name = dup_nt_name( root, nt_name, &fd->nt_namelen ); - fd->unix_name = NULL; fstat( fd->unix_fd, &st ); + fd->unix_name = NULL; + fd->nt_name = dup_nt_name( root, nt_name, &fd->nt_namelen, S_ISDIR(st.st_mode) ); *mode = st.st_mode;
/* only bother with an inode for normal files and directories */ @@ -2717,7 +2729,7 @@ static void set_fd_name( struct fd *fd, struct fd *root, const char *nameptr, da }
free( fd->nt_name ); - fd->nt_name = dup_nt_name( root, nt_name, &fd->nt_namelen ); + fd->nt_name = dup_nt_name( root, nt_name, &fd->nt_namelen, S_ISDIR(st.st_mode) ); free( fd->unix_name ); fd->closed->unix_name = fd->unix_name = realpath( name, NULL ); free( name );
This should most likely be done client-side. In general the server doesn't interpret the NT path, it just passes it through.