[PATCH 1/5] kernel32/tests: Add some tests for mounted folders.

Zebediah Figura z.figura12 at gmail.com
Sat Mar 14 10:40:57 CDT 2020


Signed-off-by: Zebediah Figura <zfigura at codeweavers.com>
---
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 | 219 +++++++++++++++++++++++++++++++++++
 1 file changed, 219 insertions(+)

diff --git a/dlls/kernel32/tests/volume.c b/dlls/kernel32/tests/volume.c
index b9121a4af4c..f8fb09d74e4 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,221 @@ 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 mounted folder.\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 );
+    todo_wine ok(ret, "got error %u\n", GetLastError());
+    if (!ret)
+    {
+        RemoveDirectoryA( "C:\\winetest_mnt" );
+        return;
+    }
+
+    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));
+    ret = GetVolumeNameForVolumeMountPointA( "C:\\winetest_mnt", path, sizeof(path) );
+    ok(!ret, "expected failure\n");
+    ok(GetLastError() == ERROR_INVALID_NAME, "wrong error %u\n", GetLastError());
+    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));
+    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());
+    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));
+    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());
+    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);
+        ret = GetVolumeNameForVolumeMountPointA( "C:\\winetest_link\\", path, sizeof(path) );
+        ok(!ret, "expected failure\n");
+        ok(GetLastError() == ERROR_INVALID_PARAMETER, "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);
+        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());
+        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:\\"), "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 +1534,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 +1552,5 @@ START_TEST(volume)
     test_GetVolumePathNamesForVolumeNameA();
     test_GetVolumePathNamesForVolumeNameW();
     test_cdrom_ioctl();
+    test_mounted_folder();
 }
-- 
2.25.1




More information about the wine-devel mailing list