Signed-off-by: Zebediah Figura zfigura@codeweavers.com ---
This addresses the test failures mentioned in response to v3 of this patch set. Similar (but not identical) test failures occur on Windows.
dlls/kernel32/tests/volume.c | 206 +++++++++++++++++------------------ 1 file changed, 99 insertions(+), 107 deletions(-)
diff --git a/dlls/kernel32/tests/volume.c b/dlls/kernel32/tests/volume.c index ded167f3e07..37d15fd3f8c 100644 --- a/dlls/kernel32/tests/volume.c +++ b/dlls/kernel32/tests/volume.c @@ -57,7 +57,6 @@ static UINT (WINAPI *pGetLogicalDriveStringsA)(UINT,LPSTR); static UINT (WINAPI *pGetLogicalDriveStringsW)(UINT,LPWSTR); static BOOL (WINAPI *pGetVolumeInformationA)(LPCSTR, LPSTR, DWORD, LPDWORD, LPDWORD, LPDWORD, LPSTR, DWORD); static BOOL (WINAPI *pGetVolumePathNameA)(LPCSTR, LPSTR, DWORD); -static BOOL (WINAPI *pGetVolumePathNameW)(LPWSTR, LPWSTR, DWORD); static BOOL (WINAPI *pGetVolumePathNamesForVolumeNameA)(LPCSTR, LPSTR, DWORD, LPDWORD); static BOOL (WINAPI *pGetVolumePathNamesForVolumeNameW)(LPCWSTR, LPWSTR, DWORD, LPDWORD); static BOOL (WINAPI *pCreateSymbolicLinkA)(const char *, const char *, DWORD); @@ -686,7 +685,7 @@ static void test_disk_query_property(void)
static void test_GetVolumePathNameA(void) { - char volume_path[MAX_PATH], cwd[MAX_PATH]; + char volume_path[MAX_PATH], cwd[MAX_PATH], expect_path[MAX_PATH]; struct { const char *file_name; const char *path_name; @@ -698,19 +697,19 @@ static void test_GetVolumePathNameA(void) NULL, NULL, 0, ERROR_INVALID_PARAMETER, 0xdeadbeef /* winxp */ }, - { /* test 1: empty input, NULL output, 0 output length */ + { /* empty input, NULL output, 0 output length */ "", NULL, 0, ERROR_INVALID_PARAMETER, 0xdeadbeef /* winxp */ }, - { /* test 2: valid input, NULL output, 0 output length */ + { /* valid input, NULL output, 0 output length */ "C:\", NULL, 0, ERROR_INVALID_PARAMETER, ERROR_FILENAME_EXCED_RANGE /* winxp */ }, - { /* test 3: valid input, valid output, 0 output length */ + { /* valid input, valid output, 0 output length */ "C:\", "C:\", 0, ERROR_INVALID_PARAMETER, ERROR_FILENAME_EXCED_RANGE /* winxp */ }, - { /* test 4: valid input, valid output, 1 output length */ + { /* valid input, valid output, 1 output length */ "C:\", "C:\", 1, ERROR_FILENAME_EXCED_RANGE, NO_ERROR }, @@ -718,155 +717,127 @@ static void test_GetVolumePathNameA(void) "C:\", "C:\", sizeof(volume_path), NO_ERROR, NO_ERROR }, - { /* test 6: lowercase input, uppercase output, valid output length */ + { /* lowercase input, uppercase output, valid output length */ "c:\", "C:\", sizeof(volume_path), NO_ERROR, NO_ERROR }, - { /* test 7: poor quality input, valid output, valid output length */ - "C::", "C:\", sizeof(volume_path), - NO_ERROR, NO_ERROR - }, - { /* test 8: really bogus input, valid output, 1 output length */ + { /* really bogus input, valid output, 1 output length */ "\\$$$", "C:\", 1, ERROR_INVALID_NAME, ERROR_FILENAME_EXCED_RANGE }, - { /* test 9: a reasonable DOS path that is guaranteed to exist */ + { /* a reasonable DOS path that is guaranteed to exist */ "C:\windows\system32", "C:\", sizeof(volume_path), NO_ERROR, NO_ERROR }, - { /* test 10: a reasonable DOS path that shouldn't exist */ + { /* a reasonable DOS path that shouldn't exist */ "C:\windows\system32\AnInvalidFolder", "C:\", sizeof(volume_path), NO_ERROR, NO_ERROR }, - { /* test 11: a reasonable NT-converted DOS path that shouldn't exist */ + { /* test 10: a reasonable NT-converted DOS path that shouldn't exist */ "\\?\C:\AnInvalidFolder", "\\?\C:\", sizeof(volume_path), NO_ERROR, NO_ERROR }, - { /* test 12: an unreasonable NT-converted DOS path */ + { /* an unreasonable NT-converted DOS path */ "\\?\InvalidDrive:\AnInvalidFolder", "\\?\InvalidDrive:\" /* win2k, winxp */, sizeof(volume_path), ERROR_INVALID_NAME, NO_ERROR }, - { /* test 13: an unreasonable NT volume path */ + { /* an unreasonable NT volume path */ "\\?\Volume{00000000-00-0000-0000-000000000000}\AnInvalidFolder", "\\?\Volume{00000000-00-0000-0000-000000000000}\" /* win2k, winxp */, sizeof(volume_path), ERROR_INVALID_NAME, NO_ERROR }, - { /* test 14: an unreasonable NT-ish path */ + { /* an unreasonable NT-ish path */ "\\ReallyBogus\InvalidDrive:\AnInvalidFolder", "\\ReallyBogus\InvalidDrive:\" /* win2k, winxp */, sizeof(volume_path), ERROR_INVALID_NAME, NO_ERROR }, - { /* test 15: poor quality input, valid output, valid (but short) output length */ - "C::", "C:\", 4, - NO_ERROR, ERROR_MORE_DATA - }, - { /* test 16: unused drive letter */ + { "M::", "C:\", 4, ERROR_FILE_NOT_FOUND, ERROR_MORE_DATA }, - { /* test 17: an unreasonable DOS path */ - "InvalidDrive:\AnInvalidFolder", "%CurrentDrive%\", sizeof(volume_path), - NO_ERROR, NO_ERROR - }, - { /* test 18: a reasonable device path */ - "\??\CdRom0", "%CurrentDrive%\", sizeof(volume_path), - NO_ERROR, NO_ERROR - }, - { /* test 19: an unreasonable device path */ - "\??\ReallyBogus", "%CurrentDrive%\", sizeof(volume_path), - NO_ERROR, NO_ERROR - }, - { /* test 20 */ + { /* test 15 */ "C:", "C:", 2, ERROR_FILENAME_EXCED_RANGE, NO_ERROR }, - { /* test 21 */ + { "C:", "C:", 3, NO_ERROR, ERROR_FILENAME_EXCED_RANGE }, - { /* test 22 */ + { "C:\", "C:", 2, ERROR_FILENAME_EXCED_RANGE, NO_ERROR }, - { /* test 23 */ + { "C:\", "C:", 3, NO_ERROR, ERROR_FILENAME_EXCED_RANGE }, - { /* test 24 */ + { "C::", "C:", 2, ERROR_FILENAME_EXCED_RANGE, NO_ERROR }, - { /* test 25 */ + { /* test 20 */ "C::", "C:", 3, NO_ERROR, ERROR_FILENAME_EXCED_RANGE }, - { /* test 26 */ - "C::", "C:\", 4, - NO_ERROR, ERROR_MORE_DATA - }, - { /* test 27 */ + { "C:\windows\system32\AnInvalidFolder", "C:", 3, NO_ERROR, ERROR_FILENAME_EXCED_RANGE }, - { /* test 28 */ + { "\\?\C:\AnInvalidFolder", "\\?\C:", 3, ERROR_FILENAME_EXCED_RANGE, NO_ERROR }, - { /* test 29 */ + { "\\?\C:\AnInvalidFolder", "\\?\C:", 6, ERROR_FILENAME_EXCED_RANGE, NO_ERROR }, - { /* test 30 */ + { "\\?\C:\AnInvalidFolder", "\\?\C:", 7, NO_ERROR, ERROR_FILENAME_EXCED_RANGE }, - { /* test 31 */ + { /* test 25 */ "\\?\c:\AnInvalidFolder", "\\?\c:", 7, NO_ERROR, ERROR_FILENAME_EXCED_RANGE }, - { /* test 32 */ + { "C:/", "C:\", 4, NO_ERROR, ERROR_MORE_DATA }, - { /* test 33 */ + { "M:/", "", 4, ERROR_FILE_NOT_FOUND, ERROR_MORE_DATA }, - { /* test 34 */ - "C:ABC:DEF:\AnInvalidFolder", "C:\", 4, - NO_ERROR, ERROR_MORE_DATA - }, - { /* test 35 */ + { "?:ABC:DEF:\AnInvalidFolder", "?:\" /* win2k, winxp */, sizeof(volume_path), ERROR_FILE_NOT_FOUND, NO_ERROR }, - { /* test 36 */ - "relative/path", "%CurrentDrive%\", sizeof(volume_path), - NO_ERROR, NO_ERROR - }, - { /* test 37 */ - "\??\C:\NonExistent", "%CurrentDrive%\", sizeof(volume_path), - NO_ERROR, NO_ERROR - }, - { /* test 38 */ - "\??\M:\NonExistent", "%CurrentDrive%\", sizeof(volume_path), - NO_ERROR, NO_ERROR - }, - { /* test 39 */ - "somefile:def", "%CurrentDrive%\", sizeof(volume_path), - NO_ERROR, NO_ERROR - }, - { /* test 40 */ + { "s:omefile", "S:\" /* win2k, winxp */, sizeof(volume_path), ERROR_FILE_NOT_FOUND, NO_ERROR }, - { /* test 41: a reasonable forward slash path that is guaranteed to exist */ + { /* test 30: a reasonable forward slash path that is guaranteed to exist */ "C:/windows/system32", "C:\", sizeof(volume_path), NO_ERROR, NO_ERROR }, }; + + static const char *relative_tests[] = + { + "InvalidDrive:\AnInvalidFolder", + "relative/path", + "somefile:def", + }; + + static const char *global_prefix_tests[] = + { + "\??\CdRom0", + "\??\ReallyBogus", + "\??\C:\NonExistent", + "\??\M:\NonExistent", + }; + BOOL ret, success; DWORD error; UINT i; @@ -878,13 +849,6 @@ static void test_GetVolumePathNameA(void) return; }
- /* Obtain the drive of the working directory */ - ret = GetCurrentDirectoryA( sizeof(cwd), cwd ); - ok( ret, "Failed to obtain the current working directory.\n" ); - cwd[2] = 0; - ret = SetEnvironmentVariableA( "CurrentDrive", cwd ); - ok( ret, "Failed to set an environment variable for the current working drive.\n" ); - for (i=0; i<ARRAY_SIZE(test_paths); i++) { BOOL broken_ret = test_paths[i].broken_error == NO_ERROR; @@ -904,14 +868,10 @@ static void test_GetVolumePathNameA(void)
if (ret) { - char path_name[MAX_PATH]; - - ExpandEnvironmentStringsA( test_paths[i].path_name, path_name, MAX_PATH); - /* If we succeeded then make sure the path is correct */ - success = (strcmp( volume_path, path_name ) == 0) - || broken(strcasecmp( volume_path, path_name ) == 0) /* XP */; - ok(success, "GetVolumePathName test %d unexpectedly returned path %s (expected %s).\n", - i, volume_path, path_name); + ok(!strcmp( volume_path, test_paths[i].path_name ) + || broken(!strcasecmp( volume_path, test_paths[i].path_name )), /* XP */ + "GetVolumePathName test %d unexpectedly returned path %s (expected %s).\n", + i, volume_path, test_paths[i].path_name); } else { @@ -925,24 +885,58 @@ static void test_GetVolumePathNameA(void) ok(volume_path[ test_paths[i].path_len ] == 0x11, "GetVolumePathName test %d corrupted byte after end of buffer.\n", i); } + + ret = GetCurrentDirectoryA( sizeof(cwd), cwd ); + ok(ret, "Failed to obtain the current working directory, error %u.\n", GetLastError()); + ret = GetVolumePathNameA( cwd, expect_path, sizeof(expect_path) ); + ok(ret, "Failed to obtain the current volume path, error %u.\n", GetLastError()); + + for (i = 0; i < ARRAY_SIZE(relative_tests); i++) + { + ret = GetVolumePathNameA( relative_tests[i], volume_path, sizeof(volume_path) ); + ok(ret, "GetVolumePathName(%s) failed unexpectedly, error %u.\n", + debugstr_a( relative_tests[i] ), GetLastError()); + ok(!strcmp( volume_path, expect_path ), "%s: expected %s, got %s.\n", + debugstr_a( relative_tests[i] ), debugstr_a( expect_path ), debugstr_a( volume_path )); + } + + cwd[3] = 0; + for (i = 0; i < ARRAY_SIZE(global_prefix_tests); i++) + { + ret = GetVolumePathNameA( global_prefix_tests[i], volume_path, sizeof(volume_path) ); + ok(ret, "GetVolumePathName(%s) failed unexpectedly, error %u.\n", + debugstr_a( global_prefix_tests[i] ), GetLastError()); + ok(!strcmp( volume_path, cwd ), "%s: expected %s, got %s.\n", + debugstr_a( global_prefix_tests[i] ), debugstr_a( cwd ), debugstr_a( volume_path )); + } + + ret = GetVolumePathNameA( "C:.", expect_path, sizeof(expect_path) ); + ok(ret, "Failed to obtain the volume path, error %u.\n", GetLastError()); + + SetLastError( 0xdeadbeef ); + ret = GetVolumePathNameA( "C::", volume_path, 1 ); + ok(!ret, "Expected failure.\n"); + ok(GetLastError() == ERROR_FILENAME_EXCED_RANGE, "Got error %u.\n", GetLastError()); + + ret = GetVolumePathNameA( "C::", volume_path, sizeof(volume_path) ); + ok(ret, "Failed to obtain the volume path, error %u.\n", GetLastError()); + ok(!strcmp(volume_path, expect_path), "Expected %s, got %s.\n", + debugstr_a( expect_path ), debugstr_a( volume_path )); + + ret = GetVolumePathNameA( "C:ABC:DEF:\AnInvalidFolder", volume_path, sizeof(volume_path) ); + ok(ret, "Failed to obtain the volume path, error %u.\n", GetLastError()); + ok(!strcmp(volume_path, expect_path), "Expected %s, got %s.\n", + debugstr_a( expect_path ), debugstr_a( volume_path )); }
static void test_GetVolumePathNameW(void) { - static WCHAR drive_c1[] = {'C',':',0}; - static WCHAR drive_c2[] = {'C',':','\',0}; WCHAR volume_path[MAX_PATH]; BOOL ret;
- if (!pGetVolumePathNameW) - { - win_skip("required functions not found\n"); - return; - } - volume_path[0] = 0; volume_path[1] = 0x11; - ret = pGetVolumePathNameW( drive_c1, volume_path, 1 ); + ret = GetVolumePathNameW( L"C:\", volume_path, 1 ); ok(!ret, "GetVolumePathNameW test succeeded unexpectedly.\n"); ok(GetLastError() == ERROR_FILENAME_EXCED_RANGE, "GetVolumePathNameW unexpectedly returned error 0x%x (expected 0x%x).\n", GetLastError(), ERROR_FILENAME_EXCED_RANGE); @@ -950,7 +944,7 @@ static void test_GetVolumePathNameW(void)
volume_path[0] = 0; volume_path[2] = 0x11; - ret = pGetVolumePathNameW( drive_c1, volume_path, 2 ); + ret = GetVolumePathNameW( L"C:\", volume_path, 2 ); ok(!ret, "GetVolumePathNameW test succeeded unexpectedly.\n"); ok(GetLastError() == ERROR_FILENAME_EXCED_RANGE, "GetVolumePathNameW unexpectedly returned error 0x%x (expected 0x%x).\n", GetLastError(), ERROR_FILENAME_EXCED_RANGE); @@ -958,18 +952,17 @@ static void test_GetVolumePathNameW(void)
volume_path[0] = 0; volume_path[3] = 0x11; - ret = pGetVolumePathNameW( drive_c1, volume_path, 3 ); + ret = GetVolumePathNameW( L"C:\", volume_path, 3 ); ok(ret || broken(!ret) /* win2k */, "GetVolumePathNameW test failed unexpectedly.\n"); - ok(memcmp(volume_path, drive_c1, sizeof(drive_c1)) == 0 - || broken(volume_path[0] == 0) /* win2k */, - "GetVolumePathNameW unexpectedly returned wrong path.\n"); + ok(!memcmp(volume_path, L"C:\", 3) || broken(!volume_path[0]) /* XP */, + "Got wrong path %s.\n", debugstr_w(volume_path)); ok(volume_path[3] == 0x11, "GetVolumePathW corrupted byte after end of buffer.\n");
volume_path[0] = 0; volume_path[4] = 0x11; - ret = pGetVolumePathNameW( drive_c1, volume_path, 4 ); + ret = GetVolumePathNameW( L"C:\", volume_path, 4 ); ok(ret, "GetVolumePathNameW test failed unexpectedly.\n"); - ok(memcmp(volume_path, drive_c2, sizeof(drive_c2)) == 0, "GetVolumePathNameW unexpectedly returned wrong path.\n"); + ok(!wcscmp(volume_path, L"C:\"), "Got wrong path %s.\n", debugstr_w(volume_path)); ok(volume_path[4] == 0x11, "GetVolumePathW corrupted byte after end of buffer.\n"); }
@@ -1539,7 +1532,6 @@ START_TEST(volume) pGetLogicalDriveStringsW = (void *) GetProcAddress(hdll, "GetLogicalDriveStringsW"); pGetVolumeInformationA = (void *) GetProcAddress(hdll, "GetVolumeInformationA"); pGetVolumePathNameA = (void *) GetProcAddress(hdll, "GetVolumePathNameA"); - pGetVolumePathNameW = (void *) GetProcAddress(hdll, "GetVolumePathNameW"); pGetVolumePathNamesForVolumeNameA = (void *) GetProcAddress(hdll, "GetVolumePathNamesForVolumeNameA"); pGetVolumePathNamesForVolumeNameW = (void *) GetProcAddress(hdll, "GetVolumePathNamesForVolumeNameW"); pCreateSymbolicLinkA = (void *) GetProcAddress(hdll, "CreateSymbolicLinkA");
So that we can query the Unix symlink target from a file handle.
Signed-off-by: Zebediah Figura zfigura@codeweavers.com --- dlls/ntdll/tests/file.c | 2 +- server/fd.c | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-)
diff --git a/dlls/ntdll/tests/file.c b/dlls/ntdll/tests/file.c index 814353e7625..75a67e8095a 100644 --- a/dlls/ntdll/tests/file.c +++ b/dlls/ntdll/tests/file.c @@ -2097,7 +2097,7 @@ static void test_file_rename_information(void) res = pNtQueryInformationFile( handle, &io, fni, sizeof(FILE_NAME_INFORMATION) + MAX_PATH * sizeof(WCHAR), FileNameInformation ); ok( res == STATUS_SUCCESS, "res expected STATUS_SUCCESS, got %x\n", res ); fni->FileName[ fni->FileNameLength / sizeof(WCHAR) ] = 0; - todo_wine ok( !lstrcmpiW(fni->FileName, newpath + 2), "FileName expected %s, got %s\n", + ok( !lstrcmpiW(fni->FileName, newpath + 2), "FileName expected %s, got %s\n", wine_dbgstr_w(newpath + 2), wine_dbgstr_w(fni->FileName) ); HeapFree( GetProcessHeap(), 0, fni );
diff --git a/server/fd.c b/server/fd.c index e7f57966b26..5019ae2da00 100644 --- a/server/fd.c +++ b/server/fd.c @@ -1756,6 +1756,7 @@ struct fd *open_fd( struct fd *root, const char *name, int flags, mode_t *mode, struct fd *fd; int root_fd = -1; int rw_mode; + char *path;
if (((options & FILE_DELETE_ON_CLOSE) && !(access & DELETE)) || ((options & FILE_DIRECTORY_FILE) && (flags & O_TRUNC))) @@ -1805,8 +1806,6 @@ struct fd *open_fd( struct fd *root, const char *name, int flags, mode_t *mode, } else rw_mode = O_RDONLY;
- fd->unix_name = dup_fd_name( root, name ); - if ((fd->unix_fd = open( name, rw_mode | (flags & ~O_TRUNC), *mode )) == -1) { /* if we tried to open a directory for write access, retry read-only */ @@ -1823,6 +1822,13 @@ struct fd *open_fd( struct fd *root, const char *name, int flags, mode_t *mode, } }
+ fd->unix_name = NULL; + if ((path = dup_fd_name( root, name ))) + { + fd->unix_name = realpath( path, NULL ); + free( path ); + } + closed_fd->unix_fd = fd->unix_fd; closed_fd->unlink = 0; closed_fd->unix_name = fd->unix_name; @@ -2441,8 +2447,10 @@ static void set_fd_name( struct fd *fd, struct fd *root, const char *nameptr, }
free( fd->unix_name ); - fd->unix_name = name; - fd->closed->unix_name = name; + fd->closed->unix_name = fd->unix_name = realpath( name, NULL ); + free( name ); + if (!fd->unix_name) + set_error( STATUS_NO_MEMORY ); return;
failed:
Signed-off-by: Zebediah Figura zfigura@codeweavers.com --- dlls/ntdll/file.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+)
diff --git a/dlls/ntdll/file.c b/dlls/ntdll/file.c index 5175e9d5ce1..d4bc712d9d3 100644 --- a/dlls/ntdll/file.c +++ b/dlls/ntdll/file.c @@ -151,6 +151,7 @@ int fd_get_file_info( int fd, struct stat *st, ULONG *attr ) /* get the stat info and file attributes for a file (by name) */ int get_file_info( const char *path, struct stat *st, ULONG *attr ) { + char *parent_path; int ret;
*attr = 0; @@ -163,6 +164,19 @@ int get_file_info( const char *path, struct stat *st, ULONG *attr ) /* is a symbolic link and a directory, consider these "reparse points" */ if (S_ISDIR( st->st_mode )) *attr |= FILE_ATTRIBUTE_REPARSE_POINT; } + else if (S_ISDIR( st->st_mode ) && (parent_path = RtlAllocateHeap( GetProcessHeap(), 0, strlen(path) + 4 ))) + { + struct stat parent_st; + + /* consider mount points to be reparse points (IO_REPARSE_TAG_MOUNT_POINT) */ + strcpy( parent_path, path ); + strcat( parent_path, "/.." ); + if (!stat( parent_path, &parent_st ) + && (st->st_dev != parent_st.st_dev || st->st_ino == parent_st.st_ino)) + *attr |= FILE_ATTRIBUTE_REPARSE_POINT; + + RtlFreeHeap( GetProcessHeap(), 0, parent_path ); + } *attr |= get_file_attributes( st ); return ret; }
Signed-off-by: Zebediah Figura zfigura@codeweavers.com --- dlls/ntdll/file.c | 31 ++++++++++++++++++++++--------- include/winternl.h | 2 +- 2 files changed, 23 insertions(+), 10 deletions(-)
diff --git a/dlls/ntdll/file.c b/dlls/ntdll/file.c index d4bc712d9d3..7b2e102e536 100644 --- a/dlls/ntdll/file.c +++ b/dlls/ntdll/file.c @@ -136,8 +136,15 @@ static inline ULONG get_file_attributes( const struct stat *st ) return attr; }
+static BOOL fd_is_mount_point( int fd, const struct stat *st ) +{ + struct stat parent; + return S_ISDIR( st->st_mode ) && !fstatat( fd, "..", &parent, 0 ) + && (parent.st_dev != st->st_dev || parent.st_ino == st->st_ino); +} + /* get the stat info and file attributes for a file (by file descriptor) */ -int fd_get_file_info( int fd, struct stat *st, ULONG *attr ) +int fd_get_file_info( int fd, unsigned int options, struct stat *st, ULONG *attr ) { int ret;
@@ -145,6 +152,9 @@ int fd_get_file_info( int fd, struct stat *st, ULONG *attr ) ret = fstat( fd, st ); if (ret == -1) return ret; *attr |= get_file_attributes( st ); + /* consider mount points to be reparse points (IO_REPARSE_TAG_MOUNT_POINT) */ + if ((options & FILE_OPEN_REPARSE_POINT) && fd_is_mount_point( fd, st )) + *attr |= FILE_ATTRIBUTE_REPARSE_POINT; return ret; }
@@ -2318,6 +2328,7 @@ NTSTATUS WINAPI NtQueryInformationFile( HANDLE hFile, PIO_STATUS_BLOCK io, struct stat st; int fd, needs_close = FALSE; ULONG attr; + unsigned int options;
TRACE("(%p,%p,%p,0x%08x,0x%08x)\n", hFile, io, ptr, len, class);
@@ -2330,7 +2341,7 @@ NTSTATUS WINAPI NtQueryInformationFile( HANDLE hFile, PIO_STATUS_BLOCK io, if (len < info_sizes[class]) return io->u.Status = STATUS_INFO_LENGTH_MISMATCH;
- if ((io->u.Status = server_get_unix_fd( hFile, 0, &fd, &needs_close, NULL, NULL ))) + if ((io->u.Status = server_get_unix_fd( hFile, 0, &fd, &needs_close, NULL, &options ))) { if (io->u.Status != STATUS_BAD_DEVICE_TYPE) return io->u.Status; return server_get_file_info( hFile, io, ptr, len, class ); @@ -2339,7 +2350,7 @@ NTSTATUS WINAPI NtQueryInformationFile( HANDLE hFile, PIO_STATUS_BLOCK io, switch (class) { case FileBasicInformation: - if (fd_get_file_info( fd, &st, &attr ) == -1) + if (fd_get_file_info( fd, options, &st, &attr ) == -1) io->u.Status = FILE_GetNtStatus(); else if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode)) io->u.Status = STATUS_INVALID_INFO_CLASS; @@ -2350,7 +2361,7 @@ NTSTATUS WINAPI NtQueryInformationFile( HANDLE hFile, PIO_STATUS_BLOCK io, { FILE_STANDARD_INFORMATION *info = ptr;
- if (fd_get_file_info( fd, &st, &attr ) == -1) io->u.Status = FILE_GetNtStatus(); + if (fd_get_file_info( fd, options, &st, &attr ) == -1) io->u.Status = FILE_GetNtStatus(); else { fill_file_info( &st, attr, info, class ); @@ -2367,7 +2378,7 @@ NTSTATUS WINAPI NtQueryInformationFile( HANDLE hFile, PIO_STATUS_BLOCK io, } break; case FileInternalInformation: - if (fd_get_file_info( fd, &st, &attr ) == -1) io->u.Status = FILE_GetNtStatus(); + if (fd_get_file_info( fd, options, &st, &attr ) == -1) io->u.Status = FILE_GetNtStatus(); else fill_file_info( &st, attr, ptr, class ); break; case FileEaInformation: @@ -2377,7 +2388,7 @@ NTSTATUS WINAPI NtQueryInformationFile( HANDLE hFile, PIO_STATUS_BLOCK io, } break; case FileEndOfFileInformation: - if (fd_get_file_info( fd, &st, &attr ) == -1) io->u.Status = FILE_GetNtStatus(); + if (fd_get_file_info( fd, options, &st, &attr ) == -1) io->u.Status = FILE_GetNtStatus(); else fill_file_info( &st, attr, ptr, class ); break; case FileAllInformation: @@ -2385,7 +2396,7 @@ NTSTATUS WINAPI NtQueryInformationFile( HANDLE hFile, PIO_STATUS_BLOCK io, FILE_ALL_INFORMATION *info = ptr; ANSI_STRING unix_name;
- if (fd_get_file_info( fd, &st, &attr ) == -1) io->u.Status = FILE_GetNtStatus(); + if (fd_get_file_info( fd, options, &st, &attr ) == -1) io->u.Status = FILE_GetNtStatus(); else if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode)) io->u.Status = STATUS_INVALID_INFO_CLASS; else if (!(io->u.Status = server_get_unix_name( hFile, &unix_name ))) @@ -2493,7 +2504,7 @@ NTSTATUS WINAPI NtQueryInformationFile( HANDLE hFile, PIO_STATUS_BLOCK io, } break; case FileIdInformation: - if (fd_get_file_info( fd, &st, &attr ) == -1) io->u.Status = FILE_GetNtStatus(); + if (fd_get_file_info( fd, options, &st, &attr ) == -1) io->u.Status = FILE_GetNtStatus(); else { FILE_ID_INFORMATION *info = ptr; @@ -2503,12 +2514,14 @@ NTSTATUS WINAPI NtQueryInformationFile( HANDLE hFile, PIO_STATUS_BLOCK io, } break; case FileAttributeTagInformation: - if (fd_get_file_info( fd, &st, &attr ) == -1) io->u.Status = FILE_GetNtStatus(); + if (fd_get_file_info( fd, options, &st, &attr ) == -1) io->u.Status = FILE_GetNtStatus(); else { FILE_ATTRIBUTE_TAG_INFORMATION *info = ptr; info->FileAttributes = attr; info->ReparseTag = 0; /* FIXME */ + if ((options & FILE_OPEN_REPARSE_POINT) && fd_is_mount_point( fd, &st )) + info->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; } break; default: diff --git a/include/winternl.h b/include/winternl.h index d9809c7ad31..19e8965e99f 100644 --- a/include/winternl.h +++ b/include/winternl.h @@ -1795,7 +1795,7 @@ typedef struct _RTL_HANDLE_TABLE #define FILE_OPEN_FOR_BACKUP_INTENT 0x00004000 #define FILE_NO_COMPRESSION 0x00008000 #define FILE_RESERVE_OPFILTER 0x00100000 -#define FILE_TRANSACTED_MODE 0x00200000 +#define FILE_OPEN_REPARSE_POINT 0x00200000 #define FILE_OPEN_OFFLINE_FILE 0x00400000 #define FILE_OPEN_FOR_FREE_SPACE_QUERY 0x00800000
Signed-off-by: Zebediah Figura zfigura@codeweavers.com ---
v4: fix handling of the global ??\ prefix (the current drive letter should be returned, rather than the current volume)
dlls/kernel32/volume.c | 242 ++++++++++++++++++++++------------------- 1 file changed, 132 insertions(+), 110 deletions(-)
diff --git a/dlls/kernel32/volume.c b/dlls/kernel32/volume.c index 5bfebf60570..7967a55b2fa 100644 --- a/dlls/kernel32/volume.c +++ b/dlls/kernel32/volume.c @@ -1591,149 +1591,171 @@ BOOL WINAPI GetVolumePathNameA(LPCSTR filename, LPSTR volumepathname, DWORD bufl return ret; }
+static BOOL is_dos_path( const UNICODE_STRING *path ) +{ + static const WCHAR global_prefix[4] = {'\','?','?','\'}; + return path->Length >= 7 * sizeof(WCHAR) + && !memcmp(path->Buffer, global_prefix, sizeof(global_prefix)) + && path->Buffer[5] == ':' && path->Buffer[6] == '\'; +} + +/* resolve all symlinks in a path in-place; return FALSE if allocation failed */ +static BOOL resolve_symlink( UNICODE_STRING *path ) +{ + OBJECT_NAME_INFORMATION *info; + OBJECT_ATTRIBUTES attr; + IO_STATUS_BLOCK io; + NTSTATUS status; + HANDLE file; + ULONG size; + + InitializeObjectAttributes( &attr, path, OBJ_CASE_INSENSITIVE, 0, NULL ); + if (NtOpenFile( &file, SYNCHRONIZE, &attr, &io, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT )) + return TRUE; + + if (NtQueryObject( file, ObjectNameInformation, NULL, 0, &size ) != STATUS_INFO_LENGTH_MISMATCH) + { + NtClose( file ); + return TRUE; + } + + if (!(info = HeapAlloc( GetProcessHeap(), 0, size ))) + { + NtClose( file ); + return FALSE; + } + + status = NtQueryObject( file, ObjectNameInformation, info, size, NULL ); + NtClose( file ); + if (status) + return TRUE; + + RtlFreeUnicodeString( path ); + status = RtlDuplicateUnicodeString( 0, &info->Name, path ); + HeapFree( GetProcessHeap(), 0, info ); + return !status; +} + /*********************************************************************** * GetVolumePathNameW (KERNEL32.@) - * - * This routine is intended to find the most basic path on the same filesystem - * for any particular path name. Since we can have very complicated drive/path - * relationships on Unix systems, due to symbolic links, the safest way to - * handle this is to start with the full path and work our way back folder by - * folder unil we find a folder on a different drive (or run out of folders). */ -BOOL WINAPI GetVolumePathNameW(LPCWSTR filename, LPWSTR volumepathname, DWORD buflen) +BOOL WINAPI GetVolumePathNameW(const WCHAR *path, WCHAR *volume_path, DWORD length) { - static const WCHAR deviceprefixW[] = { '\','?','?','\',0 }; - static const WCHAR ntprefixW[] = { '\','\','?','\',0 }; - WCHAR fallbackpathW[] = { 'C',':','\',0 }; - NTSTATUS status = STATUS_SUCCESS; - WCHAR *volumenameW = NULL, *c; - int pos, last_pos, stop_pos; + static const WCHAR device_prefix[4] = {'\','\','.','\'}; + static const WCHAR device_prefix2[4] = {'\','\','?','\'}; + static const WCHAR global_prefix[4] = {'\','?','?','\'}; + FILE_ATTRIBUTE_TAG_INFORMATION attr_info; + FILE_BASIC_INFORMATION basic_info; + OBJECT_ATTRIBUTES attr; UNICODE_STRING nt_name; - ANSI_STRING unix_name; - BOOL first_run = TRUE; - dev_t search_dev = 0; - struct stat st; - - TRACE("(%s, %p, %d)\n", debugstr_w(filename), volumepathname, buflen); + NTSTATUS status;
- if (!filename || !volumepathname || !buflen) + if (path && !memcmp(path, global_prefix, sizeof(global_prefix))) { - SetLastError(ERROR_INVALID_PARAMETER); + WCHAR current_drive[MAX_PATH]; + + GetCurrentDirectoryW( ARRAY_SIZE(current_drive), current_drive ); + if (length >= 3) + { + WCHAR ret_path[4] = {current_drive[0], ':', '\', 0}; + lstrcpynW( volume_path, ret_path, length ); + return TRUE; + } + + SetLastError( ERROR_FILENAME_EXCED_RANGE ); return FALSE; }
- last_pos = pos = strlenW( filename ); - /* allocate enough memory for searching the path (need room for a slash and a NULL terminator) */ - if (!(volumenameW = HeapAlloc( GetProcessHeap(), 0, (pos + 2) * sizeof(WCHAR) ))) + if (!volume_path || !length || !RtlDosPathNameToNtPathName_U( path, &nt_name, NULL, NULL )) { - SetLastError( ERROR_NOT_ENOUGH_MEMORY ); + SetLastError( ERROR_INVALID_PARAMETER ); return FALSE; } - strcpyW( volumenameW, filename );
- /* Normalize path */ - for (c = volumenameW; *c; c++) if (*c == '/') *c = '\'; + if (!is_dos_path( &nt_name )) + { + RtlFreeUnicodeString( &nt_name ); + WARN("invalid path %s\n", debugstr_w(path)); + SetLastError( ERROR_INVALID_NAME ); + return FALSE; + }
- stop_pos = 0; - /* stop searching slashes early for NT-type and nearly NT-type paths */ - if (strncmpW(ntprefixW, filename, strlenW(ntprefixW)) == 0) - stop_pos = strlenW(ntprefixW)-1; - else if (strncmpW(ntprefixW, filename, 2) == 0) - stop_pos = 2; + InitializeObjectAttributes( &attr, &nt_name, OBJ_CASE_INSENSITIVE, 0, NULL );
- do + while (nt_name.Length > 7 * sizeof(WCHAR)) { - volumenameW[pos+0] = '\'; - volumenameW[pos+1] = '\0'; - if (!RtlDosPathNameToNtPathName_U( volumenameW, &nt_name, NULL, NULL )) - goto cleanup; - volumenameW[pos] = '\0'; - status = wine_nt_to_unix_file_name( &nt_name, &unix_name, FILE_OPEN, FALSE ); - RtlFreeUnicodeString( &nt_name ); - if (status == STATUS_SUCCESS) + IO_STATUS_BLOCK io; + HANDLE file; + + if (!NtQueryAttributesFile( &attr, &basic_info ) + && (basic_info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) + && (basic_info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) + && !NtOpenFile( &file, SYNCHRONIZE | FILE_READ_ATTRIBUTES, &attr, &io, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN_REPARSE_POINT | FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT )) { - if (stat( unix_name.Buffer, &st ) != 0) - { - RtlFreeAnsiString( &unix_name ); - status = STATUS_OBJECT_NAME_INVALID; - goto cleanup; - } - if (first_run) - { - first_run = FALSE; - search_dev = st.st_dev; - } - else if (st.st_dev != search_dev) + status = NtQueryInformationFile( file, &io, &attr_info, + sizeof(attr_info), FileAttributeTagInformation ); + NtClose( file ); + if (!status) { - /* folder is on a new filesystem, return the last folder */ - RtlFreeAnsiString( &unix_name ); - break; + + if (attr_info.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) + break; + + if (!resolve_symlink( &nt_name )) + { + SetLastError( ERROR_OUTOFMEMORY ); + return FALSE; + } } } - RtlFreeAnsiString( &unix_name ); - last_pos = pos; - c = strrchrW( volumenameW, '\' ); - if (c != NULL) - pos = c-volumenameW; - } while (c != NULL && pos > stop_pos);
- if (status != STATUS_SUCCESS) + if (nt_name.Buffer[(nt_name.Length / sizeof(WCHAR)) - 1] == '\') + nt_name.Length -= sizeof(WCHAR); + while (nt_name.Length && nt_name.Buffer[(nt_name.Length / sizeof(WCHAR)) - 1] != '\') + nt_name.Length -= sizeof(WCHAR); + } + + nt_name.Buffer[nt_name.Length / sizeof(WCHAR)] = 0; + + if (NtQueryAttributesFile( &attr, &basic_info )) { - WCHAR cwdW[MAX_PATH]; + RtlFreeUnicodeString( &nt_name ); + WARN("nonexistent path %s -> %s\n", debugstr_w(path), debugstr_w( nt_name.Buffer )); + SetLastError( ERROR_FILE_NOT_FOUND ); + return FALSE; + }
- /* the path was completely invalid */ - if (filename[0] == '\' && strncmpW(deviceprefixW, filename, strlenW(deviceprefixW)) != 0) + if (!memcmp(path, device_prefix, sizeof(device_prefix)) + || !memcmp(path, device_prefix2, sizeof(device_prefix2))) + { + if (length >= nt_name.Length / sizeof(WCHAR)) { - /* NT-style paths (that are not device paths) fail */ - status = STATUS_OBJECT_NAME_INVALID; - goto cleanup; - } + memcpy(volume_path, path, 4 * sizeof(WCHAR)); + lstrcpynW( volume_path + 4, nt_name.Buffer + 4, length - 4 );
- /* DOS-style paths (anything not beginning with a slash) have fallback replies */ - if (filename[1] == ':') - { - /* if the path is semi-sane (X:) then use the given drive letter (if it is mounted) */ - fallbackpathW[0] = filename[0]; - if (!isalphaW(filename[0]) || GetDriveTypeW( fallbackpathW ) == DRIVE_NO_ROOT_DIR) - { - status = STATUS_OBJECT_NAME_NOT_FOUND; - goto cleanup; - } - } - else if (GetCurrentDirectoryW(ARRAY_SIZE(cwdW), cwdW )) - { - /* if the path is completely bogus then revert to the drive of the working directory */ - fallbackpathW[0] = cwdW[0]; - } - else - { - status = STATUS_OBJECT_NAME_INVALID; - goto cleanup; + TRACE("%s -> %s\n", debugstr_w(path), debugstr_w(volume_path)); + + RtlFreeUnicodeString( &nt_name ); + return TRUE; } - last_pos = strlenW(fallbackpathW) - 1; /* points to \ */ - filename = fallbackpathW; - status = STATUS_SUCCESS; } - - if (last_pos + 1 <= buflen) + else if (length >= (nt_name.Length / sizeof(WCHAR)) - 4) { - memcpy(volumepathname, filename, last_pos * sizeof(WCHAR)); - if (last_pos + 2 <= buflen) volumepathname[last_pos++] = '\'; - volumepathname[last_pos] = '\0'; + lstrcpynW( volume_path, nt_name.Buffer + 4, length ); + volume_path[0] = toupperW(volume_path[0]);
- /* DOS-style paths always return upper-case drive letters */ - if (volumepathname[1] == ':') - volumepathname[0] = toupperW(volumepathname[0]); + TRACE("%s -> %s\n", debugstr_w(path), debugstr_w(volume_path));
- TRACE("Successfully translated path %s to mount-point %s\n", - debugstr_w(filename), debugstr_w(volumepathname)); + RtlFreeUnicodeString( &nt_name ); + return TRUE; } - else - status = STATUS_NAME_TOO_LONG;
-cleanup: - HeapFree( GetProcessHeap(), 0, volumenameW ); - return set_ntstatus( status ); + RtlFreeUnicodeString( &nt_name ); + SetLastError( ERROR_FILENAME_EXCED_RANGE ); + return FALSE; }
Hi,
While running your changed tests, I think I found new failures. Being a bot and all I'm not very good at pattern recognition, so I might be wrong, but could you please double-check?
Full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=67627
Your paranoid android.
=== debiant (32 bit report) ===
kernel32: debugger.c:148: Test failed: wrong size for 'C:\users\winetest\Temp\wtb7e0.tmp': read=0 debugger.c:1128: Test failed: Got 2076730001 failures from child process. debugger.c:148: Test failed: wrong size for 'C:\users\winetest\Temp\wtb849.tmp': read=0 debugger.c:1128: Test failed: Got 2076730001 failures from child process. debugger.c:148: Test failed: wrong size for 'C:\users\winetest\Temp\wtb8a4.tmp': read=0 debugger.c:1128: Test failed: Got 2076730001 failures from child process.
=== debiant (32 bit Chinese:China report) ===
kernel32: debugger.c:148: Test failed: wrong size for 'C:\users\winetest\Temp\wt4c4.tmp': read=0 debugger.c:1128: Test failed: Got 2076730001 failures from child process. debugger.c:debugger.c968:: Test failed: 148OpenEvent failed, last error : Test failed: 2wrong size for '. debugger.c:1128: Test failed: Got debugger.c2076730001: failures from child process. 977: Test failed: OpenEvent failed, last error 2. debugger.c:148: Test failed: wrong size for 'C:\users\winetest\Temp\wt577.tmp': read=0 debugger.c:1128: Test failed: Got 2076730001 failures from child process.
=== debiant (32 bit WoW report) ===
kernel32: debugger.c:debugger.c968:148: Test failed: wrong size for 'C:\users\winetest\Temp\wt8091.tmp': read=0 debugger.c:1128: Test failed: Got 2076730001 failures from child process. debugger.c:148: Test failed: wrong size for 'C:\users\winetest\Temp\wt80f2.tmpdebugger.c': read=:0 968: Test failed: OpenEvent failed, last error 2. debugger.c:1128: Test failed: Got 2076730001 failures from child process. debugger.c:977: Test failed: OpenEvent failed, last error 2. debugger.c:148: Test failed: wrong size for 'C:\users\winetest\Temp\wt814c.tmp': read=debugger.c0: debugger.c:1128: Test failed: Got 2076730001 failures from child process.
=== debiant (64 bit WoW report) ===
kernel32: debugger.c:148: Test failed: wrong size for 'C:\users\winetest\Temp\wtd01b.tmp': read=0 debugger.c:1128: Test failed: Got 2076730001 failures from child process. debugger.c:148: Test failed: wrong size for 'C:\users\winetest\Temp\wtd081.tmp': read=0 debugger.c:1128: Test failed: Got 2076730001 failures from child process. debugger.c:148: Test failed: wrong size for 'debugger.cC:\users\winetest\Temp\wtd0c8.tmp': read=0 debugger.c:1128: Test failed: Got 2076730001 failures from child process.
On 3/19/20 11:36 PM, Marvin wrote:
Hi,
While running your changed tests, I think I found new failures. Being a bot and all I'm not very good at pattern recognition, so I might be wrong, but could you please double-check?
Full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=67627
Your paranoid android.
=== debiant (32 bit report) ===
kernel32: debugger.c:148: Test failed: wrong size for 'C:\users\winetest\Temp\wtb7e0.tmp': read=0 debugger.c:1128: Test failed: Got 2076730001 failures from child process. debugger.c:148: Test failed: wrong size for 'C:\users\winetest\Temp\wtb849.tmp': read=0 debugger.c:1128: Test failed: Got 2076730001 failures from child process. debugger.c:148: Test failed: wrong size for 'C:\users\winetest\Temp\wtb8a4.tmp': read=0 debugger.c:1128: Test failed: Got 2076730001 failures from child process.
This seems to be a coincidence, and another victim of temp filenames in failure messages:
http://test.winehq.org/data/aaa625217b202dc0a22d67e63e941b027146a456/linux_n...
That said, it's odd that it occurs in the first place only on debiant. Not that I even know if that can cause this error, but do we know that the machine has the requisite permissions set for ptrace?
=== debiant (32 bit Chinese:China report) ===
kernel32: debugger.c:148: Test failed: wrong size for 'C:\users\winetest\Temp\wt4c4.tmp': read=0 debugger.c:1128: Test failed: Got 2076730001 failures from child process. debugger.c:debugger.c968:: Test failed: 148OpenEvent failed, last error : Test failed: 2wrong size for '. debugger.c:1128: Test failed: Got debugger.c2076730001: failures from child process. 977: Test failed: OpenEvent failed, last error 2. debugger.c:148: Test failed: wrong size for 'C:\users\winetest\Temp\wt577.tmp': read=0 debugger.c:1128: Test failed: Got 2076730001 failures from child process.
=== debiant (32 bit WoW report) ===
kernel32: debugger.c:debugger.c968:148: Test failed: wrong size for 'C:\users\winetest\Temp\wt8091.tmp': read=0 debugger.c:1128: Test failed: Got 2076730001 failures from child process. debugger.c:148: Test failed: wrong size for 'C:\users\winetest\Temp\wt80f2.tmpdebugger.c': read=:0 968: Test failed: OpenEvent failed, last error 2. debugger.c:1128: Test failed: Got 2076730001 failures from child process. debugger.c:977: Test failed: OpenEvent failed, last error 2. debugger.c:148: Test failed: wrong size for 'C:\users\winetest\Temp\wt814c.tmp': read=debugger.c0: debugger.c:1128: Test failed: Got 2076730001 failures from child process.
=== debiant (64 bit WoW report) ===
kernel32: debugger.c:148: Test failed: wrong size for 'C:\users\winetest\Temp\wtd01b.tmp': read=0 debugger.c:1128: Test failed: Got 2076730001 failures from child process. debugger.c:148: Test failed: wrong size for 'C:\users\winetest\Temp\wtd081.tmp': read=0 debugger.c:1128: Test failed: Got 2076730001 failures from child process. debugger.c:148: Test failed: wrong size for 'debugger.cC:\users\winetest\Temp\wtd0c8.tmp': read=0 debugger.c:1128: Test failed: Got 2076730001 failures from child process.
Hi,
While running your changed tests, I think I found new failures. Being a bot and all I'm not very good at pattern recognition, so I might be wrong, but could you please double-check?
Full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=67623
Your paranoid android.
=== debiant (32 bit report) ===
kernel32: volume.c:923: Test failed: Expected "C:.\", got "C:\". volume.c:928: Test failed: Expected "C:.\", got "C:\".
=== debiant (32 bit French report) ===
kernel32: volume.c:923: Test failed: Expected "C:.\", got "C:\". volume.c:928: Test failed: Expected "C:.\", got "C:\".
=== debiant (32 bit Japanese:Japan report) ===
kernel32: volume.c:923: Test failed: Expected "C:.\", got "C:\". volume.c:928: Test failed: Expected "C:.\", got "C:\".
=== debiant (32 bit Chinese:China report) ===
kernel32: volume.c:923: Test failed: Expected "C:.\", got "C:\". volume.c:928: Test failed: Expected "C:.\", got "C:\".
=== debiant (32 bit WoW report) ===
kernel32: volume.c:923: Test failed: Expected "C:.\", got "C:\". volume.c:928: Test failed: Expected "C:.\", got "C:\".
=== debiant (64 bit WoW report) ===
kernel32: volume.c:923: Test failed: Expected "C:.\", got "C:\". volume.c:928: Test failed: Expected "C:.\", got "C:\".