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.~~
Now doing this client-side in `get_nt_and_unix_names`, which intercepts paths before they're handed to the server.
-- v2: ntdll: Remove trailing slashes from the NT names for directories.
From: Tim Clem tclem@codeweavers.com
Fixes a regression with af35741d. --- dlls/kernel32/tests/file.c | 24 ++++++++++++++++++++++++ dlls/ntdll/unix/file.c | 22 ++++++++++++++++++++++ 2 files changed, 46 insertions(+)
diff --git a/dlls/kernel32/tests/file.c b/dlls/kernel32/tests/file.c index 83766dfaaa2..1a5981261a5 100644 --- a/dlls/kernel32/tests/file.c +++ b/dlls/kernel32/tests/file.c @@ -5436,6 +5436,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/dlls/ntdll/unix/file.c b/dlls/ntdll/unix/file.c index 5bed090a45b..a6c60635e33 100644 --- a/dlls/ntdll/unix/file.c +++ b/dlls/ntdll/unix/file.c @@ -4045,8 +4045,30 @@ NTSTATUS get_nt_and_unix_names( OBJECT_ATTRIBUTES *attr, UNICODE_STRING *nt_name if (status && status != STATUS_NO_SUCH_FILE) TRACE( "%s -> ret %x\n", debugstr_us(orig), status ); else + { + /* Trim trailing slashes from anything other than roots of drives. */ + USHORT nt_len = attr->ObjectName->Length / sizeof(WCHAR); + if (nt_len > 7 /* "??\X:" */ && attr->ObjectName->Buffer[nt_len - 1] == '\') + { + if (nt_name->Buffer) + { + nt_name->Buffer[nt_len - 1] = 0; + nt_name->Length -= sizeof(WCHAR); + } + else + { + WCHAR *buffer = malloc( nt_len * sizeof(WCHAR) ); + if (!buffer) return STATUS_NO_MEMORY; + memcpy( buffer, attr->ObjectName->Buffer, (nt_len - 1) * sizeof(WCHAR) ); + buffer[nt_len - 1] = 0; + init_unicode_string( nt_name, buffer ); + attr->ObjectName = nt_name; + } + } + TRACE( "%s -> ret %x nt %s unix %s\n", debugstr_us(orig), status, debugstr_us(attr->ObjectName), debugstr_a(*unix_name_ret) ); + } return status; }
On Wed Jul 9 16:36:21 2025 +0000, Alexandre Julliard wrote:
This should most likely be done client-side. In general the server doesn't interpret the NT path, it just passes it through.
v2 does this in ntdll. `get_nt_and_unix_names` seemed like the most sensible place to me, but I'm open to suggestions obviously.
On Wed Jul 9 16:36:21 2025 +0000, Tim Clem wrote:
v2 does this in ntdll. `get_nt_and_unix_names` seemed like the most sensible place to me, but I'm open to suggestions obviously.
You can't assume that you get a drive letter path, there are other possible formats. Also the tests should use NtCreateFile to confirm that this should be done on the kernel side.
On Wed Jul 9 20:13:08 2025 +0000, Alexandre Julliard wrote:
You can't assume that you get a drive letter path, there are other possible formats. Also the tests should use NtCreateFile to confirm that this should be done on the kernel side.
Ah, true, didn't think that NtCreateFile behavior might differ. It doesn't, and I'll update the tests.
As for other possible formats, does it suffice to skip other prefixes using `get_dos_prefix_len`, like `nt_to_unix_file_name_no_root` does? After such a prefix, I should always have a drive letter path, right? Otherwise previous parts of `get_nt_and_unix_names` will have set a non-zero status, I think.
On Thu Jul 10 18:30:57 2025 +0000, Tim Clem wrote:
Ah, true, didn't think that NtCreateFile behavior might differ. It doesn't, and I'll update the tests. As for other possible formats, does it suffice to skip other prefixes using `get_dos_prefix_len`, like `nt_to_unix_file_name_no_root` does? After such a prefix, I should always have a drive letter path, right? Otherwise previous parts of `get_nt_and_unix_names` will have set a non-zero status, I think.
No, you can also get other DOS devices, or relative path names.
On Thu Jul 10 19:59:52 2025 +0000, Alexandre Julliard wrote:
No, you can also get other DOS devices, or relative path names.
Ah, I see - the Unix path gets fully resolved, but the NT path can be relative if there's a root in the OBJECT_ATTRIBUTES. That makes it very tricky to correct in `get_nt_and_unix_names`.
Perhaps we should just strip trailing slashes in GetFinalPathNameByHandle for now, since we're not going to match native on ObjectNameInformation anyway?
Superseded by 8f799fb41615f2a54ccb963d2987ab2109ee61d4.
This merge request was closed by Tim Clem.