https://git.reactos.org/?p=reactos.git;a=commitdiff;h=e0e45ffa1a59b6e228d2e…
commit e0e45ffa1a59b6e228d2e3879ea986c30d9ead70
Author: Hermès Bélusca-Maïto <hermes.belusca-maito(a)reactos.org>
AuthorDate: Fri Dec 27 12:05:43 2024 +0100
Commit: Hermès Bélusca-Maïto <hermes.belusca-maito(a)reactos.org>
CommitDate: Mon Jan 6 21:26:12 2025 +0100
[PARTMGR_APITEST] Add a test suite for the partition manager (#7591)
CORE-13525
Add a test for IOCTL_STORAGE_GET_DEVICE_NUMBER.
---
modules/rostests/apitests/CMakeLists.txt | 1 +
modules/rostests/apitests/partmgr/CMakeLists.txt | 17 ++
.../rostests/apitests/partmgr/StorDeviceNumber.c | 306 +++++++++++++++++++++
modules/rostests/apitests/partmgr/precomp.h | 20 ++
modules/rostests/apitests/partmgr/testlist.c | 10 +
5 files changed, 354 insertions(+)
diff --git a/modules/rostests/apitests/CMakeLists.txt
b/modules/rostests/apitests/CMakeLists.txt
index 17a36e94d23..92ea11342a9 100644
--- a/modules/rostests/apitests/CMakeLists.txt
+++ b/modules/rostests/apitests/CMakeLists.txt
@@ -39,6 +39,7 @@ add_subdirectory(netshell)
add_subdirectory(ntdll)
add_subdirectory(ole32)
add_subdirectory(opengl32)
+add_subdirectory(partmgr)
add_subdirectory(pefile)
add_subdirectory(powrprof)
add_subdirectory(rtl)
diff --git a/modules/rostests/apitests/partmgr/CMakeLists.txt
b/modules/rostests/apitests/partmgr/CMakeLists.txt
new file mode 100644
index 00000000000..32684f3252e
--- /dev/null
+++ b/modules/rostests/apitests/partmgr/CMakeLists.txt
@@ -0,0 +1,17 @@
+
+list(APPEND SOURCE
+ StorDeviceNumber.c)
+
+list(APPEND PCH_SKIP_SOURCE
+ testlist.c)
+
+add_executable(partmgr_apitest
+ ${SOURCE}
+ ${PCH_SKIP_SOURCE})
+
+target_link_libraries(partmgr_apitest wine ${PSEH_LIB})
+set_module_type(partmgr_apitest win32cui)
+add_importlibs(partmgr_apitest msvcrt kernel32 ntdll)
+# TODO: Enable this when we get more than one source file to justify its use
+#add_pch(partmgr_apitest precomp.h "${PCH_SKIP_SOURCE}")
+add_rostests_file(TARGET partmgr_apitest)
diff --git a/modules/rostests/apitests/partmgr/StorDeviceNumber.c
b/modules/rostests/apitests/partmgr/StorDeviceNumber.c
new file mode 100644
index 00000000000..8407b0475f2
--- /dev/null
+++ b/modules/rostests/apitests/partmgr/StorDeviceNumber.c
@@ -0,0 +1,306 @@
+/*
+ * PROJECT: ReactOS API Tests
+ * LICENSE: GPL-2.0-or-later (
https://spdx.org/licenses/GPL-2.0-or-later)
+ * PURPOSE: Test for IOCTL_STORAGE_GET_DEVICE_NUMBER
+ * COPYRIGHT: Copyright 2024 Hermès Bélusca-Maïto
<hermes.belusca-maito(a)reactos.org>
+ */
+
+#include "precomp.h"
+#include <ntddstor.h>
+
+static LPCSTR wine_dbgstr_us(const UNICODE_STRING *us)
+{
+ if (!us) return "(null)";
+ return wine_dbgstr_wn(us->Buffer, us->Length / sizeof(WCHAR));
+}
+
+/* Flags combination allowing all the read, write and delete share modes.
+ * Currently similar to FILE_SHARE_VALID_FLAGS. */
+#define FILE_SHARE_ALL \
+ (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE)
+
+static BOOLEAN
+Test_Device_StorDeviceNumber(
+ _In_ PCWSTR NtDeviceName)
+{
+ BOOLEAN Success = FALSE; // Suppose failure.
+ NTSTATUS Status;
+ HANDLE DeviceHandle1 = NULL, DeviceHandle2 = NULL;
+ FILE_FS_DEVICE_INFORMATION DeviceInfo;
+ STORAGE_DEVICE_NUMBER DeviceNumber;
+ UNICODE_STRING DeviceName;
+ OBJECT_ATTRIBUTES ObjectAttributes;
+ IO_STATUS_BLOCK IoStatusBlock;
+ WCHAR NtLegacyDeviceName[MAX_PATH];
+
+ ULONG BufferSize;
+ struct { OBJECT_NAME_INFORMATION; WCHAR Buffer[MAX_PATH]; } DeviceName1Buffer;
+ PUNICODE_STRING DeviceName1 = &DeviceName1Buffer.Name;
+ struct { OBJECT_NAME_INFORMATION; WCHAR Buffer[MAX_PATH]; } DeviceName2Buffer;
+ PUNICODE_STRING DeviceName2 = &DeviceName2Buffer.Name;
+
+ /* Open a handle to the device */
+ RtlInitUnicodeString(&DeviceName, NtDeviceName);
+ InitializeObjectAttributes(&ObjectAttributes,
+ &DeviceName,
+ OBJ_CASE_INSENSITIVE,
+ NULL,
+ NULL);
+ Status = NtOpenFile(&DeviceHandle1,
+ FILE_READ_ATTRIBUTES | SYNCHRONIZE,
+ &ObjectAttributes,
+ &IoStatusBlock,
+ FILE_SHARE_ALL,
+ /* FILE_NON_DIRECTORY_FILE | */ FILE_SYNCHRONOUS_IO_NONALERT);
+ ok_ntstatus(Status, STATUS_SUCCESS);
+ if (!NT_SUCCESS(Status))
+ {
+ skip("Device '%s': Opening failed\n",
wine_dbgstr_us(&DeviceName));
+ goto Quit;
+ }
+
+ /* Verify the device information before proceeding further */
+ Status = NtQueryVolumeInformationFile(DeviceHandle1,
+ &IoStatusBlock,
+ &DeviceInfo,
+ sizeof(DeviceInfo),
+ FileFsDeviceInformation);
+ ok_ntstatus(Status, STATUS_SUCCESS);
+ if (!NT_SUCCESS(Status))
+ {
+ skip("FileFsDeviceInformation('%s') failed, Status
0x%08lx\n",
+ wine_dbgstr_us(&DeviceName), Status);
+ goto Quit;
+ }
+
+ /* Ignore volumes that are NOT on usual disks */
+ switch (DeviceInfo.DeviceType)
+ {
+ /* Testable devices */
+ case FILE_DEVICE_CD_ROM:
+ // case FILE_DEVICE_CD_ROM_FILE_SYSTEM:
+ case FILE_DEVICE_DISK:
+ // case FILE_DEVICE_DISK_FILE_SYSTEM:
+ // case FILE_DEVICE_NETWORK:
+ // case FILE_DEVICE_NETWORK_FILE_SYSTEM:
+ case FILE_DEVICE_VIRTUAL_DISK:
+ break;
+
+ /* Untestable devices */
+ default:
+ skip("Device '%s': Cannot test, device type %lu\n",
+ wine_dbgstr_us(&DeviceName), DeviceInfo.DeviceType);
+ goto Quit;
+ }
+#if 0
+ if (DeviceInfo.DeviceType != FILE_DEVICE_DISK &&
+ DeviceInfo.DeviceType != FILE_DEVICE_VIRTUAL_DISK &&
+ DeviceInfo.DeviceType != FILE_DEVICE_CD_ROM)
+ {
+ skip("Device '%s': Cannot test, device type %lu\n",
+ wine_dbgstr_us(&DeviceName), DeviceInfo.DeviceType);
+ goto Quit;
+ }
+#endif
+
+ /*
+ * Retrieve the storage device number.
+ * Note that this call is unsupported if this is a dynamic volume.
+ * NOTE: Usually fails for floppy disks.
+ */
+ Status = NtDeviceIoControlFile(DeviceHandle1,
+ NULL, NULL, NULL,
+ &IoStatusBlock,
+ IOCTL_STORAGE_GET_DEVICE_NUMBER,
+ NULL, 0,
+ &DeviceNumber, sizeof(DeviceNumber));
+ if (!NT_SUCCESS(Status))
+ {
+ skip("Device '%s': Couldn't retrieve disk number\n",
wine_dbgstr_us(&DeviceName));
+ goto Quit;
+ }
+ ok(DeviceNumber.DeviceType == DeviceInfo.DeviceType,
+ "Device '%s': Device type mismatch\n",
wine_dbgstr_us(&DeviceName));
+
+ /* NOTE: this value is set to 0xFFFFFFFF (-1) for the disks that
+ * represent the physical paths of a multipath I/O (MPIO) disk. */
+ ok(DeviceNumber.DeviceNumber != ULONG_MAX,
+ "Device '%s': Invalid disk number reported\n",
wine_dbgstr_us(&DeviceName));
+ if (DeviceNumber.DeviceNumber == ULONG_MAX)
+ goto Quit;
+
+ switch (DeviceInfo.DeviceType)
+ {
+ /* Testable devices */
+ case FILE_DEVICE_CD_ROM:
+ // case FILE_DEVICE_CD_ROM_FILE_SYSTEM:
+ {
+ /* CD-ROMs don't have partitions, their partition number is -1 */
+ ok(DeviceNumber.PartitionNumber == ULONG_MAX,
+ "Device '%s': Invalid partition number (%lu) reported, expected
ULONG_MAX (-1)\n",
+ wine_dbgstr_us(&DeviceName), DeviceNumber.PartitionNumber);
+
+ /* Map to an NT device name */
+ RtlStringCchPrintfW(NtLegacyDeviceName, _countof(NtLegacyDeviceName),
+ L"\\Device\\CdRom%lu",
+ DeviceNumber.DeviceNumber);
+ break;
+ }
+
+ case FILE_DEVICE_DISK:
+ // case FILE_DEVICE_DISK_FILE_SYSTEM:
+ case FILE_DEVICE_VIRTUAL_DISK:
+ {
+ /* Check whether this is a floppy or a partitionable device */
+ if (DeviceInfo.Characteristics & FILE_FLOPPY_DISKETTE)
+ {
+ /* Floppies don't have partitions, their partition number is -1 */
+ ok(DeviceNumber.PartitionNumber == ULONG_MAX,
+ "Device '%s': Invalid partition number (%lu) reported,
expected ULONG_MAX (-1)\n",
+ wine_dbgstr_us(&DeviceName), DeviceNumber.PartitionNumber);
+
+ /* Map to an NT device name */
+ RtlStringCchPrintfW(NtLegacyDeviceName, _countof(NtLegacyDeviceName),
+ L"\\Device\\Floppy%lu",
+ DeviceNumber.DeviceNumber);
+ }
+ else
+ {
+ /* The device is partitionable, so it must have a valid partition number */
+ ok(DeviceNumber.PartitionNumber != ULONG_MAX,
+ "Device '%s': Invalid partition number (%lu) reported;
unpartitionable device?\n",
+ wine_dbgstr_us(&DeviceName), DeviceNumber.PartitionNumber);
+ if (DeviceNumber.PartitionNumber == ULONG_MAX)
+ goto Quit;
+
+ /* Map to an NT device name */
+ RtlStringCchPrintfW(NtLegacyDeviceName, _countof(NtLegacyDeviceName),
+ L"\\Device\\Harddisk%lu\\Partition%lu",
+ DeviceNumber.DeviceNumber,
DeviceNumber.PartitionNumber);
+ }
+ break;
+ }
+
+ /* Untestable devices */
+ default:
+ skip("Device '%s': Cannot test, device type %lu\n",
+ wine_dbgstr_us(&DeviceName), DeviceInfo.DeviceType);
+ goto Quit;
+ }
+
+ /* Open the device using the legacy path */
+ RtlInitUnicodeString(&DeviceName, NtLegacyDeviceName);
+ InitializeObjectAttributes(&ObjectAttributes,
+ &DeviceName,
+ OBJ_CASE_INSENSITIVE,
+ NULL,
+ NULL);
+ Status = NtOpenFile(&DeviceHandle2,
+ FILE_READ_ATTRIBUTES | SYNCHRONIZE,
+ &ObjectAttributes,
+ &IoStatusBlock,
+ FILE_SHARE_ALL,
+ /* FILE_NON_DIRECTORY_FILE | */ FILE_SYNCHRONOUS_IO_NONALERT);
+ ok_ntstatus(Status, STATUS_SUCCESS);
+ if (!NT_SUCCESS(Status))
+ {
+ skip("Device '%s': Opening failed\n",
wine_dbgstr_us(&DeviceName));
+ goto Quit;
+ }
+
+ /*
+ * Verify whether both retrieved handles refer to the same device.
+ * Since we're not running on Windows 10, we cannot use
kernel32!CompareObjectHandles()
+ * or ntdll!NtCompareObjects(), therefore we have to rely on comparing
+ * whether the devices referred by both handles have the same canonical name.
+ */
+ Status = NtQueryObject(DeviceHandle1,
+ ObjectNameInformation,
+ &DeviceName1Buffer,
+ sizeof(DeviceName1Buffer),
+ &BufferSize);
+ ok_ntstatus(Status, STATUS_SUCCESS);
+
+ Status = NtQueryObject(DeviceHandle2,
+ ObjectNameInformation,
+ &DeviceName2Buffer,
+ sizeof(DeviceName2Buffer),
+ &BufferSize);
+ ok_ntstatus(Status, STATUS_SUCCESS);
+
+ Success = RtlEqualUnicodeString(DeviceName1, DeviceName2, FALSE);
+ ok(Success, "Devices '%s' and '%s' are not the same!\n",
+ wine_dbgstr_us(DeviceName1), wine_dbgstr_us(DeviceName2));
+
+Quit:
+ /* Cleanup */
+ if (DeviceHandle2)
+ NtClose(DeviceHandle2);
+ if (DeviceHandle1)
+ NtClose(DeviceHandle1);
+
+ return Success;
+}
+
+static BOOLEAN
+Test_Drive_StorDeviceNumber(
+ _In_ WCHAR Drive)
+{
+ WCHAR NtDeviceName[] = L"\\DosDevices\\?:";
+ NtDeviceName[sizeof("\\DosDevices\\")-1] = Drive;
+ return Test_Device_StorDeviceNumber(NtDeviceName);
+}
+
+START_TEST(StorDeviceNumber)
+{
+ DWORD Drives;
+ UCHAR i;
+
+ /* Enumerate existing testable drives */
+ Drives = GetLogicalDrives();
+ if (Drives == 0)
+ {
+ skip("Drives map unavailable, error 0x%lx\n", GetLastError());
+ goto otherTests;
+ }
+ for (i = 0; i <= 'Z'-'A'; ++i)
+ {
+ WCHAR DriveName[] = L"?:\\";
+ UINT DriveType;
+
+ /* Skip non-existing drives */
+ if (!(Drives & (1 << i)))
+ continue;
+
+ /* Retrieve the drive type and see whether we can test it */
+ DriveName[0] = L'A' + i;
+ DriveType = GetDriveTypeW(DriveName);
+
+ switch (DriveType)
+ {
+ case DRIVE_REMOVABLE:
+ case DRIVE_FIXED:
+ case DRIVE_CDROM:
+ case DRIVE_RAMDISK:
+ {
+ Test_Drive_StorDeviceNumber(L'A' + i);
+ break;
+ }
+
+ case DRIVE_UNKNOWN:
+ case DRIVE_NO_ROOT_DIR:
+ case DRIVE_REMOTE:
+ default:
+ /* Unhandled drive type, just skip it silently */
+ trace("Drive %c with unhandled type %u\n", 'A' + i,
DriveType);
+ break;
+ }
+ }
+
+otherTests:
+ /* Test the drive containing SystemRoot */
+ Test_Drive_StorDeviceNumber(SharedUserData->NtSystemRoot[0]);
+
+ /* Test \??\PhysicalDrive0, if it exists */
+ Test_Device_StorDeviceNumber(L"\\??\\PhysicalDrive0");
+}
diff --git a/modules/rostests/apitests/partmgr/precomp.h
b/modules/rostests/apitests/partmgr/precomp.h
new file mode 100644
index 00000000000..fc5eaa9b38b
--- /dev/null
+++ b/modules/rostests/apitests/partmgr/precomp.h
@@ -0,0 +1,20 @@
+/*
+ * PROJECT: ReactOS API Tests
+ * LICENSE: GPL-2.0-or-later (
https://spdx.org/licenses/GPL-2.0-or-later)
+ * PURPOSE: Precompiled header
+ * COPYRIGHT: Copyright 2024 Hermès Bélusca-Maïto
<hermes.belusca-maito(a)reactos.org>
+ */
+
+#pragma once
+
+#define WIN32_NO_STATUS
+#include <apitest.h>
+
+#define NTOS_MODE_USER
+#include <ndk/iofuncs.h>
+#include <ndk/obfuncs.h>
+#include <ndk/rtlfuncs.h>
+
+#include <ntstrsafe.h>
+
+/* EOF */
diff --git a/modules/rostests/apitests/partmgr/testlist.c
b/modules/rostests/apitests/partmgr/testlist.c
new file mode 100644
index 00000000000..01ad0e3a04a
--- /dev/null
+++ b/modules/rostests/apitests/partmgr/testlist.c
@@ -0,0 +1,10 @@
+#define STANDALONE
+#include <apitest.h>
+
+extern void func_StorDeviceNumber(void);
+
+const struct test winetest_testlist[] =
+{
+ { "StorDeviceNumber", func_StorDeviceNumber },
+ { 0, 0 }
+};