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(); }