https://git.reactos.org/?p=reactos.git;a=commitdiff;h=e0e45ffa1a59b6e228d2e3879ea986c30d9ead70
commit e0e45ffa1a59b6e228d2e3879ea986c30d9ead70 Author: Hermès Bélusca-Maïto <hermes.belusca-ma...@reactos.org> AuthorDate: Fri Dec 27 12:05:43 2024 +0100 Commit: Hermès Bélusca-Maïto <hermes.belusca-ma...@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-ma...@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-ma...@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 } +};