https://git.reactos.org/?p=reactos.git;a=commitdiff;h=cc1deb29024ae58e52832f8bb07ac23fdbafe281

commit cc1deb29024ae58e52832f8bb07ac23fdbafe281
Author:     Hermès Bélusca-Maïto <[email protected]>
AuthorDate: Mon Jan 6 21:54:20 2025 +0100
Commit:     Hermès Bélusca-Maïto <[email protected]>
CommitDate: Tue Jan 21 19:15:57 2025 +0100

    [MOUNTMGR_APITEST] Add tests for mountmgr 
IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH(S) (#6990)
    
    These tests verify the output of IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH,
    IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATHS and IOCTL_MOUNTMGR_QUERY_POINTS,
    as well as their consistency.
    
    References:
    https://www.osronline.com/article.cfm%5Eid=107.htm
    https://community.osr.com/t/obtaining-volume-name-with-deviceobject/15121/3
---
 modules/rostests/apitests/mountmgr/CMakeLists.txt  |   1 +
 .../apitests/mountmgr/QueryDosVolumePaths.c        | 635 +++++++++++++++++++++
 modules/rostests/apitests/mountmgr/precomp.h       |   5 +
 modules/rostests/apitests/mountmgr/testlist.c      |   2 +
 modules/rostests/apitests/mountmgr/utils.c         |  53 ++
 5 files changed, 696 insertions(+)

diff --git a/modules/rostests/apitests/mountmgr/CMakeLists.txt 
b/modules/rostests/apitests/mountmgr/CMakeLists.txt
index dbba2775f56..c53e4d79aff 100644
--- a/modules/rostests/apitests/mountmgr/CMakeLists.txt
+++ b/modules/rostests/apitests/mountmgr/CMakeLists.txt
@@ -1,5 +1,6 @@
 
 list(APPEND SOURCE
+    QueryDosVolumePaths.c
     QueryPoints.c
     utils.c)
 
diff --git a/modules/rostests/apitests/mountmgr/QueryDosVolumePaths.c 
b/modules/rostests/apitests/mountmgr/QueryDosVolumePaths.c
new file mode 100644
index 00000000000..138e45bd56e
--- /dev/null
+++ b/modules/rostests/apitests/mountmgr/QueryDosVolumePaths.c
@@ -0,0 +1,635 @@
+/*
+ * PROJECT:     ReactOS API Tests
+ * LICENSE:     GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
+ * PURPOSE:     Test for IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH(S)
+ * COPYRIGHT:   Copyright 2025 Hermès Bélusca-Maïto 
<[email protected]>
+ */
+
+#include "precomp.h"
+
+
+/**
+ * @brief
+ * Invokes either IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH or
+ * IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATHS for testing, given
+ * the volume device name, and returns an allocated volume
+ * paths buffer. This buffer must be freed by the caller via
+ * RtlFreeHeap(RtlGetProcessHeap(), ...) .
+ *
+ * These IOCTLs return both the drive letter (if any) and the
+ * volume GUID symlink path, as well as any other file-system
+ * mount reparse points linking to the volume.
+ **/
+static VOID
+Call_QueryDosVolume_Path_Paths(
+    _In_ HANDLE MountMgrHandle,
+    _In_ PCWSTR NtVolumeName,
+    _In_ ULONG IoctlPathOrPaths,
+    _Out_ PMOUNTMGR_VOLUME_PATHS* pVolumePathPtr)
+{
+    NTSTATUS Status;
+    ULONG Length;
+    IO_STATUS_BLOCK IoStatusBlock;
+    UNICODE_STRING VolumeName;
+    MOUNTMGR_VOLUME_PATHS VolumePath;
+    PMOUNTMGR_VOLUME_PATHS VolumePathPtr;
+    ULONG DeviceNameLength;
+    /*
+     * This variable is used to query the device name.
+     * It's based on MOUNTMGR_TARGET_NAME (mountmgr.h).
+     * Doing it this way prevents memory allocation.
+     * The device name won't be longer.
+     */
+    struct
+    {
+        USHORT NameLength;
+        WCHAR DeviceName[256];
+    } DeviceName;
+
+
+    *pVolumePathPtr = NULL;
+
+    /* First, build the corresponding device name */
+    RtlInitUnicodeString(&VolumeName, NtVolumeName);
+    DeviceName.NameLength = VolumeName.Length;
+    RtlCopyMemory(&DeviceName.DeviceName, VolumeName.Buffer, 
VolumeName.Length);
+    DeviceNameLength = FIELD_OFFSET(MOUNTMGR_TARGET_NAME, DeviceName) + 
DeviceName.NameLength;
+
+    /* Now, query the MountMgr for the DOS path(s) */
+    Status = NtDeviceIoControlFile(MountMgrHandle,
+                                   NULL, NULL, NULL,
+                                   &IoStatusBlock,
+                                   IoctlPathOrPaths,
+                                   &DeviceName, DeviceNameLength,
+                                   &VolumePath, sizeof(VolumePath));
+
+    /* Check for unsupported device */
+    if (Status == STATUS_NO_MEDIA_IN_DEVICE || Status == 
STATUS_INVALID_DEVICE_REQUEST)
+    {
+        skip("Device '%S': Doesn't support MountMgr queries, Status 0x%08lx\n",
+             NtVolumeName, Status);
+        return;
+    }
+
+    /* The only tolerated failure here is buffer too small, which is expected 
*/
+    ok(NT_SUCCESS(Status) || (Status == STATUS_BUFFER_OVERFLOW),
+       "Device '%S': IOCTL 0x%lx failed unexpectedly, Status 0x%08lx\n",
+       NtVolumeName, IoctlPathOrPaths, Status);
+    if (!NT_SUCCESS(Status) && (Status != STATUS_BUFFER_OVERFLOW))
+    {
+        skip("Device '%S': Wrong Status\n", NtVolumeName);
+        return;
+    }
+
+    /* Compute the needed size to store the DOS path(s).
+     * Even if MOUNTMGR_VOLUME_PATHS allows bigger name lengths
+     * than MAXUSHORT, we can't use them, because we have to return
+     * this in an UNICODE_STRING that stores length in a USHORT. */
+    Length = sizeof(VolumePath) + VolumePath.MultiSzLength;
+    ok(Length <= MAXUSHORT,
+       "Device '%S': DOS volume path too large: %lu\n",
+       NtVolumeName, Length);
+    if (Length > MAXUSHORT)
+    {
+        skip("Device '%S': Wrong Length\n", NtVolumeName);
+        return;
+    }
+
+    /* Allocate the buffer and fill it with test pattern */
+    VolumePathPtr = RtlAllocateHeap(RtlGetProcessHeap(), 0, Length);
+    if (!VolumePathPtr)
+    {
+        skip("Device '%S': Failed to allocate buffer with size %lu)\n",
+             NtVolumeName, Length);
+        return;
+    }
+    RtlFillMemory(VolumePathPtr, Length, 0xCC);
+
+    /* Re-query the DOS path(s) with the proper size */
+    Status = NtDeviceIoControlFile(MountMgrHandle,
+                                   NULL, NULL, NULL,
+                                   &IoStatusBlock,
+                                   IoctlPathOrPaths,
+                                   &DeviceName, DeviceNameLength,
+                                   VolumePathPtr, Length);
+    ok(NT_SUCCESS(Status),
+       "Device '%S': IOCTL 0x%lx failed unexpectedly, Status 0x%08lx\n",
+       NtVolumeName, IoctlPathOrPaths, Status);
+
+    if (winetest_debug > 1)
+    {
+        trace("Buffer:\n");
+        DumpBuffer(VolumePathPtr, Length);
+        printf("\n");
+    }
+
+    /* Return the buffer */
+    *pVolumePathPtr = VolumePathPtr;
+}
+
+/**
+ * @brief
+ * Invokes IOCTL_MOUNTMGR_QUERY_POINTS for testing, given
+ * the volume device name, and returns an allocated mount
+ * points buffer. This buffer must be freed by the caller
+ * via RtlFreeHeap(RtlGetProcessHeap(), ...) .
+ *
+ * This IOCTL only returns both the drive letter (if any)
+ * and the volume GUID symlink path, but does NOT return
+ * file-system mount reparse points.
+ **/
+static VOID
+Call_QueryPoints(
+    _In_ HANDLE MountMgrHandle,
+    _In_ PCWSTR NtVolumeName,
+    _Out_ PMOUNTMGR_MOUNT_POINTS* pMountPointsPtr)
+{
+    NTSTATUS Status;
+    ULONG Length;
+    IO_STATUS_BLOCK IoStatusBlock;
+    UNICODE_STRING VolumeName;
+    MOUNTMGR_MOUNT_POINTS MountPoints;
+    PMOUNTMGR_MOUNT_POINTS MountPointsPtr;
+    /*
+     * This variable is used to query the device name.
+     * It's based on MOUNTMGR_MOUNT_POINT (mountmgr.h).
+     * Doing it this way prevents memory allocation.
+     * The device name won't be longer.
+     */
+    struct
+    {
+        MOUNTMGR_MOUNT_POINT;
+        WCHAR DeviceName[256];
+    } DeviceName;
+
+
+    *pMountPointsPtr = NULL;
+
+    /* First, build the corresponding device name */
+    RtlInitUnicodeString(&VolumeName, NtVolumeName);
+    DeviceName.SymbolicLinkNameOffset = DeviceName.UniqueIdOffset = 0;
+    DeviceName.SymbolicLinkNameLength = DeviceName.UniqueIdLength = 0;
+    DeviceName.DeviceNameOffset = ((ULONG_PTR)&DeviceName.DeviceName - 
(ULONG_PTR)&DeviceName);
+    DeviceName.DeviceNameLength = VolumeName.Length;
+    RtlCopyMemory(&DeviceName.DeviceName, VolumeName.Buffer, 
VolumeName.Length);
+
+    /* Now, query the MountMgr for the mount points */
+    Status = NtDeviceIoControlFile(MountMgrHandle,
+                                   NULL, NULL, NULL,
+                                   &IoStatusBlock,
+                                   IOCTL_MOUNTMGR_QUERY_POINTS,
+                                   &DeviceName, sizeof(DeviceName),
+                                   &MountPoints, sizeof(MountPoints));
+
+    /* Check for unsupported device */
+    if (Status == STATUS_NO_MEDIA_IN_DEVICE || Status == 
STATUS_INVALID_DEVICE_REQUEST)
+    {
+        skip("Device '%S': Doesn't support MountMgr queries, Status 0x%08lx\n",
+             NtVolumeName, Status);
+        return;
+    }
+
+    /* The only tolerated failure here is buffer too small, which is expected 
*/
+    ok(NT_SUCCESS(Status) || (Status == STATUS_BUFFER_OVERFLOW),
+       "Device '%S': IOCTL 0x%lx failed unexpectedly, Status 0x%08lx\n",
+       NtVolumeName, IOCTL_MOUNTMGR_QUERY_POINTS, Status);
+    if (!NT_SUCCESS(Status) && (Status != STATUS_BUFFER_OVERFLOW))
+    {
+        skip("Device '%S': Wrong Status\n", NtVolumeName);
+        return;
+    }
+
+    /* Compute the needed size to retrieve the mount points */
+    Length = MountPoints.Size;
+
+    /* Allocate the buffer and fill it with test pattern */
+    MountPointsPtr = RtlAllocateHeap(RtlGetProcessHeap(), 0, Length);
+    if (!MountPointsPtr)
+    {
+        skip("Device '%S': Failed to allocate buffer with size %lu)\n",
+             NtVolumeName, Length);
+        return;
+    }
+    RtlFillMemory(MountPointsPtr, Length, 0xCC);
+
+    /* Re-query the mount points with the proper size */
+    Status = NtDeviceIoControlFile(MountMgrHandle,
+                                   NULL, NULL, NULL,
+                                   &IoStatusBlock,
+                                   IOCTL_MOUNTMGR_QUERY_POINTS,
+                                   &DeviceName, sizeof(DeviceName),
+                                   MountPointsPtr, Length);
+    ok(NT_SUCCESS(Status),
+       "Device '%S': IOCTL 0x%lx failed unexpectedly, Status 0x%08lx\n",
+       NtVolumeName, IOCTL_MOUNTMGR_QUERY_POINTS, Status);
+
+    if (winetest_debug > 1)
+    {
+        trace("IOCTL_MOUNTMGR_QUERY_POINTS returned:\n"
+              "  Size: %lu\n"
+              "  NumberOfMountPoints: %lu\n",
+              MountPointsPtr->Size,
+              MountPointsPtr->NumberOfMountPoints);
+
+        trace("Buffer:\n");
+        DumpBuffer(MountPointsPtr, Length);
+        printf("\n");
+    }
+
+    /* Return the buffer */
+    *pMountPointsPtr = MountPointsPtr;
+}
+
+
+#define IS_DRIVE_LETTER_PFX(s) \
+  ((s)->Length >= 2*sizeof(WCHAR) && (s)->Buffer[0] >= 'A' && \
+   (s)->Buffer[0] <= 'Z' && (s)->Buffer[1] == ':')
+
+/* Differs from MOUNTMGR_IS_DRIVE_LETTER(): no '\DosDevices\' accounted for */
+#define IS_DRIVE_LETTER(s) \
+  (IS_DRIVE_LETTER_PFX(s) && (s)->Length == 2*sizeof(WCHAR))
+
+
+/**
+ * @brief   Tests the output of IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH.
+ **/
+static VOID
+Test_QueryDosVolumePath(
+    _In_ PCWSTR NtVolumeName,
+    _In_ PMOUNTMGR_VOLUME_PATHS VolumePath)
+{
+    UNICODE_STRING DosPath;
+
+    UNREFERENCED_PARAMETER(NtVolumeName);
+
+    /* The VolumePath should contain one NUL-terminated string (always there?),
+     * plus one final NUL-terminator */
+    ok(VolumePath->MultiSzLength >= 2 * sizeof(UNICODE_NULL),
+       "DOS volume path string too short (length: %lu)\n",
+       VolumePath->MultiSzLength / sizeof(WCHAR));
+    ok(VolumePath->MultiSz[VolumePath->MultiSzLength / sizeof(WCHAR) - 2] == 
UNICODE_NULL,
+       "Missing NUL-terminator (2)\n");
+    ok(VolumePath->MultiSz[VolumePath->MultiSzLength / sizeof(WCHAR) - 1] == 
UNICODE_NULL,
+       "Missing NUL-terminator (1)\n");
+
+    /* Build the result string */
+    // RtlInitUnicodeString(&DosPath, VolumePath->MultiSz);
+    DosPath.Length = (USHORT)VolumePath->MultiSzLength - 2 * 
sizeof(UNICODE_NULL);
+    DosPath.MaximumLength = DosPath.Length + sizeof(UNICODE_NULL);
+    DosPath.Buffer = VolumePath->MultiSz;
+
+    /* The returned DOS path is either a drive letter (*WITHOUT* any
+     * '\DosDevices\' prefix present) or a Win32 file-system reparse point
+     * path, or a volume GUID name in Win32 format, i.e. prefixed by '\\?\' */
+    ok(IS_DRIVE_LETTER_PFX(&DosPath) || MOUNTMGR_IS_DOS_VOLUME_NAME(&DosPath),
+       "Invalid DOS volume path returned '%s'\n", wine_dbgstr_us(&DosPath));
+}
+
+/**
+ * @brief   Tests the output of IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATHS.
+ **/
+static VOID
+Test_QueryDosVolumePaths(
+    _In_ PCWSTR NtVolumeName,
+    _In_ PMOUNTMGR_VOLUME_PATHS VolumePaths,
+    _In_opt_ PMOUNTMGR_VOLUME_PATHS VolumePath)
+{
+    UNICODE_STRING DosPath;
+    PCWSTR pMountPoint;
+
+    /* The VolumePaths should contain zero or more NUL-terminated strings,
+     * plus one final NUL-terminator */
+
+    ok(VolumePaths->MultiSzLength >= sizeof(UNICODE_NULL),
+       "DOS volume path string too short (length: %lu)\n",
+       VolumePaths->MultiSzLength / sizeof(WCHAR));
+
+    /* Check for correct double-NUL-termination, if there is at least one 
string */
+    if (VolumePaths->MultiSzLength >= 2 * sizeof(UNICODE_NULL),
+        VolumePaths->MultiSz[0] != UNICODE_NULL)
+    {
+        ok(VolumePaths->MultiSz[VolumePaths->MultiSzLength / sizeof(WCHAR) - 
2] == UNICODE_NULL,
+           "Missing NUL-terminator (2)\n");
+    }
+    /* Check for the final NUL-terminator */
+    ok(VolumePaths->MultiSz[VolumePaths->MultiSzLength / sizeof(WCHAR) - 1] == 
UNICODE_NULL,
+       "Missing NUL-terminator (1)\n");
+
+    if (winetest_debug > 1)
+    {
+        trace("\n%S =>\n", NtVolumeName);
+        for (pMountPoint = VolumePaths->MultiSz; *pMountPoint;
+             pMountPoint += wcslen(pMountPoint) + 1)
+        {
+            printf("  '%S'\n", pMountPoint);
+        }
+        printf("\n");
+    }
+
+    for (pMountPoint = VolumePaths->MultiSz; *pMountPoint;
+         pMountPoint += wcslen(pMountPoint) + 1)
+    {
+        /* The returned DOS path is either a drive letter (*WITHOUT* any
+         * '\DosDevices\' prefix present) or a Win32 file-system reparse point
+         * path, or a volume GUID name in Win32 format, i.e. prefixed by 
'\\?\' */
+        RtlInitUnicodeString(&DosPath, pMountPoint);
+        ok(IS_DRIVE_LETTER_PFX(&DosPath) || 
MOUNTMGR_IS_DOS_VOLUME_NAME(&DosPath),
+           "Invalid DOS volume path returned '%s'\n", 
wine_dbgstr_us(&DosPath));
+    }
+
+    /*
+     * If provided, verify that the single VolumePath is found at the
+     * first position in the volume paths list, *IF* this is a DOS path;
+     * otherwise if it's a Volume{GUID} path, this means there is no
+     * DOS path associated, and none is listed in the volume paths list.
+     */
+    if (VolumePath)
+    {
+        RtlInitUnicodeString(&DosPath, VolumePath->MultiSz);
+        if (IS_DRIVE_LETTER_PFX(&DosPath))
+        {
+            /*
+             * The single path is a DOS path (single drive letter or Win32
+             * file-system reparse point path). It has to be listed first
+             * in the volume paths list.
+             */
+            UNICODE_STRING FirstPath;
+            BOOLEAN AreEqual;
+
+            ok(VolumePaths->MultiSzLength >= 2 * sizeof(UNICODE_NULL),
+               "DOS VolumePaths list isn't long enough\n");
+            ok(*VolumePaths->MultiSz != UNICODE_NULL,
+               "Empty DOS VolumePaths list\n");
+
+            RtlInitUnicodeString(&FirstPath, VolumePaths->MultiSz);
+            AreEqual = RtlEqualUnicodeString(&DosPath, &FirstPath, FALSE);
+            ok(AreEqual, "DOS paths '%s' and '%s' are not the same!\n",
+               wine_dbgstr_us(&DosPath), wine_dbgstr_us(&FirstPath));
+        }
+        else if (MOUNTMGR_IS_DOS_VOLUME_NAME(&DosPath))
+        {
+            /*
+             * The single "DOS" path is actually a volume name. This means
+             * that it wasn't really mounted, and the volume paths list must
+             * be empty. It contains only the last NUL-terminator.
+             */
+            ok(VolumePaths->MultiSzLength == sizeof(UNICODE_NULL),
+               "DOS VolumePaths list isn't 1 WCHAR long\n");
+            ok(*VolumePaths->MultiSz == UNICODE_NULL,
+               "Non-empty DOS VolumePaths list\n");
+        }
+        else
+        {
+            /* The volume path is invalid (shouldn't happen) */
+            ok(FALSE, "Invalid DOS volume path returned '%s'\n", 
wine_dbgstr_us(&DosPath));
+        }
+    }
+}
+
+static BOOLEAN
+doesPathExistInMountPoints(
+    _In_ PMOUNTMGR_MOUNT_POINTS MountPoints,
+    _In_ PUNICODE_STRING DosPath)
+{
+    UNICODE_STRING DosDevicesPrefix = RTL_CONSTANT_STRING(L"\\DosDevices\\");
+    ULONG i;
+    BOOLEAN IsDosVolName;
+    BOOLEAN Found = FALSE;
+
+    IsDosVolName = MOUNTMGR_IS_DOS_VOLUME_NAME(DosPath);
+    /* Temporarily patch \\?\ to \??\ in DosPath for comparison */
+    if (IsDosVolName)
+        DosPath->Buffer[1] = L'?';
+
+    for (i = 0; i < MountPoints->NumberOfMountPoints; ++i)
+    {
+        UNICODE_STRING SymLink;
+
+        SymLink.Length = SymLink.MaximumLength = 
MountPoints->MountPoints[i].SymbolicLinkNameLength;
+        SymLink.Buffer = (PWCHAR)((ULONG_PTR)MountPoints + 
MountPoints->MountPoints[i].SymbolicLinkNameOffset);
+
+        if (IS_DRIVE_LETTER(DosPath))
+        {
+            if (RtlPrefixUnicodeString(&DosDevicesPrefix, &SymLink, FALSE))
+            {
+                /* Advance past the prefix */
+                SymLink.Length -= DosDevicesPrefix.Length;
+                SymLink.MaximumLength -= DosDevicesPrefix.Length;
+                SymLink.Buffer += DosDevicesPrefix.Length / sizeof(WCHAR);
+
+                Found = RtlEqualUnicodeString(DosPath, &SymLink, FALSE);
+            }
+        }
+        else if (/*MOUNTMGR_IS_DOS_VOLUME_NAME(DosPath) ||*/ // See above
+                 MOUNTMGR_IS_NT_VOLUME_NAME(DosPath))
+        {
+            Found = RtlEqualUnicodeString(DosPath, &SymLink, FALSE);
+        }
+        else
+        {
+            /* Just test for simple string comparison, the path should not be 
found */
+            Found = RtlEqualUnicodeString(DosPath, &SymLink, FALSE);
+        }
+
+        /* Stop searching if we've found something */
+        if (Found)
+            break;
+    }
+
+    /* Revert \??\ back to \\?\ */
+    if (IsDosVolName)
+        DosPath->Buffer[1] = L'\\';
+
+    return Found;
+}
+
+/**
+ * @brief   Tests the output of IOCTL_MOUNTMGR_QUERY_POINTS.
+ **/
+static VOID
+Test_QueryPoints(
+    _In_ PCWSTR NtVolumeName,
+    _In_ PMOUNTMGR_MOUNT_POINTS MountPoints,
+    _In_opt_ PMOUNTMGR_VOLUME_PATHS VolumePath,
+    _In_opt_ PMOUNTMGR_VOLUME_PATHS VolumePaths)
+{
+    UNICODE_STRING DosPath;
+    PCWSTR pMountPoint;
+    BOOLEAN ExpectedFound, Found;
+
+    if (winetest_debug > 1)
+    {
+        ULONG i;
+        trace("\n%S =>\n", NtVolumeName);
+        for (i = 0; i < MountPoints->NumberOfMountPoints; ++i)
+        {
+            UNICODE_STRING DevName, SymLink;
+
+            DevName.Length = DevName.MaximumLength = 
MountPoints->MountPoints[i].DeviceNameLength;
+            DevName.Buffer = (PWCHAR)((ULONG_PTR)MountPoints + 
MountPoints->MountPoints[i].DeviceNameOffset);
+
+            SymLink.Length = SymLink.MaximumLength = 
MountPoints->MountPoints[i].SymbolicLinkNameLength;
+            SymLink.Buffer = (PWCHAR)((ULONG_PTR)MountPoints + 
MountPoints->MountPoints[i].SymbolicLinkNameOffset);
+
+            printf("  '%s' -- '%s'\n", wine_dbgstr_us(&DevName), 
wine_dbgstr_us(&SymLink));
+        }
+        printf("\n");
+    }
+
+    /*
+     * The Win32 file-system reparse point paths are NOT listed amongst
+     * the mount points. Only the drive letter and the volume GUID name
+     * are, but in an NT format (using '\DosDevices\' or '\??\' prefixes).
+     */
+
+    if (VolumePath)
+    {
+        /* VolumePath can either be a drive letter (usual case), a Win32
+         * reparse point path (if the volume is mounted only with these),
+         * or a volume GUID name (if the volume is NOT mounted). */
+        RtlInitUnicodeString(&DosPath, VolumePath->MultiSz);
+        ExpectedFound = (IS_DRIVE_LETTER(&DosPath) || 
MOUNTMGR_IS_DOS_VOLUME_NAME(&DosPath));
+        Found = doesPathExistInMountPoints(MountPoints, &DosPath);
+        ok(Found == ExpectedFound,
+           "DOS path '%s' %sfound in the mount points list, expected %sto be 
found\n",
+           wine_dbgstr_us(&DosPath), Found ? "" : "NOT ", ExpectedFound ? "" : 
"NOT ");
+    }
+
+    if (VolumePaths)
+    {
+        /* VolumePaths only contains a drive letter (usual case) or a Win32
+         * reparse point path (if the volume is mounted only with these).
+         * If the volume is NOT mounted, VolumePaths does not list the
+         * volume GUID name, contrary to VolumePath. */
+        for (pMountPoint = VolumePaths->MultiSz; *pMountPoint;
+             pMountPoint += wcslen(pMountPoint) + 1)
+        {
+            /* Only the drive letter (but NOT the volume GUID name!) can be 
found in the list */
+            RtlInitUnicodeString(&DosPath, pMountPoint);
+            ExpectedFound = IS_DRIVE_LETTER(&DosPath);
+            Found = doesPathExistInMountPoints(MountPoints, &DosPath);
+            ok(Found == ExpectedFound,
+               "DOS path '%s' %sfound in the mount points list, expected %sto 
be found\n",
+               wine_dbgstr_us(&DosPath), Found ? "" : "NOT ", ExpectedFound ? 
"" : "NOT ");
+        }
+    }
+}
+
+/**
+ * @brief
+ * Tests the consistency of IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH,
+ * IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATHS and IOCTL_MOUNTMGR_QUERY_POINTS.
+ **/
+static VOID
+Test_QueryDosVolumePathAndPaths(
+    _In_ HANDLE MountMgrHandle,
+    _In_ PCWSTR NtVolumeName)
+{
+    PMOUNTMGR_VOLUME_PATHS VolumePath = NULL;
+    PMOUNTMGR_VOLUME_PATHS VolumePaths = NULL;
+    PMOUNTMGR_MOUNT_POINTS MountPoints = NULL;
+
+    if (winetest_debug > 1)
+        trace("%S\n", NtVolumeName);
+
+    Call_QueryDosVolume_Path_Paths(MountMgrHandle,
+                                   NtVolumeName,
+                                   IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH,
+                                   &VolumePath);
+    if (VolumePath)
+        Test_QueryDosVolumePath(NtVolumeName, VolumePath);
+    else
+        skip("Device '%S': IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH failed\n", 
NtVolumeName);
+
+    Call_QueryDosVolume_Path_Paths(MountMgrHandle,
+                                   NtVolumeName,
+                                   IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATHS,
+                                   &VolumePaths);
+    if (VolumePaths)
+        Test_QueryDosVolumePaths(NtVolumeName, VolumePaths, VolumePath);
+    else
+        skip("Device '%S': IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATHS failed\n", 
NtVolumeName);
+
+    Call_QueryPoints(MountMgrHandle, NtVolumeName, &MountPoints);
+    if (MountPoints)
+        Test_QueryPoints(NtVolumeName, MountPoints, VolumePath, VolumePaths);
+    else
+        skip("Device '%S': IOCTL_MOUNTMGR_QUERY_POINTS failed\n", 
NtVolumeName);
+
+    if (MountPoints)
+        RtlFreeHeap(RtlGetProcessHeap(), 0, MountPoints);
+    if (VolumePaths)
+        RtlFreeHeap(RtlGetProcessHeap(), 0, VolumePaths);
+    if (VolumePath)
+        RtlFreeHeap(RtlGetProcessHeap(), 0, VolumePath);
+}
+
+
+START_TEST(QueryDosVolumePaths)
+{
+    HANDLE MountMgrHandle;
+    HANDLE hFindVolume;
+    WCHAR szVolumeName[MAX_PATH];
+
+    MountMgrHandle = GetMountMgrHandle();
+    if (!MountMgrHandle)
+    {
+        win_skip("MountMgr unavailable: %lu\n", GetLastError());
+        return;
+    }
+
+    hFindVolume = FindFirstVolumeW(szVolumeName, _countof(szVolumeName));
+    if (hFindVolume == INVALID_HANDLE_VALUE)
+        goto otherTests;
+    do
+    {
+        UNICODE_STRING VolumeName;
+        USHORT Length;
+
+        /*
+         * The Win32 FindFirst/NextVolumeW() functions convert the '\??\'
+         * prefix in '\??\Volume{...}' to '\\?\' and append a trailing
+         * backslash. Test this behaviour.
+         *
+         * NOTE: these functions actively filter out anything that is NOT
+         * '\??\Volume{...}' returned from IOCTL_MOUNTMGR_QUERY_POINTS.
+         * Thus, it also excludes mount-points specified as drive letters,
+         * like '\DosDevices\C:' .
+         */
+
+        RtlInitUnicodeString(&VolumeName, szVolumeName);
+        Length = VolumeName.Length / sizeof(WCHAR);
+        ok(Length >= 1 && VolumeName.Buffer[Length - 1] == L'\\',
+           "No trailing backslash found\n");
+
+        /* Remove the trailing backslash */
+        if (Length >= 1)
+        {
+            VolumeName.Length -= sizeof(WCHAR);
+            if (szVolumeName[Length - 1] == L'\\')
+                szVolumeName[Length - 1] = UNICODE_NULL;
+        }
+
+        ok(MOUNTMGR_IS_DOS_VOLUME_NAME(&VolumeName),
+           "Invalid DOS volume path returned '%s'\n", 
wine_dbgstr_us(&VolumeName));
+
+        /* Patch '\\?\' back to '\??\' to convert to an NT path */
+        if (szVolumeName[0] == L'\\' && szVolumeName[1] == L'\\' &&
+            szVolumeName[2] == L'?'  && szVolumeName[3] == L'\\')
+        {
+            szVolumeName[1] = L'?';
+        }
+
+        Test_QueryDosVolumePathAndPaths(MountMgrHandle, szVolumeName);
+    } while (FindNextVolumeW(hFindVolume, szVolumeName, 
_countof(szVolumeName)));
+    FindVolumeClose(hFindVolume);
+
+otherTests:
+    /* Test the drive containing SystemRoot */
+    wcscpy(szVolumeName, L"\\DosDevices\\?:");
+    szVolumeName[sizeof("\\DosDevices\\")-1] = SharedUserData->NtSystemRoot[0];
+    Test_QueryDosVolumePathAndPaths(MountMgrHandle, szVolumeName);
+
+    /* We are done */
+    CloseHandle(MountMgrHandle);
+}
diff --git a/modules/rostests/apitests/mountmgr/precomp.h 
b/modules/rostests/apitests/mountmgr/precomp.h
index 462f62e1a38..8031f379b24 100644
--- a/modules/rostests/apitests/mountmgr/precomp.h
+++ b/modules/rostests/apitests/mountmgr/precomp.h
@@ -25,4 +25,9 @@ LPCSTR wine_dbgstr_us(const UNICODE_STRING *us);
 HANDLE
 GetMountMgrHandle(VOID);
 
+VOID
+DumpBuffer(
+    _In_ PVOID Buffer,
+    _In_ ULONG Length);
+
 /* EOF */
diff --git a/modules/rostests/apitests/mountmgr/testlist.c 
b/modules/rostests/apitests/mountmgr/testlist.c
index ab7c0921a26..9c96445bbcd 100644
--- a/modules/rostests/apitests/mountmgr/testlist.c
+++ b/modules/rostests/apitests/mountmgr/testlist.c
@@ -1,10 +1,12 @@
 #define STANDALONE
 #include <apitest.h>
 
+extern void func_QueryDosVolumePaths(void);
 extern void func_QueryPoints(void);
 
 const struct test winetest_testlist[] =
 {
+    { "QueryDosVolumePaths", func_QueryDosVolumePaths },
     { "QueryPoints", func_QueryPoints },
     { 0, 0 }
 };
diff --git a/modules/rostests/apitests/mountmgr/utils.c 
b/modules/rostests/apitests/mountmgr/utils.c
index 50755581f04..7fa0794d2aa 100644
--- a/modules/rostests/apitests/mountmgr/utils.c
+++ b/modules/rostests/apitests/mountmgr/utils.c
@@ -47,3 +47,56 @@ GetMountMgrHandle(VOID)
 
     return MountMgrHandle;
 }
+
+VOID
+DumpBuffer(
+    _In_ PVOID Buffer,
+    _In_ ULONG Length)
+{
+#define LINE_SIZE   (75 + 2)
+    ULONG i;
+    PBYTE Ptr1, Ptr2;
+    CHAR  LineBuffer[LINE_SIZE];
+    PCHAR Line;
+    ULONG LineSize;
+
+    Ptr1 = Ptr2 = Buffer;
+    while ((ULONG_PTR)Buffer + Length - (ULONG_PTR)Ptr1 > 0)
+    {
+        Ptr1 = Ptr2;
+        Line = LineBuffer;
+
+        /* Print the address */
+        Line += _snprintf(Line, LINE_SIZE + LineBuffer - Line, "%08Ix ", 
(ULONG_PTR)Ptr1);
+
+        /* Print up to 16 bytes... */
+
+        /* ... in hexadecimal form first... */
+        i = 0;
+        while (i++ <= 0x0F && ((ULONG_PTR)Buffer + Length - (ULONG_PTR)Ptr1 > 
0))
+        {
+            Line += _snprintf(Line, LINE_SIZE + LineBuffer - Line, " %02x", 
*Ptr1);
+            ++Ptr1;
+        }
+
+        /* ... align with spaces if needed... */
+        RtlFillMemory(Line, (0x0F + 2 - i) * 3 + 2, ' ');
+        Line += (0x0F + 2 - i) * 3 + 2;
+
+        /* ... then in character form. */
+        i = 0;
+        while (i++ <= 0x0F && ((ULONG_PTR)Buffer + Length - (ULONG_PTR)Ptr2 > 
0))
+        {
+            *Line++ = ((*Ptr2 >= 0x20 && *Ptr2 <= 0x7E) || (*Ptr2 >= 0x80 && 
*Ptr2 < 0xFF) ? *Ptr2 : '.');
+            ++Ptr2;
+        }
+
+        /* Newline */
+        *Line++ = '\r';
+        *Line++ = '\n';
+
+        /* Finally display the line */
+        LineSize = Line - LineBuffer;
+        printf("%.*s", (int)LineSize, LineBuffer);
+    }
+}

Reply via email to