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 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 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 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 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 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 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 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 568 START_TEST(QueryDosVolumePaths) 569 { 570 HANDLE MountMgrHandle; 571 HANDLE hFindVolume; 572 WCHAR szVolumeName[MAX_PATH]; 573 574 MountMgrHandle = GetMountMgrHandle(); 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