From cc1deb29024ae58e52832f8bb07ac23fdbafe281 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herm=C3=A8s=20B=C3=A9lusca-Ma=C3=AFto?= Date: Mon, 6 Jan 2025 21:54:20 +0100 Subject: [PATCH] [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 --- .../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(+) create mode 100644 modules/rostests/apitests/mountmgr/QueryDosVolumePaths.c 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 + */ + +#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 +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); + } +}