1 /*
2  * PROJECT:     ReactOS API Tests
3  * LICENSE:     GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4  * PURPOSE:     Test for IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH(S)
5  * COPYRIGHT:   Copyright 2025 Hermès Bélusca-Maïto <hermes.belusca-maito@reactos.org>
6  */
7 
8 #include "precomp.h"
9 
10 
11 /**
12  * @brief
13  * Invokes either IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH or
14  * IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATHS for testing, given
15  * the volume device name, and returns an allocated volume
16  * paths buffer. This buffer must be freed by the caller via
17  * RtlFreeHeap(RtlGetProcessHeap(), ...) .
18  *
19  * These IOCTLs return both the drive letter (if any) and the
20  * volume GUID symlink path, as well as any other file-system
21  * mount reparse points linking to the volume.
22  **/
23 static VOID
Call_QueryDosVolume_Path_Paths(_In_ HANDLE MountMgrHandle,_In_ PCWSTR NtVolumeName,_In_ ULONG IoctlPathOrPaths,_Out_ PMOUNTMGR_VOLUME_PATHS * pVolumePathPtr)24 Call_QueryDosVolume_Path_Paths(
25     _In_ HANDLE MountMgrHandle,
26     _In_ PCWSTR NtVolumeName,
27     _In_ ULONG IoctlPathOrPaths,
28     _Out_ PMOUNTMGR_VOLUME_PATHS* pVolumePathPtr)
29 {
30     NTSTATUS Status;
31     ULONG Length;
32     IO_STATUS_BLOCK IoStatusBlock;
33     UNICODE_STRING VolumeName;
34     MOUNTMGR_VOLUME_PATHS VolumePath;
35     PMOUNTMGR_VOLUME_PATHS VolumePathPtr;
36     ULONG DeviceNameLength;
37     /*
38      * This variable is used to query the device name.
39      * It's based on MOUNTMGR_TARGET_NAME (mountmgr.h).
40      * Doing it this way prevents memory allocation.
41      * The device name won't be longer.
42      */
43     struct
44     {
45         USHORT NameLength;
46         WCHAR DeviceName[256];
47     } DeviceName;
48 
49 
50     *pVolumePathPtr = NULL;
51 
52     /* First, build the corresponding device name */
53     RtlInitUnicodeString(&VolumeName, NtVolumeName);
54     DeviceName.NameLength = VolumeName.Length;
55     RtlCopyMemory(&DeviceName.DeviceName, VolumeName.Buffer, VolumeName.Length);
56     DeviceNameLength = FIELD_OFFSET(MOUNTMGR_TARGET_NAME, DeviceName) + DeviceName.NameLength;
57 
58     /* Now, query the MountMgr for the DOS path(s) */
59     Status = NtDeviceIoControlFile(MountMgrHandle,
60                                    NULL, NULL, NULL,
61                                    &IoStatusBlock,
62                                    IoctlPathOrPaths,
63                                    &DeviceName, DeviceNameLength,
64                                    &VolumePath, sizeof(VolumePath));
65 
66     /* Check for unsupported device */
67     if (Status == STATUS_NO_MEDIA_IN_DEVICE || Status == STATUS_INVALID_DEVICE_REQUEST)
68     {
69         skip("Device '%S': Doesn't support MountMgr queries, Status 0x%08lx\n",
70              NtVolumeName, Status);
71         return;
72     }
73 
74     /* The only tolerated failure here is buffer too small, which is expected */
75     ok(NT_SUCCESS(Status) || (Status == STATUS_BUFFER_OVERFLOW),
76        "Device '%S': IOCTL 0x%lx failed unexpectedly, Status 0x%08lx\n",
77        NtVolumeName, IoctlPathOrPaths, Status);
78     if (!NT_SUCCESS(Status) && (Status != STATUS_BUFFER_OVERFLOW))
79     {
80         skip("Device '%S': Wrong Status\n", NtVolumeName);
81         return;
82     }
83 
84     /* Compute the needed size to store the DOS path(s).
85      * Even if MOUNTMGR_VOLUME_PATHS allows bigger name lengths
86      * than MAXUSHORT, we can't use them, because we have to return
87      * this in an UNICODE_STRING that stores length in a USHORT. */
88     Length = sizeof(VolumePath) + VolumePath.MultiSzLength;
89     ok(Length <= MAXUSHORT,
90        "Device '%S': DOS volume path too large: %lu\n",
91        NtVolumeName, Length);
92     if (Length > MAXUSHORT)
93     {
94         skip("Device '%S': Wrong Length\n", NtVolumeName);
95         return;
96     }
97 
98     /* Allocate the buffer and fill it with test pattern */
99     VolumePathPtr = RtlAllocateHeap(RtlGetProcessHeap(), 0, Length);
100     if (!VolumePathPtr)
101     {
102         skip("Device '%S': Failed to allocate buffer with size %lu)\n",
103              NtVolumeName, Length);
104         return;
105     }
106     RtlFillMemory(VolumePathPtr, Length, 0xCC);
107 
108     /* Re-query the DOS path(s) with the proper size */
109     Status = NtDeviceIoControlFile(MountMgrHandle,
110                                    NULL, NULL, NULL,
111                                    &IoStatusBlock,
112                                    IoctlPathOrPaths,
113                                    &DeviceName, DeviceNameLength,
114                                    VolumePathPtr, Length);
115     ok(NT_SUCCESS(Status),
116        "Device '%S': IOCTL 0x%lx failed unexpectedly, Status 0x%08lx\n",
117        NtVolumeName, IoctlPathOrPaths, Status);
118 
119     if (winetest_debug > 1)
120     {
121         trace("Buffer:\n");
122         DumpBuffer(VolumePathPtr, Length);
123         printf("\n");
124     }
125 
126     /* Return the buffer */
127     *pVolumePathPtr = VolumePathPtr;
128 }
129 
130 /**
131  * @brief
132  * Invokes IOCTL_MOUNTMGR_QUERY_POINTS for testing, given
133  * the volume device name, and returns an allocated mount
134  * points buffer. This buffer must be freed by the caller
135  * via RtlFreeHeap(RtlGetProcessHeap(), ...) .
136  *
137  * This IOCTL only returns both the drive letter (if any)
138  * and the volume GUID symlink path, but does NOT return
139  * file-system mount reparse points.
140  **/
141 static VOID
Call_QueryPoints(_In_ HANDLE MountMgrHandle,_In_ PCWSTR NtVolumeName,_Out_ PMOUNTMGR_MOUNT_POINTS * pMountPointsPtr)142 Call_QueryPoints(
143     _In_ HANDLE MountMgrHandle,
144     _In_ PCWSTR NtVolumeName,
145     _Out_ PMOUNTMGR_MOUNT_POINTS* pMountPointsPtr)
146 {
147     NTSTATUS Status;
148     ULONG Length;
149     IO_STATUS_BLOCK IoStatusBlock;
150     UNICODE_STRING VolumeName;
151     MOUNTMGR_MOUNT_POINTS MountPoints;
152     PMOUNTMGR_MOUNT_POINTS MountPointsPtr;
153     /*
154      * This variable is used to query the device name.
155      * It's based on MOUNTMGR_MOUNT_POINT (mountmgr.h).
156      * Doing it this way prevents memory allocation.
157      * The device name won't be longer.
158      */
159     struct
160     {
161         MOUNTMGR_MOUNT_POINT;
162         WCHAR DeviceName[256];
163     } DeviceName;
164 
165 
166     *pMountPointsPtr = NULL;
167 
168     /* First, build the corresponding device name */
169     RtlInitUnicodeString(&VolumeName, NtVolumeName);
170     DeviceName.SymbolicLinkNameOffset = DeviceName.UniqueIdOffset = 0;
171     DeviceName.SymbolicLinkNameLength = DeviceName.UniqueIdLength = 0;
172     DeviceName.DeviceNameOffset = ((ULONG_PTR)&DeviceName.DeviceName - (ULONG_PTR)&DeviceName);
173     DeviceName.DeviceNameLength = VolumeName.Length;
174     RtlCopyMemory(&DeviceName.DeviceName, VolumeName.Buffer, VolumeName.Length);
175 
176     /* Now, query the MountMgr for the mount points */
177     Status = NtDeviceIoControlFile(MountMgrHandle,
178                                    NULL, NULL, NULL,
179                                    &IoStatusBlock,
180                                    IOCTL_MOUNTMGR_QUERY_POINTS,
181                                    &DeviceName, sizeof(DeviceName),
182                                    &MountPoints, sizeof(MountPoints));
183 
184     /* Check for unsupported device */
185     if (Status == STATUS_NO_MEDIA_IN_DEVICE || Status == STATUS_INVALID_DEVICE_REQUEST)
186     {
187         skip("Device '%S': Doesn't support MountMgr queries, Status 0x%08lx\n",
188              NtVolumeName, Status);
189         return;
190     }
191 
192     /* The only tolerated failure here is buffer too small, which is expected */
193     ok(NT_SUCCESS(Status) || (Status == STATUS_BUFFER_OVERFLOW),
194        "Device '%S': IOCTL 0x%lx failed unexpectedly, Status 0x%08lx\n",
195        NtVolumeName, IOCTL_MOUNTMGR_QUERY_POINTS, Status);
196     if (!NT_SUCCESS(Status) && (Status != STATUS_BUFFER_OVERFLOW))
197     {
198         skip("Device '%S': Wrong Status\n", NtVolumeName);
199         return;
200     }
201 
202     /* Compute the needed size to retrieve the mount points */
203     Length = MountPoints.Size;
204 
205     /* Allocate the buffer and fill it with test pattern */
206     MountPointsPtr = RtlAllocateHeap(RtlGetProcessHeap(), 0, Length);
207     if (!MountPointsPtr)
208     {
209         skip("Device '%S': Failed to allocate buffer with size %lu)\n",
210              NtVolumeName, Length);
211         return;
212     }
213     RtlFillMemory(MountPointsPtr, Length, 0xCC);
214 
215     /* Re-query the mount points with the proper size */
216     Status = NtDeviceIoControlFile(MountMgrHandle,
217                                    NULL, NULL, NULL,
218                                    &IoStatusBlock,
219                                    IOCTL_MOUNTMGR_QUERY_POINTS,
220                                    &DeviceName, sizeof(DeviceName),
221                                    MountPointsPtr, Length);
222     ok(NT_SUCCESS(Status),
223        "Device '%S': IOCTL 0x%lx failed unexpectedly, Status 0x%08lx\n",
224        NtVolumeName, IOCTL_MOUNTMGR_QUERY_POINTS, Status);
225 
226     if (winetest_debug > 1)
227     {
228         trace("IOCTL_MOUNTMGR_QUERY_POINTS returned:\n"
229               "  Size: %lu\n"
230               "  NumberOfMountPoints: %lu\n",
231               MountPointsPtr->Size,
232               MountPointsPtr->NumberOfMountPoints);
233 
234         trace("Buffer:\n");
235         DumpBuffer(MountPointsPtr, Length);
236         printf("\n");
237     }
238 
239     /* Return the buffer */
240     *pMountPointsPtr = MountPointsPtr;
241 }
242 
243 
244 #define IS_DRIVE_LETTER_PFX(s) \
245   ((s)->Length >= 2*sizeof(WCHAR) && (s)->Buffer[0] >= 'A' && \
246    (s)->Buffer[0] <= 'Z' && (s)->Buffer[1] == ':')
247 
248 /* Differs from MOUNTMGR_IS_DRIVE_LETTER(): no '\DosDevices\' accounted for */
249 #define IS_DRIVE_LETTER(s) \
250   (IS_DRIVE_LETTER_PFX(s) && (s)->Length == 2*sizeof(WCHAR))
251 
252 
253 /**
254  * @brief   Tests the output of IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH.
255  **/
256 static VOID
Test_QueryDosVolumePath(_In_ PCWSTR NtVolumeName,_In_ PMOUNTMGR_VOLUME_PATHS VolumePath)257 Test_QueryDosVolumePath(
258     _In_ PCWSTR NtVolumeName,
259     _In_ PMOUNTMGR_VOLUME_PATHS VolumePath)
260 {
261     UNICODE_STRING DosPath;
262 
263     UNREFERENCED_PARAMETER(NtVolumeName);
264 
265     /* The VolumePath should contain one NUL-terminated string (always there?),
266      * plus one final NUL-terminator */
267     ok(VolumePath->MultiSzLength >= 2 * sizeof(UNICODE_NULL),
268        "DOS volume path string too short (length: %lu)\n",
269        VolumePath->MultiSzLength / sizeof(WCHAR));
270     ok(VolumePath->MultiSz[VolumePath->MultiSzLength / sizeof(WCHAR) - 2] == UNICODE_NULL,
271        "Missing NUL-terminator (2)\n");
272     ok(VolumePath->MultiSz[VolumePath->MultiSzLength / sizeof(WCHAR) - 1] == UNICODE_NULL,
273        "Missing NUL-terminator (1)\n");
274 
275     /* Build the result string */
276     // RtlInitUnicodeString(&DosPath, VolumePath->MultiSz);
277     DosPath.Length = (USHORT)VolumePath->MultiSzLength - 2 * sizeof(UNICODE_NULL);
278     DosPath.MaximumLength = DosPath.Length + sizeof(UNICODE_NULL);
279     DosPath.Buffer = VolumePath->MultiSz;
280 
281     /* The returned DOS path is either a drive letter (*WITHOUT* any
282      * '\DosDevices\' prefix present) or a Win32 file-system reparse point
283      * path, or a volume GUID name in Win32 format, i.e. prefixed by '\\?\' */
284     ok(IS_DRIVE_LETTER_PFX(&DosPath) || MOUNTMGR_IS_DOS_VOLUME_NAME(&DosPath),
285        "Invalid DOS volume path returned '%s'\n", wine_dbgstr_us(&DosPath));
286 }
287 
288 /**
289  * @brief   Tests the output of IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATHS.
290  **/
291 static VOID
Test_QueryDosVolumePaths(_In_ PCWSTR NtVolumeName,_In_ PMOUNTMGR_VOLUME_PATHS VolumePaths,_In_opt_ PMOUNTMGR_VOLUME_PATHS VolumePath)292 Test_QueryDosVolumePaths(
293     _In_ PCWSTR NtVolumeName,
294     _In_ PMOUNTMGR_VOLUME_PATHS VolumePaths,
295     _In_opt_ PMOUNTMGR_VOLUME_PATHS VolumePath)
296 {
297     UNICODE_STRING DosPath;
298     PCWSTR pMountPoint;
299 
300     /* The VolumePaths should contain zero or more NUL-terminated strings,
301      * plus one final NUL-terminator */
302 
303     ok(VolumePaths->MultiSzLength >= sizeof(UNICODE_NULL),
304        "DOS volume path string too short (length: %lu)\n",
305        VolumePaths->MultiSzLength / sizeof(WCHAR));
306 
307     /* Check for correct double-NUL-termination, if there is at least one string */
308     if (VolumePaths->MultiSzLength >= 2 * sizeof(UNICODE_NULL),
309         VolumePaths->MultiSz[0] != UNICODE_NULL)
310     {
311         ok(VolumePaths->MultiSz[VolumePaths->MultiSzLength / sizeof(WCHAR) - 2] == UNICODE_NULL,
312            "Missing NUL-terminator (2)\n");
313     }
314     /* Check for the final NUL-terminator */
315     ok(VolumePaths->MultiSz[VolumePaths->MultiSzLength / sizeof(WCHAR) - 1] == UNICODE_NULL,
316        "Missing NUL-terminator (1)\n");
317 
318     if (winetest_debug > 1)
319     {
320         trace("\n%S =>\n", NtVolumeName);
321         for (pMountPoint = VolumePaths->MultiSz; *pMountPoint;
322              pMountPoint += wcslen(pMountPoint) + 1)
323         {
324             printf("  '%S'\n", pMountPoint);
325         }
326         printf("\n");
327     }
328 
329     for (pMountPoint = VolumePaths->MultiSz; *pMountPoint;
330          pMountPoint += wcslen(pMountPoint) + 1)
331     {
332         /* The returned DOS path is either a drive letter (*WITHOUT* any
333          * '\DosDevices\' prefix present) or a Win32 file-system reparse point
334          * path, or a volume GUID name in Win32 format, i.e. prefixed by '\\?\' */
335         RtlInitUnicodeString(&DosPath, pMountPoint);
336         ok(IS_DRIVE_LETTER_PFX(&DosPath) || MOUNTMGR_IS_DOS_VOLUME_NAME(&DosPath),
337            "Invalid DOS volume path returned '%s'\n", wine_dbgstr_us(&DosPath));
338     }
339 
340     /*
341      * If provided, verify that the single VolumePath is found at the
342      * first position in the volume paths list, *IF* this is a DOS path;
343      * otherwise if it's a Volume{GUID} path, this means there is no
344      * DOS path associated, and none is listed in the volume paths list.
345      */
346     if (VolumePath)
347     {
348         RtlInitUnicodeString(&DosPath, VolumePath->MultiSz);
349         if (IS_DRIVE_LETTER_PFX(&DosPath))
350         {
351             /*
352              * The single path is a DOS path (single drive letter or Win32
353              * file-system reparse point path). It has to be listed first
354              * in the volume paths list.
355              */
356             UNICODE_STRING FirstPath;
357             BOOLEAN AreEqual;
358 
359             ok(VolumePaths->MultiSzLength >= 2 * sizeof(UNICODE_NULL),
360                "DOS VolumePaths list isn't long enough\n");
361             ok(*VolumePaths->MultiSz != UNICODE_NULL,
362                "Empty DOS VolumePaths list\n");
363 
364             RtlInitUnicodeString(&FirstPath, VolumePaths->MultiSz);
365             AreEqual = RtlEqualUnicodeString(&DosPath, &FirstPath, FALSE);
366             ok(AreEqual, "DOS paths '%s' and '%s' are not the same!\n",
367                wine_dbgstr_us(&DosPath), wine_dbgstr_us(&FirstPath));
368         }
369         else if (MOUNTMGR_IS_DOS_VOLUME_NAME(&DosPath))
370         {
371             /*
372              * The single "DOS" path is actually a volume name. This means
373              * that it wasn't really mounted, and the volume paths list must
374              * be empty. It contains only the last NUL-terminator.
375              */
376             ok(VolumePaths->MultiSzLength == sizeof(UNICODE_NULL),
377                "DOS VolumePaths list isn't 1 WCHAR long\n");
378             ok(*VolumePaths->MultiSz == UNICODE_NULL,
379                "Non-empty DOS VolumePaths list\n");
380         }
381         else
382         {
383             /* The volume path is invalid (shouldn't happen) */
384             ok(FALSE, "Invalid DOS volume path returned '%s'\n", wine_dbgstr_us(&DosPath));
385         }
386     }
387 }
388 
389 static BOOLEAN
doesPathExistInMountPoints(_In_ PMOUNTMGR_MOUNT_POINTS MountPoints,_In_ PUNICODE_STRING DosPath)390 doesPathExistInMountPoints(
391     _In_ PMOUNTMGR_MOUNT_POINTS MountPoints,
392     _In_ PUNICODE_STRING DosPath)
393 {
394     UNICODE_STRING DosDevicesPrefix = RTL_CONSTANT_STRING(L"\\DosDevices\\");
395     ULONG i;
396     BOOLEAN IsDosVolName;
397     BOOLEAN Found = FALSE;
398 
399     IsDosVolName = MOUNTMGR_IS_DOS_VOLUME_NAME(DosPath);
400     /* Temporarily patch \\?\ to \??\ in DosPath for comparison */
401     if (IsDosVolName)
402         DosPath->Buffer[1] = L'?';
403 
404     for (i = 0; i < MountPoints->NumberOfMountPoints; ++i)
405     {
406         UNICODE_STRING SymLink;
407 
408         SymLink.Length = SymLink.MaximumLength = MountPoints->MountPoints[i].SymbolicLinkNameLength;
409         SymLink.Buffer = (PWCHAR)((ULONG_PTR)MountPoints + MountPoints->MountPoints[i].SymbolicLinkNameOffset);
410 
411         if (IS_DRIVE_LETTER(DosPath))
412         {
413             if (RtlPrefixUnicodeString(&DosDevicesPrefix, &SymLink, FALSE))
414             {
415                 /* Advance past the prefix */
416                 SymLink.Length -= DosDevicesPrefix.Length;
417                 SymLink.MaximumLength -= DosDevicesPrefix.Length;
418                 SymLink.Buffer += DosDevicesPrefix.Length / sizeof(WCHAR);
419 
420                 Found = RtlEqualUnicodeString(DosPath, &SymLink, FALSE);
421             }
422         }
423         else if (/*MOUNTMGR_IS_DOS_VOLUME_NAME(DosPath) ||*/ // See above
424                  MOUNTMGR_IS_NT_VOLUME_NAME(DosPath))
425         {
426             Found = RtlEqualUnicodeString(DosPath, &SymLink, FALSE);
427         }
428         else
429         {
430             /* Just test for simple string comparison, the path should not be found */
431             Found = RtlEqualUnicodeString(DosPath, &SymLink, FALSE);
432         }
433 
434         /* Stop searching if we've found something */
435         if (Found)
436             break;
437     }
438 
439     /* Revert \??\ back to \\?\ */
440     if (IsDosVolName)
441         DosPath->Buffer[1] = L'\\';
442 
443     return Found;
444 }
445 
446 /**
447  * @brief   Tests the output of IOCTL_MOUNTMGR_QUERY_POINTS.
448  **/
449 static VOID
Test_QueryPoints(_In_ PCWSTR NtVolumeName,_In_ PMOUNTMGR_MOUNT_POINTS MountPoints,_In_opt_ PMOUNTMGR_VOLUME_PATHS VolumePath,_In_opt_ PMOUNTMGR_VOLUME_PATHS VolumePaths)450 Test_QueryPoints(
451     _In_ PCWSTR NtVolumeName,
452     _In_ PMOUNTMGR_MOUNT_POINTS MountPoints,
453     _In_opt_ PMOUNTMGR_VOLUME_PATHS VolumePath,
454     _In_opt_ PMOUNTMGR_VOLUME_PATHS VolumePaths)
455 {
456     UNICODE_STRING DosPath;
457     PCWSTR pMountPoint;
458     BOOLEAN ExpectedFound, Found;
459 
460     if (winetest_debug > 1)
461     {
462         ULONG i;
463         trace("\n%S =>\n", NtVolumeName);
464         for (i = 0; i < MountPoints->NumberOfMountPoints; ++i)
465         {
466             UNICODE_STRING DevName, SymLink;
467 
468             DevName.Length = DevName.MaximumLength = MountPoints->MountPoints[i].DeviceNameLength;
469             DevName.Buffer = (PWCHAR)((ULONG_PTR)MountPoints + MountPoints->MountPoints[i].DeviceNameOffset);
470 
471             SymLink.Length = SymLink.MaximumLength = MountPoints->MountPoints[i].SymbolicLinkNameLength;
472             SymLink.Buffer = (PWCHAR)((ULONG_PTR)MountPoints + MountPoints->MountPoints[i].SymbolicLinkNameOffset);
473 
474             printf("  '%s' -- '%s'\n", wine_dbgstr_us(&DevName), wine_dbgstr_us(&SymLink));
475         }
476         printf("\n");
477     }
478 
479     /*
480      * The Win32 file-system reparse point paths are NOT listed amongst
481      * the mount points. Only the drive letter and the volume GUID name
482      * are, but in an NT format (using '\DosDevices\' or '\??\' prefixes).
483      */
484 
485     if (VolumePath)
486     {
487         /* VolumePath can either be a drive letter (usual case), a Win32
488          * reparse point path (if the volume is mounted only with these),
489          * or a volume GUID name (if the volume is NOT mounted). */
490         RtlInitUnicodeString(&DosPath, VolumePath->MultiSz);
491         ExpectedFound = (IS_DRIVE_LETTER(&DosPath) || MOUNTMGR_IS_DOS_VOLUME_NAME(&DosPath));
492         Found = doesPathExistInMountPoints(MountPoints, &DosPath);
493         ok(Found == ExpectedFound,
494            "DOS path '%s' %sfound in the mount points list, expected %sto be found\n",
495            wine_dbgstr_us(&DosPath), Found ? "" : "NOT ", ExpectedFound ? "" : "NOT ");
496     }
497 
498     if (VolumePaths)
499     {
500         /* VolumePaths only contains a drive letter (usual case) or a Win32
501          * reparse point path (if the volume is mounted only with these).
502          * If the volume is NOT mounted, VolumePaths does not list the
503          * volume GUID name, contrary to VolumePath. */
504         for (pMountPoint = VolumePaths->MultiSz; *pMountPoint;
505              pMountPoint += wcslen(pMountPoint) + 1)
506         {
507             /* Only the drive letter (but NOT the volume GUID name!) can be found in the list */
508             RtlInitUnicodeString(&DosPath, pMountPoint);
509             ExpectedFound = IS_DRIVE_LETTER(&DosPath);
510             Found = doesPathExistInMountPoints(MountPoints, &DosPath);
511             ok(Found == ExpectedFound,
512                "DOS path '%s' %sfound in the mount points list, expected %sto be found\n",
513                wine_dbgstr_us(&DosPath), Found ? "" : "NOT ", ExpectedFound ? "" : "NOT ");
514         }
515     }
516 }
517 
518 /**
519  * @brief
520  * Tests the consistency of IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH,
521  * IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATHS and IOCTL_MOUNTMGR_QUERY_POINTS.
522  **/
523 static VOID
Test_QueryDosVolumePathAndPaths(_In_ HANDLE MountMgrHandle,_In_ PCWSTR NtVolumeName)524 Test_QueryDosVolumePathAndPaths(
525     _In_ HANDLE MountMgrHandle,
526     _In_ PCWSTR NtVolumeName)
527 {
528     PMOUNTMGR_VOLUME_PATHS VolumePath = NULL;
529     PMOUNTMGR_VOLUME_PATHS VolumePaths = NULL;
530     PMOUNTMGR_MOUNT_POINTS MountPoints = NULL;
531 
532     if (winetest_debug > 1)
533         trace("%S\n", NtVolumeName);
534 
535     Call_QueryDosVolume_Path_Paths(MountMgrHandle,
536                                    NtVolumeName,
537                                    IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH,
538                                    &VolumePath);
539     if (VolumePath)
540         Test_QueryDosVolumePath(NtVolumeName, VolumePath);
541     else
542         skip("Device '%S': IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH failed\n", NtVolumeName);
543 
544     Call_QueryDosVolume_Path_Paths(MountMgrHandle,
545                                    NtVolumeName,
546                                    IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATHS,
547                                    &VolumePaths);
548     if (VolumePaths)
549         Test_QueryDosVolumePaths(NtVolumeName, VolumePaths, VolumePath);
550     else
551         skip("Device '%S': IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATHS failed\n", NtVolumeName);
552 
553     Call_QueryPoints(MountMgrHandle, NtVolumeName, &MountPoints);
554     if (MountPoints)
555         Test_QueryPoints(NtVolumeName, MountPoints, VolumePath, VolumePaths);
556     else
557         skip("Device '%S': IOCTL_MOUNTMGR_QUERY_POINTS failed\n", NtVolumeName);
558 
559     if (MountPoints)
560         RtlFreeHeap(RtlGetProcessHeap(), 0, MountPoints);
561     if (VolumePaths)
562         RtlFreeHeap(RtlGetProcessHeap(), 0, VolumePaths);
563     if (VolumePath)
564         RtlFreeHeap(RtlGetProcessHeap(), 0, VolumePath);
565 }
566 
567 
START_TEST(QueryDosVolumePaths)568 START_TEST(QueryDosVolumePaths)
569 {
570     HANDLE MountMgrHandle;
571     HANDLE hFindVolume;
572     WCHAR szVolumeName[MAX_PATH];
573 
574     MountMgrHandle = GetMountMgrHandle(FILE_READ_ATTRIBUTES);
575     if (!MountMgrHandle)
576     {
577         win_skip("MountMgr unavailable: %lu\n", GetLastError());
578         return;
579     }
580 
581     hFindVolume = FindFirstVolumeW(szVolumeName, _countof(szVolumeName));
582     if (hFindVolume == INVALID_HANDLE_VALUE)
583         goto otherTests;
584     do
585     {
586         UNICODE_STRING VolumeName;
587         USHORT Length;
588 
589         /*
590          * The Win32 FindFirst/NextVolumeW() functions convert the '\??\'
591          * prefix in '\??\Volume{...}' to '\\?\' and append a trailing
592          * backslash. Test this behaviour.
593          *
594          * NOTE: these functions actively filter out anything that is NOT
595          * '\??\Volume{...}' returned from IOCTL_MOUNTMGR_QUERY_POINTS.
596          * Thus, it also excludes mount-points specified as drive letters,
597          * like '\DosDevices\C:' .
598          */
599 
600         RtlInitUnicodeString(&VolumeName, szVolumeName);
601         Length = VolumeName.Length / sizeof(WCHAR);
602         ok(Length >= 1 && VolumeName.Buffer[Length - 1] == L'\\',
603            "No trailing backslash found\n");
604 
605         /* Remove the trailing backslash */
606         if (Length >= 1)
607         {
608             VolumeName.Length -= sizeof(WCHAR);
609             if (szVolumeName[Length - 1] == L'\\')
610                 szVolumeName[Length - 1] = UNICODE_NULL;
611         }
612 
613         ok(MOUNTMGR_IS_DOS_VOLUME_NAME(&VolumeName),
614            "Invalid DOS volume path returned '%s'\n", wine_dbgstr_us(&VolumeName));
615 
616         /* Patch '\\?\' back to '\??\' to convert to an NT path */
617         if (szVolumeName[0] == L'\\' && szVolumeName[1] == L'\\' &&
618             szVolumeName[2] == L'?'  && szVolumeName[3] == L'\\')
619         {
620             szVolumeName[1] = L'?';
621         }
622 
623         Test_QueryDosVolumePathAndPaths(MountMgrHandle, szVolumeName);
624     } while (FindNextVolumeW(hFindVolume, szVolumeName, _countof(szVolumeName)));
625     FindVolumeClose(hFindVolume);
626 
627 otherTests:
628     /* Test the drive containing SystemRoot */
629     wcscpy(szVolumeName, L"\\DosDevices\\?:");
630     szVolumeName[sizeof("\\DosDevices\\")-1] = SharedUserData->NtSystemRoot[0];
631     Test_QueryDosVolumePathAndPaths(MountMgrHandle, szVolumeName);
632 
633     /* We are done */
634     CloseHandle(MountMgrHandle);
635 }
636