Signed-off-by: Zebediah Figura zfigura@codeweavers.com --- v2: fix test failures on w2008 and w8adm
The goal of these tests is not immediately to guide an implementation of mounted folders (though they could certainly help), but rather to show how other volume-related functions behave around mounted folders.
The end goal is to sanely treat Unix mount points other than / as mounted folders on the Z: drive. This allows us to do away with Unix file I/O in GetVolumePathName(), while still returning the correct mount point for any given file. In particular, [1] documents the original purpose of the implementation—that the path returned by GetVolumePathName() be the on the same host partition that the input file is on. Mounted folders respect this invariant, including where symlinks are concerned.
To that end, I've also added some documentation regarding symlinks across volumes, which cannot be systematically tested.
[1] https://www.winehq.org/pipermail/wine-patches/2011-October/107470.html
dlls/kernel32/tests/volume.c | 231 +++++++++++++++++++++++++++++++++++ 1 file changed, 231 insertions(+)
diff --git a/dlls/kernel32/tests/volume.c b/dlls/kernel32/tests/volume.c index b9121a4af4c..25495c9ce1c 100644 --- a/dlls/kernel32/tests/volume.c +++ b/dlls/kernel32/tests/volume.c @@ -22,6 +22,7 @@ #include "winbase.h" #include "winioctl.h" #include "ntddstor.h" +#include "winternl.h" #include <stdio.h> #include "ddk/ntddcdvd.h" #include "ddk/mountmgr.h" @@ -59,6 +60,7 @@ 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);
/* ############################### */
@@ -1302,6 +1304,233 @@ static void test_cdrom_ioctl(void)
}
+static void test_mounted_folder(void) +{ + char name_buffer[200], path[MAX_PATH], volume_name[100], *p; + FILE_NAME_INFORMATION *name = (FILE_NAME_INFORMATION *)name_buffer; + FILE_ATTRIBUTE_TAG_INFO info; + IO_STATUS_BLOCK io; + BOOL ret, got_path; + NTSTATUS status; + HANDLE file; + DWORD size; + + ret = CreateDirectoryA("C:\winetest_mnt", NULL); + if (!ret && GetLastError() == ERROR_ACCESS_DENIED) + { + skip("Not enough permissions to create a folder in the C: drive.\n"); + return; + } + ok(ret, "got error %u\n", GetLastError()); + + ret = GetVolumeNameForVolumeMountPointA( "C:\", volume_name, sizeof(volume_name) ); + ok(ret, "got error %u\n", GetLastError()); + + ret = SetVolumeMountPointA( "C:\winetest_mnt\", volume_name ); + if (!ret) + { + skip("Not enough permissions to create a mounted folder.\n"); + RemoveDirectoryA( "C:\winetest_mnt" ); + return; + } + todo_wine ok(ret, "got error %u\n", GetLastError()); + + file = CreateFileA( "C:\winetest_mnt", 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL ); + ok(file != INVALID_HANDLE_VALUE, "got error %u\n", GetLastError()); + + status = NtQueryInformationFile( file, &io, &info, sizeof(info), FileAttributeTagInformation ); + ok(!status, "got status %#x\n", status); + ok((info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) + && (info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY), "got attributes %#x\n", info.FileAttributes); + ok(info.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT, "got reparse tag %#x\n", info.ReparseTag); + + status = NtQueryInformationFile( file, &io, name, sizeof(name_buffer), FileNameInformation ); + ok(!status, "got status %#x\n", status); + ok(name->FileNameLength == wcslen(L"\winetest_mnt") * sizeof(WCHAR), "got length %u\n", name->FileNameLength); + ok(!wcsnicmp(name->FileName, L"\winetest_mnt", wcslen(L"\winetest_mnt")), "got name %s\n", + debugstr_wn(name->FileName, name->FileNameLength / sizeof(WCHAR))); + + CloseHandle( file ); + + file = CreateFileA( "C:\winetest_mnt", 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL ); + ok(file != INVALID_HANDLE_VALUE, "got error %u\n", GetLastError()); + + status = NtQueryInformationFile( file, &io, &info, sizeof(info), FileAttributeTagInformation ); + ok(!status, "got status %#x\n", status); + ok(!(info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) + && (info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY), "got attributes %#x\n", info.FileAttributes); + ok(!info.ReparseTag, "got reparse tag %#x\n", info.ReparseTag); + + status = NtQueryInformationFile( file, &io, name, sizeof(name_buffer), FileNameInformation ); + ok(!status, "got status %#x\n", status); + ok(name->FileNameLength == wcslen(L"\") * sizeof(WCHAR), "got length %u\n", name->FileNameLength); + ok(!wcsnicmp(name->FileName, L"\", wcslen(L"\")), "got name %s\n", + debugstr_wn(name->FileName, name->FileNameLength / sizeof(WCHAR))); + + CloseHandle( file ); + + file = CreateFileA( "C:\winetest_mnt\windows", 0, FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL ); + ok(file != INVALID_HANDLE_VALUE, "got error %u\n", GetLastError()); + + status = NtQueryInformationFile( file, &io, name, sizeof(name_buffer), FileNameInformation ); + ok(!status, "got status %#x\n", status); + ok(name->FileNameLength == wcslen(L"\windows") * sizeof(WCHAR), "got length %u\n", name->FileNameLength); + ok(!wcsnicmp(name->FileName, L"\windows", wcslen(L"\windows")), "got name %s\n", + debugstr_wn(name->FileName, name->FileNameLength / sizeof(WCHAR))); + + CloseHandle( file ); + + ret = GetVolumePathNameA( "C:\winetest_mnt", path, sizeof(path) ); + ok(ret, "got error %u\n", GetLastError()); + ok(!strcmp(path, "C:\winetest_mnt\"), "got %s\n", debugstr_a(path)); + SetLastError(0xdeadbeef); + ret = GetVolumeNameForVolumeMountPointA( "C:\winetest_mnt", path, sizeof(path) ); + ok(!ret, "expected failure\n"); + ok(GetLastError() == ERROR_INVALID_NAME, "wrong error %u\n", GetLastError()); + SetLastError(0xdeadbeef); + ret = GetVolumeInformationA( "C:\winetest_mnt", NULL, 0, NULL, NULL, NULL, NULL, 0 ); + ok(!ret, "expected failure\n"); + ok(GetLastError() == ERROR_INVALID_NAME, "wrong error %u\n", GetLastError()); + + ret = GetVolumeNameForVolumeMountPointA( "C:\winetest_mnt\", path, sizeof(path) ); + ok(ret, "got error %u\n", GetLastError()); + ok(!strcmp(path, volume_name), "expected %s, got %s\n", debugstr_a(volume_name), debugstr_a(path)); + ret = GetVolumeInformationA( "C:\winetest_mnt\", NULL, 0, NULL, NULL, NULL, NULL, 0 ); + ok(ret, "got error %u\n", GetLastError()); + + ret = GetVolumePathNameA( "C:\winetest_mnt\windows", path, sizeof(path) ); + ok(ret, "got error %u\n", GetLastError()); + ok(!strcmp(path, "C:\winetest_mnt\"), "got %s\n", debugstr_a(path)); + SetLastError(0xdeadbeef); + ret = GetVolumeNameForVolumeMountPointA( "C:\winetest_mnt\windows\", path, sizeof(path) ); + ok(!ret, "expected failure\n"); + ok(GetLastError() == ERROR_NOT_A_REPARSE_POINT, "wrong error %u\n", GetLastError()); + SetLastError(0xdeadbeef); + ret = GetVolumeInformationA( "C:\winetest_mnt\windows\", NULL, 0, NULL, NULL, NULL, NULL, 0 ); + ok(!ret, "expected failure\n"); + ok(GetLastError() == ERROR_DIR_NOT_ROOT, "wrong error %u\n", GetLastError()); + + ret = GetVolumePathNameA( "C:\winetest_mnt\nonexistent\", path, sizeof(path) ); + ok(ret, "got error %u\n", GetLastError()); + ok(!strcmp(path, "C:\winetest_mnt\"), "got %s\n", debugstr_a(path)); + SetLastError(0xdeadbeef); + ret = GetVolumeNameForVolumeMountPointA( "C:\winetest_mnt\nonexistent\", path, sizeof(path) ); + ok(!ret, "expected failure\n"); + ok(GetLastError() == ERROR_FILE_NOT_FOUND, "wrong error %u\n", GetLastError()); + SetLastError(0xdeadbeef); + ret = GetVolumeInformationA( "C:\winetest_mnt\nonexistent\", NULL, 0, NULL, NULL, NULL, NULL, 0 ); + ok(!ret, "expected failure\n"); + ok(GetLastError() == ERROR_FILE_NOT_FOUND, "wrong error %u\n", GetLastError()); + + ret = GetVolumePathNameA( "C:\winetest_mnt\winetest_mnt", path, sizeof(path) ); + ok(ret, "got error %u\n", GetLastError()); + ok(!strcmp(path, "C:\winetest_mnt\winetest_mnt\"), "got %s\n", debugstr_a(path)); + ret = GetVolumeNameForVolumeMountPointA( "C:\winetest_mnt\winetest_mnt\", path, sizeof(path) ); + ok(ret, "got error %u\n", GetLastError()); + ok(!strcmp(path, volume_name), "expected %s, got %s\n", debugstr_a(volume_name), debugstr_a(path)); + ret = GetVolumeInformationA( "C:\winetest_mnt\winetest_mnt\", NULL, 0, NULL, NULL, NULL, NULL, 0 ); + ok(ret, "got error %u\n", GetLastError()); + + ret = GetVolumePathNameA( "C:/winetest_mnt/../winetest_mnt/.", path, sizeof(path) ); + ok(ret, "got error %u\n", GetLastError()); + ok(!strcmp(path, "C:\winetest_mnt\"), "got %s\n", debugstr_a(path)); + ret = GetVolumeNameForVolumeMountPointA( "C:/winetest_mnt/../winetest_mnt/.\", path, sizeof(path) ); + ok(ret, "got error %u\n", GetLastError()); + ok(!strcmp(path, volume_name), "expected %s, got %s\n", debugstr_a(volume_name), debugstr_a(path)); + ret = GetVolumeInformationA( "C:/winetest_mnt/../winetest_mnt/.\", NULL, 0, NULL, NULL, NULL, NULL, 0 ); + ok(ret, "got error %u\n", GetLastError()); + + ret = GetVolumePathNamesForVolumeNameA( volume_name, path, sizeof(path), &size ); + ok(ret, "got error %u\n", GetLastError()); + got_path = FALSE; + for (p = path; *p; p += strlen(p) + 1) + { + if (!strcmp( p, "C:\winetest_mnt\" )) + got_path = TRUE; + ok(strcmp( p, "C:\winetest_mnt\winetest_mnt\" ), "GetVolumePathNamesForVolumeName() should not recurse\n"); + } + ok(got_path, "mount point was not enumerated\n"); + + /* test interaction with symbolic links */ + + if (pCreateSymbolicLinkA) + { + ret = pCreateSymbolicLinkA( "C:\winetest_link", "C:\winetest_mnt\", SYMBOLIC_LINK_FLAG_DIRECTORY ); + ok(ret, "got error %u\n", GetLastError()); + + ret = GetVolumePathNameA( "C:\winetest_link\", path, sizeof(path) ); + ok(ret, "got error %u\n", GetLastError()); + ok(!strcmp(path, "C:\"), "got %s\n", path); + SetLastError(0xdeadbeef); + ret = GetVolumeNameForVolumeMountPointA( "C:\winetest_link\", path, sizeof(path) ); + ok(!ret, "expected failure\n"); + ok(GetLastError() == ERROR_INVALID_PARAMETER + || broken(GetLastError() == ERROR_SUCCESS) /* 2008 */, "wrong error %u\n", GetLastError()); + ret = GetVolumeInformationA( "C:\winetest_link\", NULL, 0, NULL, NULL, NULL, NULL, 0 ); + ok(ret, "got error %u\n", GetLastError()); + + ret = GetVolumePathNameA( "C:\winetest_link\windows\", path, sizeof(path) ); + ok(ret, "got error %u\n", GetLastError()); + ok(!strcmp(path, "C:\"), "got %s\n", path); + SetLastError(0xdeadbeef); + ret = GetVolumeNameForVolumeMountPointA( "C:\winetest_link\windows\", path, sizeof(path) ); + ok(!ret, "expected failure\n"); + ok(GetLastError() == ERROR_NOT_A_REPARSE_POINT, "wrong error %u\n", GetLastError()); + SetLastError(0xdeadbeef); + ret = GetVolumeInformationA( "C:\winetest_link\windows\", NULL, 0, NULL, NULL, NULL, NULL, 0 ); + ok(!ret, "expected failure\n"); + ok(GetLastError() == ERROR_DIR_NOT_ROOT, "wrong error %u\n", GetLastError()); + + ret = GetVolumePathNameA( "C:\winetest_link\winetest_mnt", path, sizeof(path) ); + ok(ret, "got error %u\n", GetLastError()); + ok(!strcmp(path, "C:\winetest_link\winetest_mnt\"), "got %s\n", debugstr_a(path)); + ret = GetVolumeNameForVolumeMountPointA( "C:\winetest_link\winetest_mnt\", path, sizeof(path) ); + ok(ret, "got error %u\n", GetLastError()); + ok(!strcmp(path, volume_name), "expected %s, got %s\n", debugstr_a(volume_name), debugstr_a(path)); + ret = GetVolumeInformationA( "C:\winetest_link\winetest_mnt\", NULL, 0, NULL, NULL, NULL, NULL, 0 ); + ok(ret, "got error %u\n", GetLastError()); + + /* The following test makes it clear that when we encounter a symlink + * while resolving, we resolve *every* junction in the path, i.e. both + * mount points and symlinks. */ + ret = GetVolumePathNameA( "C:\winetest_link\winetest_mnt\winetest_link\windows\", path, sizeof(path) ); + ok(ret, "got error %u\n", GetLastError()); + ok(!strcmp(path, "C:\") || !strcmp(path, "C:\winetest_link\winetest_mnt\" /* 2008 */, + "got %s\n", debugstr_a(path)); + + file = CreateFileA( "C:\winetest_link\winetest_mnt\winetest_link\windows\", 0, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL ); + ok(file != INVALID_HANDLE_VALUE, "got error %u\n", GetLastError()); + + status = NtQueryInformationFile( file, &io, name, sizeof(name_buffer), FileNameInformation ); + ok(!status, "got status %#x\n", status); + ok(name->FileNameLength == wcslen(L"\windows") * sizeof(WCHAR), "got length %u\n", name->FileNameLength); + ok(!wcsnicmp(name->FileName, L"\windows", wcslen(L"\windows")), "got name %s\n", + debugstr_wn(name->FileName, name->FileNameLength / sizeof(WCHAR))); + + CloseHandle( file ); + + ret = RemoveDirectoryA( "C:\winetest_link\" ); + ok(ret, "got error %u\n", GetLastError()); + + /* The following cannot be automatically tested: + * + * Suppose C: and D: are mount points of different volumes. If C:/a is + * symlinked to D:/b, GetVolumePathName("C:\a") will return "D:\" if + * "a" is a directory, but "C:\" if "a" is a file. + * Moreover, if D: is mounted at C:/mnt, and C:/a is symlinked to + * C:/mnt, GetVolumePathName("C:\mnt\b") will still return "D:\". */ + } + + ret = DeleteVolumeMountPointA( "C:\winetest_mnt\" ); + ok(ret, "got error %u\n", GetLastError()); + ret = RemoveDirectoryA( "C:\winetest_mnt" ); + ok(ret, "got error %u\n", GetLastError()); +} + START_TEST(volume) { hdll = GetModuleHandleA("kernel32.dll"); @@ -1317,6 +1546,7 @@ START_TEST(volume) pGetVolumePathNameW = (void *) GetProcAddress(hdll, "GetVolumePathNameW"); pGetVolumePathNamesForVolumeNameA = (void *) GetProcAddress(hdll, "GetVolumePathNamesForVolumeNameA"); pGetVolumePathNamesForVolumeNameW = (void *) GetProcAddress(hdll, "GetVolumePathNamesForVolumeNameW"); + pCreateSymbolicLinkA = (void *) GetProcAddress(hdll, "CreateSymbolicLinkA");
test_query_dos_deviceA(); test_dos_devices(); @@ -1334,4 +1564,5 @@ START_TEST(volume) test_GetVolumePathNamesForVolumeNameA(); test_GetVolumePathNamesForVolumeNameW(); test_cdrom_ioctl(); + test_mounted_folder(); }
Wine intentionally treats these specially, so there is no point in testing them.
Signed-off-by: Zebediah Figura zfigura@codeweavers.com --- This fixes test failures on Debian reported in v1 of this patch series.
dlls/kernel32/tests/volume.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-)
diff --git a/dlls/kernel32/tests/volume.c b/dlls/kernel32/tests/volume.c index 25495c9ce1c..9a809249545 100644 --- a/dlls/kernel32/tests/volume.c +++ b/dlls/kernel32/tests/volume.c @@ -847,26 +847,22 @@ static void test_GetVolumePathNameA(void) NO_ERROR, NO_ERROR }, { /* test 37 */ - "/unix-style/absolute/path", "%CurrentDrive%\", sizeof(volume_path), - NO_ERROR, NO_ERROR - }, - { /* test 38 */ "\??\C:\NonExistent", "%CurrentDrive%\", sizeof(volume_path), NO_ERROR, NO_ERROR }, - { /* test 39 */ + { /* test 38 */ "\??\M:\NonExistent", "%CurrentDrive%\", sizeof(volume_path), NO_ERROR, NO_ERROR }, - { /* test 40 */ + { /* test 39 */ "somefile:def", "%CurrentDrive%\", sizeof(volume_path), NO_ERROR, NO_ERROR }, - { /* test 41 */ + { /* test 40 */ "s:omefile", "S:\" /* win2k, winxp */, sizeof(volume_path), ERROR_FILE_NOT_FOUND, NO_ERROR }, - { /* test 42: a reasonable forward slash path that is guaranteed to exist */ + { /* test 41: a reasonable forward slash path that is guaranteed to exist */ "C:/windows/system32", "C:\", sizeof(volume_path), NO_ERROR, NO_ERROR },
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=67179
Your paranoid android.
=== build (build log) ===
../../../../wine/dlls/kernel32/tests/volume.c:1497:38: error: too many arguments to function ‘strcmp’ ../../../../wine/dlls/kernel32/tests/volume.c:1498:46: error: expected ‘)’ before ‘;’ token ../../../../wine/include/wine/test.h:115:83: error: too few arguments to function ‘winetest_ok’ ../../../../wine/dlls/kernel32/tests/volume.c:1522:5: error: expected ‘;’ before ‘}’ token Makefile:1287: recipe for target 'volume.o' failed Makefile:7950: recipe for target 'dlls/kernel32/tests' failed Task: The exe32 Wine crossbuild failed
=== debiant (build log) ===
../../../../wine/dlls/kernel32/tests/volume.c:1497:38: error: too many arguments to function ‘strcmp’ ../../../../wine/dlls/kernel32/tests/volume.c:1498:46: error: expected ‘)’ before ‘;’ token ../../../../wine/include/wine/test.h:115:83: error: too few arguments to function ‘winetest_ok’ ../../../../wine/dlls/kernel32/tests/volume.c:1513:51: error: expected ‘;’ before ‘}’ token Task: The win32 build failed
=== debiant (build log) ===
../../../../wine/dlls/kernel32/tests/volume.c:1497:38: error: too many arguments to function ‘strcmp’ ../../../../wine/dlls/kernel32/tests/volume.c:1498:46: error: expected ‘)’ before ‘;’ token ../../../../wine/include/wine/test.h:115:83: error: too few arguments to function ‘winetest_ok’ ../../../../wine/dlls/kernel32/tests/volume.c:1513:51: error: expected ‘;’ before ‘}’ token Task: The wow64 build failed
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; }
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=67181
Your paranoid android.
=== debiant (build log) ===
../../../../wine/dlls/kernel32/tests/volume.c:1497:38: error: too many arguments to function ‘strcmp’ ../../../../wine/dlls/kernel32/tests/volume.c:1498:46: error: expected ‘)’ before ‘;’ token ../../../../wine/include/wine/test.h:115:83: error: too few arguments to function ‘winetest_ok’ ../../../../wine/dlls/kernel32/tests/volume.c:1513:51: error: expected ‘;’ before ‘}’ token Task: The win32 build failed
=== debiant (build log) ===
../../../../wine/dlls/kernel32/tests/volume.c:1497:38: error: too many arguments to function ‘strcmp’ ../../../../wine/dlls/kernel32/tests/volume.c:1498:46: error: expected ‘)’ before ‘;’ token ../../../../wine/include/wine/test.h:115:83: error: too few arguments to function ‘winetest_ok’ ../../../../wine/dlls/kernel32/tests/volume.c:1513:51: error: expected ‘;’ before ‘}’ token Task: The wow64 build failed
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
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=67182
Your paranoid android.
=== debiant (build log) ===
../../../../wine/dlls/kernel32/tests/volume.c:1497:38: error: too many arguments to function ‘strcmp’ ../../../../wine/dlls/kernel32/tests/volume.c:1498:46: error: expected ‘)’ before ‘;’ token ../../../../wine/include/wine/test.h:115:83: error: too few arguments to function ‘winetest_ok’ ../../../../wine/dlls/kernel32/tests/volume.c:1513:51: error: expected ‘;’ before ‘}’ token Task: The win32 build failed
=== debiant (build log) ===
../../../../wine/dlls/kernel32/tests/volume.c:1497:38: error: too many arguments to function ‘strcmp’ ../../../../wine/dlls/kernel32/tests/volume.c:1498:46: error: expected ‘)’ before ‘;’ token ../../../../wine/include/wine/test.h:115:83: error: too few arguments to function ‘winetest_ok’ ../../../../wine/dlls/kernel32/tests/volume.c:1513:51: error: expected ‘;’ before ‘}’ token Task: The wow64 build failed
Signed-off-by: Zebediah Figura zfigura@codeweavers.com --- v2: fix a double free; thanks Zhiyi
dlls/kernel32/volume.c | 230 +++++++++++++++++++++-------------------- 1 file changed, 120 insertions(+), 110 deletions(-)
diff --git a/dlls/kernel32/volume.c b/dlls/kernel32/volume.c index 5bfebf60570..d344a4bfd5e 100644 --- a/dlls/kernel32/volume.c +++ b/dlls/kernel32/volume.c @@ -1591,149 +1591,159 @@ 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] = {'\','?','?','\'}; + static const WCHAR dotW[] = {'.',0}; + 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; + NTSTATUS status;
- TRACE("(%s, %p, %d)\n", debugstr_w(filename), volumepathname, buflen); + if (path && !memcmp(path, global_prefix, sizeof(global_prefix))) + path = dotW;
- if (!filename || !volumepathname || !buflen) + if (!volume_path || !length || !RtlDosPathNameToNtPathName_U( path, &nt_name, NULL, NULL )) { - SetLastError(ERROR_INVALID_PARAMETER); + SetLastError( ERROR_INVALID_PARAMETER ); 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 (!is_dos_path( &nt_name )) { - SetLastError( ERROR_NOT_ENOUGH_MEMORY ); + RtlFreeUnicodeString( &nt_name ); + WARN("invalid path %s\n", debugstr_w(path)); + SetLastError( ERROR_INVALID_NAME ); return FALSE; } - strcpyW( volumenameW, filename );
- /* Normalize path */ - for (c = volumenameW; *c; c++) if (*c == '/') *c = '\'; - - 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=67178
Your paranoid android.
=== build (build log) ===
../../../../wine/dlls/kernel32/tests/volume.c:1501:38: error: too many arguments to function ‘strcmp’ ../../../../wine/dlls/kernel32/tests/volume.c:1502:46: error: expected ‘)’ before ‘;’ token ../../../../wine/include/wine/test.h:115:83: error: too few arguments to function ‘winetest_ok’ ../../../../wine/dlls/kernel32/tests/volume.c:1526:5: error: expected ‘;’ before ‘}’ token Makefile:1287: recipe for target 'volume.o' failed Makefile:7950: recipe for target 'dlls/kernel32/tests' failed Task: The exe32 Wine crossbuild failed
=== debiant (build log) ===
../../../../wine/dlls/kernel32/tests/volume.c:1501:38: error: too many arguments to function ‘strcmp’ ../../../../wine/dlls/kernel32/tests/volume.c:1502:46: error: expected ‘)’ before ‘;’ token ../../../../wine/include/wine/test.h:115:83: error: too few arguments to function ‘winetest_ok’ ../../../../wine/dlls/kernel32/tests/volume.c:1517:51: error: expected ‘;’ before ‘}’ token Task: The win32 build failed
=== debiant (build log) ===
../../../../wine/dlls/kernel32/tests/volume.c:1501:38: error: too many arguments to function ‘strcmp’ ../../../../wine/dlls/kernel32/tests/volume.c:1502:46: error: expected ‘)’ before ‘;’ token ../../../../wine/include/wine/test.h:115:83: error: too few arguments to function ‘winetest_ok’ ../../../../wine/dlls/kernel32/tests/volume.c:1517:51: error: expected ‘;’ before ‘}’ token Task: The wow64 build failed