From 538b9e4fbf4dbb8c6a3eced8e7b181c19991dcb3 Mon Sep 17 00:00:00 2001 From: Pierre Schweitzer Date: Tue, 19 Oct 2010 20:34:48 +0000 Subject: [PATCH] [NTOSKRNL] Implemented FstubAdjustPartitionCount(), FstubConvertExtendedToLayout(), FstubCopyEntryEFI(), FstubCreateDiskMBR(), FstubCreateDiskEFI(), FstubCreateDiskRaw(), FstubDbgPrintSetPartitionEx(), FstubReadHeaderEFI(), FstubReadPartitionTableEFI(), FstubSetPartitionInformationEFI(), FstubVerifyPartitionTableEFI(), FstubWriteBootSectorEFI(), FstubWriteEntryEFI(), FstubWriteHeaderEFI(), FstubWritePartitionTableEFI(), FstubWritePartitionTableMBR(), FstubWriteSector() Implemented IoCreateDisk(), IoGetBootDiskInformation(), IoReadDiskSignature(), IoSetPartitionInformationEx(), IoVerifyPartitionTable(), IoWritePartitionTableEx() To sum up, this commit finishes FSTUB API implementation in the ReactOS kernel. This means one important thing: now ReactOS kernel knows about a bit about EFI and about GPT. No need to say that it's the first step into EFI support. But a lot more work is needed. Especially since the kernel is the only real entity in ReactOS to handle GPT. All the rest of the OS doesn't know anything about GPT. A small note about FstubVerifyPartitionTableEFI(). This function is supposed to check whether a disk formated with GPT is valid, and if it's not, to fix it. First step is implemented. Second step isn't yet supported. A general note about all that stuff: on GPT, backup table isn't properly handled for the moment, as ReactOS is experiencing disk geometry issues. That means it's not having the proper disk sectors count and then, can't find the backup table (which is located on last disk sector). References: http://www.intel.com/technology/efi/ http://developer.apple.com/library/mac/#technotes/tn2006/tn2166.html http://git.kernel.org/?p=linux/kernel/git/stable/linux-2.6.35.y.git;a=blob;f=fs/partitions/efi.h;hb=HEAD http://git.kernel.org/?p=linux/kernel/git/stable/linux-2.6.35.y.git;a=blob;f=fs/partitions/efi.c;hb=HEAD svn path=/trunk/; revision=49212 --- reactos/ntoskrnl/fstub/fstubex.c | 1832 +++++++++++++++++++++++++++++- 1 file changed, 1808 insertions(+), 24 deletions(-) diff --git a/reactos/ntoskrnl/fstub/fstubex.c b/reactos/ntoskrnl/fstub/fstubex.c index d04c29923e6..51b8ea48b52 100644 --- a/reactos/ntoskrnl/fstub/fstubex.c +++ b/reactos/ntoskrnl/fstub/fstubex.c @@ -14,9 +14,6 @@ /* PRIVATE FUNCTIONS *********************************************************/ -#define PARTITION_ENTRY_SIZE 128 -#define TAG_FSTUB 'BtsF' - typedef struct _DISK_INFORMATION { PDEVICE_OBJECT DeviceObject; @@ -26,6 +23,88 @@ typedef struct _DISK_INFORMATION ULONGLONG SectorCount; } DISK_INFORMATION, *PDISK_INFORMATION; +#include +typedef struct _EFI_PARTITION_HEADER +{ + ULONGLONG Signature; // 0 + ULONG Revision; // 8 + ULONG HeaderSize; // 12 + ULONG HeaderCRC32; // 16 + ULONG Reserved; // 20 + ULONGLONG MyLBA; // 24 + ULONGLONG AlternateLBA; // 32 + ULONGLONG FirstUsableLBA; // 40 + ULONGLONG LastUsableLBA; // 48 + GUID DiskGUID; // 56 + ULONGLONG PartitionEntryLBA; // 72 + ULONG NumberOfEntries; // 80 + ULONG SizeOfPartitionEntry; // 84 + ULONG PartitionEntryCRC32; // 88 +} EFI_PARTITION_HEADER, *PEFI_PARTITION_HEADER; +#include + +typedef struct _EFI_PARTITION_ENTRY +{ + GUID PartitionType; // 0 + GUID UniquePartition; // 16 + ULONGLONG StartingLBA; // 32 + ULONGLONG EndingLBA; // 40 + ULONGLONG Attributes; // 48 + WCHAR Name[0x24]; // 56 +} EFI_PARTITION_ENTRY, *PEFI_PARTITION_ENTRY; + +typedef struct _CREATE_DISK_MBR +{ + ULONG Signature; +} CREATE_DISK_MBR, *PCREATE_DISK_MBR; + +typedef struct _CREATE_DISK_GPT +{ + GUID DiskId; + ULONG MaxPartitionCount; +} CREATE_DISK_GPT, *PCREATE_DISK_GPT; + +typedef struct _CREATE_DISK +{ + PARTITION_STYLE PartitionStyle; + union + { + CREATE_DISK_MBR Mbr; + CREATE_DISK_GPT Gpt; + }; +} CREATE_DISK, *PCREATE_DISK; + +typedef struct _PARTITION_TABLE_ENTRY +{ + UCHAR BootIndicator; + UCHAR StartHead; + UCHAR StartSector; + UCHAR StartCylinder; + UCHAR SystemIndicator; + UCHAR EndHead; + UCHAR EndSector; + UCHAR EndCylinder; + ULONG SectorCountBeforePartition; + ULONG PartitionSectorCount; +} PARTITION_TABLE_ENTRY, *PPARTITION_TABLE_ENTRY; + +typedef struct _MASTER_BOOT_RECORD +{ + UCHAR MasterBootRecordCodeAndData[0x1B8]; // 0 + ULONG Signature; // 440 + USHORT Reserved; // 444 + PARTITION_TABLE_ENTRY PartitionTable[4]; // 446 + USHORT MasterBootRecordMagic; // 510 +} MASTER_BOOT_RECORD, *PMASTER_BOOT_RECORD; + +/* Tag for Fstub allocations */ +#define TAG_FSTUB 'BtsF' +/* Partition entry size (bytes) - FIXME: It's hardcoded as Microsoft does, but according to specs, it shouldn't be */ +#define PARTITION_ENTRY_SIZE 128 +/* Defines "EFI PART" */ +#define EFI_HEADER_SIGNATURE 0x5452415020494645ULL +/* Defines version 1.0 */ +#define EFI_HEADER_REVISION_1 0x00010000 /* Defines system type for MBR showing that a GPT is following */ #define EFI_PMBR_OSTYPE_EFI 0xEE @@ -42,6 +121,17 @@ FstubDbgPrintPartitionEx(IN PPARTITION_INFORMATION_EX PartitionEntry, IN ULONG PartitionNumber ); +NTSTATUS +NTAPI +FstubDetectPartitionStyle(IN PDISK_INFORMATION Disk, + IN PARTITION_STYLE * PartitionStyle +); + +VOID +NTAPI +FstubFreeDiskInformation(IN PDISK_INFORMATION DiskBuffer +); + NTSTATUS NTAPI FstubGetDiskGeometry(IN PDEVICE_OBJECT DeviceObject, @@ -56,6 +146,67 @@ FstubReadSector(IN PDEVICE_OBJECT DeviceObject, OUT PUSHORT Buffer ); +NTSTATUS +NTAPI +FstubWriteBootSectorEFI(IN PDISK_INFORMATION Disk +); + +NTSTATUS +NTAPI +FstubWritePartitionTableEFI(IN PDISK_INFORMATION Disk, + IN GUID DiskGUID, + IN ULONG MaxPartitionCount, + IN ULONGLONG FirstUsableLBA, + IN ULONGLONG LastUsableLBA, + IN BOOLEAN WriteBackupTable, + IN ULONG PartitionCount, + IN PPARTITION_INFORMATION_EX PartitionEntries OPTIONAL +); + +NTSTATUS +NTAPI +FstubWriteSector(IN PDEVICE_OBJECT DeviceObject, + IN ULONG SectorSize, + IN ULONGLONG StartingSector OPTIONAL, + IN PUSHORT Buffer +); + +VOID +NTAPI +FstubAdjustPartitionCount(IN ULONG SectorSize, + IN OUT PULONG PartitionCount) +{ + ULONG Count; + PAGED_CODE(); + + ASSERT(SectorSize); + ASSERT(PartitionCount); + + /* Get partition count */ + Count = *PartitionCount; + /* We need at least 128 entries */ + if (Count < 128) + { + Count = 128; + } + + /* Then, ensure that we will have a round value, + * ie, all sectors will be full of entries + * There won't be lonely entries + */ + Count = (Count * PARTITION_ENTRY_SIZE) / SectorSize; + Count = (Count * SectorSize) / PARTITION_ENTRY_SIZE; + ASSERT(*PartitionCount <= Count); + /* Return result */ + *PartitionCount = Count; + + /* One more sanity check */ + if (SectorSize == 512) + { + ASSERT(Count % 4 == 0); + } +} + NTSTATUS NTAPI FstubAllocateDiskInformation(IN PDEVICE_OBJECT DeviceObject, @@ -118,6 +269,265 @@ FstubAllocateDiskInformation(IN PDEVICE_OBJECT DeviceObject, return STATUS_SUCCESS; } +PDRIVE_LAYOUT_INFORMATION +NTAPI +FstubConvertExtendedToLayout(IN PDRIVE_LAYOUT_INFORMATION_EX LayoutEx) +{ + ULONG i; + PDRIVE_LAYOUT_INFORMATION DriveLayout; + PAGED_CODE(); + + ASSERT(LayoutEx); + + /* Check whether we're dealing with MBR partition table */ + if (LayoutEx->PartitionStyle != PARTITION_STYLE_MBR) + { + ASSERT(FALSE); + return NULL; + } + + /* Allocate needed buffer */ + DriveLayout = ExAllocatePoolWithTag(NonPagedPool, + FIELD_OFFSET(DRIVE_LAYOUT_INFORMATION, PartitionEntry) + + LayoutEx->PartitionCount * sizeof(PARTITION_INFORMATION), + 'BtsF'); + if (!DriveLayout) + { + return NULL; + } + + /* Convert information about partition table */ + DriveLayout->PartitionCount = LayoutEx->PartitionCount; + DriveLayout->Signature = LayoutEx->Mbr.Signature; + + /* Convert each partition */ + for (i = 0; i < LayoutEx->PartitionCount; i++) + { + DriveLayout->PartitionEntry[i].StartingOffset = LayoutEx->PartitionEntry[i].StartingOffset; + DriveLayout->PartitionEntry[i].PartitionLength = LayoutEx->PartitionEntry[i].PartitionLength; + DriveLayout->PartitionEntry[i].HiddenSectors = LayoutEx->PartitionEntry[i].Mbr.HiddenSectors; + DriveLayout->PartitionEntry[i].PartitionNumber = LayoutEx->PartitionEntry[i].PartitionNumber; + DriveLayout->PartitionEntry[i].PartitionType = LayoutEx->PartitionEntry[i].Mbr.PartitionType; + DriveLayout->PartitionEntry[i].BootIndicator = LayoutEx->PartitionEntry[i].Mbr.BootIndicator; + DriveLayout->PartitionEntry[i].RecognizedPartition = LayoutEx->PartitionEntry[i].Mbr.RecognizedPartition; + DriveLayout->PartitionEntry[i].RewritePartition = LayoutEx->PartitionEntry[i].RewritePartition; + } + + return DriveLayout; +} + +VOID +NTAPI +FstubCopyEntryEFI(OUT PEFI_PARTITION_ENTRY Entry, + IN PPARTITION_INFORMATION_EX Partition, + ULONG SectorSize) +{ + PAGED_CODE(); + + ASSERT(Entry); + ASSERT(Partition); + ASSERT(SectorSize); + + /* Just convert data to EFI partition entry type */ + Entry->PartitionType = Partition->Gpt.PartitionType; + Entry->UniquePartition = Partition->Gpt.PartitionId; + Entry->StartingLBA = Partition->StartingOffset.QuadPart / SectorSize; + Entry->EndingLBA = (Partition->StartingOffset.QuadPart + Partition->PartitionLength.QuadPart - 1) / SectorSize; + Entry->Attributes = Partition->Gpt.Attributes; + RtlCopyMemory(Entry->Name, Partition->Gpt.Name, sizeof(Entry->Name)); +} + +NTSTATUS +NTAPI +FstubCreateDiskMBR(IN PDEVICE_OBJECT DeviceObject, + IN PCREATE_DISK_MBR DiskInfo) +{ + NTSTATUS Status; + PDISK_INFORMATION Disk = NULL; + PMASTER_BOOT_RECORD MasterBootRecord; + PAGED_CODE(); + + ASSERT(DeviceObject); + + /* Allocate internal structure */ + Status = FstubAllocateDiskInformation(DeviceObject, &Disk, 0); + if (!NT_SUCCESS(Status)) + { + return Status; + } + + /* Read previous MBR, if any */ + Status = FstubReadSector(Disk->DeviceObject, + Disk->SectorSize, + 0ULL, + Disk->Buffer); + if (!NT_SUCCESS(Status)) + { + FstubFreeDiskInformation(Disk); + return Status; + } + /* Fill the buffer with needed information, we won't overwrite boot code */ + MasterBootRecord = (PMASTER_BOOT_RECORD)Disk->Buffer; + MasterBootRecord->Signature = DiskInfo->Signature; + RtlZeroMemory(MasterBootRecord->PartitionTable, sizeof(PARTITION_TABLE_ENTRY) * 4); + MasterBootRecord->MasterBootRecordMagic = BOOT_RECORD_SIGNATURE; + + /* Finally, write MBR */ + Status = FstubWriteSector(Disk->DeviceObject, + Disk->SectorSize, + 0ULL, + Disk->Buffer); + + /* Release internal structure and return */ + FstubFreeDiskInformation(Disk); + return Status; +} + +NTSTATUS +NTAPI +FstubCreateDiskEFI(IN PDEVICE_OBJECT DeviceObject, + IN PCREATE_DISK_GPT DiskInfo) +{ + NTSTATUS Status; + PDISK_INFORMATION Disk = NULL; + ULONGLONG FirstUsableLBA, LastUsableLBA; + ULONG MaxPartitionCount, SectorsForPartitions; + PAGED_CODE(); + + ASSERT(DeviceObject); + ASSERT(DiskInfo); + + /* Allocate internal structure */ + Status = FstubAllocateDiskInformation(DeviceObject, &Disk, 0); + if (!NT_SUCCESS(Status)) + { + return Status; + } + ASSERT(Disk); + + /* Write legacy MBR */ + Status = FstubWriteBootSectorEFI(Disk); + if (!NT_SUCCESS(Status)) + { + FstubFreeDiskInformation(Disk); + return Status; + } + + /* Get max entries and adjust its number */ + MaxPartitionCount = DiskInfo->MaxPartitionCount; + FstubAdjustPartitionCount(Disk->SectorSize, &MaxPartitionCount); + + /* Count number of sectors needed to store partitions */ + SectorsForPartitions = (MaxPartitionCount * PARTITION_ENTRY_SIZE) / Disk->SectorSize; + /* Set first usable LBA: Legacy MBR + GPT header + Partitions entries */ + FirstUsableLBA = SectorsForPartitions + 2; + /* Set last usable LBA: Last sector - GPT header - Partitions entries */ + LastUsableLBA = Disk->SectorCount - SectorsForPartitions - 1; + + /* First, write primary table */ + Status = FstubWritePartitionTableEFI(Disk, + DiskInfo->DiskId, + MaxPartitionCount, + FirstUsableLBA, + LastUsableLBA, + FALSE, + 0, + NULL); + /* Then, write backup table */ + if (NT_SUCCESS(Status)) + { + Status = FstubWritePartitionTableEFI(Disk, + DiskInfo->DiskId, + MaxPartitionCount, + FirstUsableLBA, + LastUsableLBA, + TRUE, + 0, + NULL); + } + + /* Release internal structure and return */ + FstubFreeDiskInformation(Disk); + return Status; +} + +NTSTATUS +NTAPI +FstubCreateDiskRaw(IN PDEVICE_OBJECT DeviceObject) +{ + NTSTATUS Status; + PDISK_INFORMATION Disk = NULL; + PARTITION_STYLE PartitionStyle; + PMASTER_BOOT_RECORD MasterBootRecord; + PAGED_CODE(); + + ASSERT(DeviceObject); + + /* Allocate internal structure */ + Status = FstubAllocateDiskInformation(DeviceObject, &Disk, 0); + if (!NT_SUCCESS(Status)) + { + return Status; + } + + /* Detect current disk partition style */ + Status = FstubDetectPartitionStyle(Disk, &PartitionStyle); + if (!NT_SUCCESS(Status)) + { + FstubFreeDiskInformation(Disk); + return Status; + } + + /* Read MBR, if any */ + Status = FstubReadSector(Disk->DeviceObject, + Disk->SectorSize, + 0ULL, + Disk->Buffer); + if (!NT_SUCCESS(Status)) + { + FstubFreeDiskInformation(Disk); + return Status; + } + + /* Only zero useful stuff */ + MasterBootRecord = (PMASTER_BOOT_RECORD)Disk->Buffer; + MasterBootRecord->Signature = 0; + RtlZeroMemory(MasterBootRecord->PartitionTable, sizeof(PARTITION_TABLE_ENTRY)); + MasterBootRecord->MasterBootRecordMagic = 0; + + /* Write back that destroyed MBR */ + Status = FstubWriteSector(Disk->DeviceObject, + Disk->SectorSize, + 0ULL, + Disk->Buffer); + /* If previous style wasn't GPT, we're done here */ + if (PartitionStyle != PARTITION_STYLE_GPT) + { + FstubFreeDiskInformation(Disk); + return Status; + } + + /* Otherwise, we've to zero the two GPT headers */ + RtlZeroMemory(Disk->Buffer, Disk->SectorSize); + /* Erase primary header */ + Status = FstubWriteSector(Disk->DeviceObject, + Disk->SectorSize, + 1ULL, + Disk->Buffer); + /* In case of success, erase backup header */ + if (NT_SUCCESS(Status)) + { + Status = FstubWriteSector(Disk->DeviceObject, + Disk->SectorSize, + Disk->SectorCount - 1ULL, + Disk->Buffer); + } + + /* Release internal structure and return */ + FstubFreeDiskInformation(Disk); + return Status; +} + PCHAR NTAPI FstubDbgGuidToString(IN PGUID Guid, @@ -220,6 +630,36 @@ FstubDbgPrintPartitionEx(IN PPARTITION_INFORMATION_EX PartitionEntry, } } +VOID +NTAPI +FstubDbgPrintSetPartitionEx(IN PSET_PARTITION_INFORMATION_EX PartitionEntry, + IN ULONG PartitionNumber) +{ + CHAR Guid[38]; + PAGED_CODE(); + + DPRINT1("FSTUB: SET_PARTITION_INFORMATION_EX: %p\n", PartitionEntry); + DPRINT1("Modifying partition %ld\n", PartitionNumber); + switch (PartitionEntry->PartitionStyle) + { + case PARTITION_STYLE_MBR: + DPRINT1(" PartitionType: %02x\n", PartitionEntry->Mbr.PartitionType); + + break; + case PARTITION_STYLE_GPT: + FstubDbgGuidToString(&(PartitionEntry->Gpt.PartitionType), Guid); + DPRINT1(" PartitionType: %s\n", Guid); + FstubDbgGuidToString(&(PartitionEntry->Gpt.PartitionId), Guid); + DPRINT1(" PartitionId: %s\n", Guid); + DPRINT1(" Attributes: %16x\n", PartitionEntry->Gpt.Attributes); + DPRINT1(" Name: %ws\n", PartitionEntry->Gpt.Name); + + break; + default: + DPRINT1(" Unsupported partition style: %ld\n", PartitionEntry[PartitionNumber].PartitionStyle); + } +} + NTSTATUS NTAPI FstubDetectPartitionStyle(IN PDISK_INFORMATION Disk, @@ -374,14 +814,281 @@ Cleanup: return Status; } +NTSTATUS +NTAPI +FstubReadHeaderEFI(IN PDISK_INFORMATION Disk, + IN BOOLEAN ReadBackupTable, + PEFI_PARTITION_HEADER HeaderBuffer) +{ + NTSTATUS Status; + PUCHAR Sector = NULL; + ULONGLONG StartingSector; + PEFI_PARTITION_HEADER EFIHeader; + ULONG i, HeaderCRC32, PreviousCRC32, SectoredPartitionEntriesSize, LonelyPartitions; + PAGED_CODE(); + + ASSERT(Disk); + ASSERT(IS_VALID_DISK_INFO(Disk)); + ASSERT(HeaderBuffer); + + /* In case we want to read backup table, we read last disk sector */ + if (ReadBackupTable) + { + StartingSector = Disk->SectorCount - 1ULL; + } + else + { + /* Otherwise we start at first sector (as sector 0 is the MBR) */ + StartingSector = 1ULL; + } + + Status = FstubReadSector(Disk->DeviceObject, + Disk->SectorSize, + StartingSector, + Disk->Buffer); + if (!NT_SUCCESS(Status)) + { + DPRINT("EFI::Failed reading header!\n"); + return Status; + } + /* Let's use read buffer as EFI_PARTITION_HEADER */ + EFIHeader = (PEFI_PARTITION_HEADER)Disk->Buffer; + + + /* First check signature + * Then, check version (we only support v1) + * Finally check header size + */ + if (EFIHeader->Signature != EFI_HEADER_SIGNATURE || + EFIHeader->Revision != EFI_HEADER_REVISION_1 || + EFIHeader->HeaderSize != sizeof(EFI_PARTITION_HEADER)) + { + DPRINT("EFI::Wrong signature/version/header size!\n"); + DPRINT("%I64x (expected: %I64x)\n", EFIHeader->Signature, EFI_HEADER_SIGNATURE); + DPRINT("%03x (expected: %03x)\n", EFIHeader->Revision, EFI_HEADER_REVISION_1); + DPRINT("%02x (expected: %02x)\n", EFIHeader->HeaderSize, sizeof(EFI_PARTITION_HEADER)); + return STATUS_DISK_CORRUPT_ERROR; + } + + /* Save current checksum */ + HeaderCRC32 = EFIHeader->HeaderCRC32; + /* Then zero the one in EFI header. This is needed to compute header checksum */ + EFIHeader->HeaderCRC32 = 0; + /* Compute header checksum and compare with the one present in partition table */ + if (RtlComputeCrc32(0, (PUCHAR)Disk->Buffer, sizeof(EFI_PARTITION_HEADER)) != HeaderCRC32) + { + DPRINT("EFI::Not matching header checksum!\n"); + return STATUS_DISK_CORRUPT_ERROR; + } + /* Put back removed checksum in header */ + EFIHeader->HeaderCRC32 = HeaderCRC32; + + /* Check if current LBA is matching with ours */ + if (EFIHeader->MyLBA != StartingSector) + { + DPRINT("EFI::Not matching starting sector!\n"); + return STATUS_DISK_CORRUPT_ERROR; + } + + /* Allocate a buffer to read a sector on the disk */ + Sector = ExAllocatePoolWithTag(NonPagedPool, + Disk->SectorSize, + 'BtsF'); + if (!Sector) + { + DPRINT("EFI::Lacking resources!\n"); + return STATUS_INSUFFICIENT_RESOURCES; + } + + /* Count how much sectors we'll have to read to read the whole partition table */ + SectoredPartitionEntriesSize = (EFIHeader->NumberOfEntries * PARTITION_ENTRY_SIZE) / Disk->SectorSize; + /* Compute partition table checksum */ + for (i = 0, PreviousCRC32 = 0; i < SectoredPartitionEntriesSize; i++) + { + Status = FstubReadSector(Disk->DeviceObject, + Disk->SectorSize, + EFIHeader->PartitionEntryLBA + i, + (PUSHORT)Sector); + if (!NT_SUCCESS(Status)) + { + ExFreePoolWithTag(Sector, 'BtsF'); + DPRINT("EFI::Failed reading sector for partition entry!\n"); + return Status; + } + + PreviousCRC32 = RtlComputeCrc32(PreviousCRC32, Sector, Disk->SectorSize); + } + + /* Check whether we have a last sector not full of partitions */ + LonelyPartitions = (EFIHeader->NumberOfEntries * PARTITION_ENTRY_SIZE) % Disk->SectorSize; + /* In such case, we have to complete checksum computation */ + if (LonelyPartitions != 0) + { + /* Read the sector that contains those partitions */ + Status = FstubReadSector(Disk->DeviceObject, + Disk->SectorSize, + EFIHeader->PartitionEntryLBA + i, + (PUSHORT)Sector); + if (!NT_SUCCESS(Status)) + { + ExFreePoolWithTag(Sector, 'BtsF'); + DPRINT("EFI::Failed reading sector for partition entry!\n"); + return Status; + } + + /* Then complete checksum by computing on each partition */ + for (i = 0; i < LonelyPartitions; i++) + { + PreviousCRC32 = RtlComputeCrc32(PreviousCRC32, Sector + i * PARTITION_ENTRY_SIZE, PARTITION_ENTRY_SIZE); + } + } + + /* Finally, release memory */ + ExFreePoolWithTag(Sector, 'BtsF'); + + /* Compare checksums */ + if (PreviousCRC32 == EFIHeader->PartitionEntryCRC32) + { + /* In case of a success, return read header */ + *HeaderBuffer = *EFIHeader; + return STATUS_SUCCESS; + } + else + { + DPRINT("EFI::Not matching partition table checksum!\n"); + DPRINT("EFI::Expected: %x, received: %x\n", EFIHeader->PartitionEntryCRC32, PreviousCRC32); + return STATUS_DISK_CORRUPT_ERROR; + } +} + NTSTATUS NTAPI FstubReadPartitionTableEFI(IN PDISK_INFORMATION Disk, IN BOOLEAN ReadBackupTable, OUT struct _DRIVE_LAYOUT_INFORMATION_EX** DriveLayout) { - UNIMPLEMENTED; - return STATUS_NOT_IMPLEMENTED; + NTSTATUS Status; + EFI_PARTITION_HEADER EfiHeader; + ULONGLONG SectorsForPartitions; + EFI_PARTITION_ENTRY PartitionEntry; + BOOLEAN UpdatedPartitionTable = FALSE; + PDRIVE_LAYOUT_INFORMATION_EX DriveLayoutEx = NULL; + ULONG i, PartitionCount, PartitionIndex, PartitionsPerSector; + PAGED_CODE(); + + ASSERT(Disk); + + /* Zero output */ + *DriveLayout = NULL; + + /* Read EFI header */ + Status = FstubReadHeaderEFI(Disk, + ReadBackupTable, + &EfiHeader); + if (!NT_SUCCESS(Status)) + { + return Status; + } + + /* Allocate a DRIVE_LAYOUT_INFORMATION_EX struct big enough */ + DriveLayoutEx = ExAllocatePoolWithTag(NonPagedPool, + FIELD_OFFSET(DRIVE_LAYOUT_INFORMATION_EX, PartitionEntry) + + EfiHeader.NumberOfEntries * sizeof(PARTITION_INFORMATION_EX), + TAG_FSTUB); + if (!DriveLayoutEx) + { + return STATUS_INSUFFICIENT_RESOURCES; + } + + if (ReadBackupTable) + { + /* If we read backup but if it doesn't match with current geometry */ + if ((Disk->SectorCount - 1ULL) != EfiHeader.AlternateLBA) + { + /* We'll update it. First, count number of sectors needed to store partitions */ + SectorsForPartitions = (EfiHeader.NumberOfEntries * PARTITION_ENTRY_SIZE) / Disk->SectorSize; + /* Then set first usable LBA: Legacy MBR + GPT header + Partitions entries */ + EfiHeader.FirstUsableLBA = SectorsForPartitions + 2; + /* Then set last usable LBA: Last sector - GPT header - Partitions entries */ + EfiHeader.LastUsableLBA = Disk->SectorCount - SectorsForPartitions - 1; + /* Inform that we'll rewrite partition table */ + UpdatedPartitionTable = TRUE; + } + } + + DriveLayoutEx->PartitionStyle = PARTITION_STYLE_GPT; + /* Translate LBA -> Offset */ + DriveLayoutEx->Gpt.StartingUsableOffset.QuadPart = EfiHeader.FirstUsableLBA * Disk->SectorSize; + DriveLayoutEx->Gpt.UsableLength.QuadPart = EfiHeader.LastUsableLBA - EfiHeader.FirstUsableLBA * Disk->SectorSize; + DriveLayoutEx->Gpt.MaxPartitionCount = EfiHeader.NumberOfEntries; + DriveLayoutEx->Gpt.DiskId = EfiHeader.DiskGUID; + + /* Count number of partitions per sector */ + PartitionsPerSector = (Disk->SectorSize / PARTITION_ENTRY_SIZE); + /* Read all partitions and fill in structure */ + for (i = 0, PartitionCount = 0, PartitionIndex = PartitionsPerSector; + i < EfiHeader.NumberOfEntries; + i++) + { + /* Only read following sector if we finished with previous sector */ + if (PartitionIndex == PartitionsPerSector) + { + Status = FstubReadSector(Disk->DeviceObject, + Disk->SectorSize, + EfiHeader.PartitionEntryLBA + (i / PartitionsPerSector), + Disk->Buffer); + if (!NT_SUCCESS(Status)) + { + ExFreePoolWithTag(DriveLayoutEx, TAG_FSTUB); + return Status; + } + + PartitionIndex = 0; + } + /* Read following partition */ + PartitionEntry = ((PEFI_PARTITION_ENTRY)Disk->Buffer)[PartitionIndex]; + PartitionIndex++; + + /* If partition GUID is 00000000-0000-0000-0000-000000000000, then it's unused, skip it */ + if (PartitionEntry.PartitionType.Data1 == 0 && + PartitionEntry.PartitionType.Data2 == 0 && + PartitionEntry.PartitionType.Data3 == 0 && + ((PULONGLONG)PartitionEntry.PartitionType.Data4)[0] == 0) + { + continue; + } + + /* Write data to structure. Don't forget GPT is using sectors, Windows offsets */ + DriveLayoutEx->PartitionEntry[PartitionCount].StartingOffset.QuadPart = PartitionEntry.StartingLBA * Disk->SectorSize; + DriveLayoutEx->PartitionEntry[PartitionCount].PartitionLength.QuadPart = (PartitionEntry.EndingLBA - + PartitionEntry.StartingLBA + 1) * + Disk->SectorSize; + /* This number starts from 1 */ + DriveLayoutEx->PartitionEntry[PartitionCount].PartitionNumber = PartitionCount + 1; + DriveLayoutEx->PartitionEntry[PartitionCount].RewritePartition = FALSE; + DriveLayoutEx->PartitionEntry[PartitionCount].PartitionStyle = PARTITION_STYLE_GPT; + DriveLayoutEx->PartitionEntry[PartitionCount].Gpt.PartitionType = PartitionEntry.PartitionType; + DriveLayoutEx->PartitionEntry[PartitionCount].Gpt.PartitionId = PartitionEntry.UniquePartition; + DriveLayoutEx->PartitionEntry[PartitionCount].Gpt.Attributes = PartitionEntry.Attributes; + RtlCopyMemory(DriveLayoutEx->PartitionEntry[PartitionCount].Gpt.Name, + PartitionEntry.Name, sizeof(PartitionEntry.Name)); + + /* Update partition count */ + PartitionCount++; + } + DriveLayoutEx->PartitionCount = PartitionCount; + + /* If we updated partition table using backup table, rewrite partition table */ + if (UpdatedPartitionTable) + { + IoWritePartitionTableEx(Disk->DeviceObject, + DriveLayoutEx); + } + + /* Finally, return read data */ + *DriveLayout = DriveLayoutEx; + + return Status; } NTSTATUS @@ -503,34 +1210,843 @@ FstubReadSector(IN PDEVICE_OBJECT DeviceObject, return Status; } +NTSTATUS +NTAPI +FstubSetPartitionInformationEFI(IN PDISK_INFORMATION Disk, + IN ULONG PartitionNumber, + IN SET_PARTITION_INFORMATION_GPT * PartitionInfo) +{ + NTSTATUS Status; + PDRIVE_LAYOUT_INFORMATION_EX Layout = NULL; + PAGED_CODE(); + + ASSERT(Disk); + ASSERT(PartitionInfo); + + /* Partition 0 isn't correct (should start at 1) */ + if (PartitionNumber == 0) + { + return STATUS_INVALID_PARAMETER; + } + + /* Read partition table */ + Status = IoReadPartitionTableEx(Disk->DeviceObject, &Layout); + if (!NT_SUCCESS(Status)) + { + return Status; + } + ASSERT(Layout); + + /* If our partition (started at 0 now) is higher than partition count, then, there's an issue */ + if (Layout->PartitionCount <= --PartitionNumber) + { + ExFreePool(Layout); + return STATUS_INVALID_PARAMETER; + } + + /* Erase actual partition entry data with provided ones */ + Layout->PartitionEntry[PartitionNumber].Gpt.PartitionType = PartitionInfo->PartitionType; + Layout->PartitionEntry[PartitionNumber].Gpt.PartitionId = PartitionInfo->PartitionId; + Layout->PartitionEntry[PartitionNumber].Gpt.Attributes = PartitionInfo->Attributes; + RtlCopyMemory(Layout->PartitionEntry[PartitionNumber].Gpt.Name, PartitionInfo->Name, sizeof(PartitionInfo->Name)); + + /* Rewrite the whole partition table to update the modified entry */ + Status = IoWritePartitionTableEx(Disk->DeviceObject, Layout); + + /* Free partition table and return */ + ExFreePool(Layout); + return Status; +} + +NTSTATUS +NTAPI +FstubVerifyPartitionTableEFI(IN PDISK_INFORMATION Disk, + IN BOOLEAN FixErrors) +{ + NTSTATUS Status; + PEFI_PARTITION_HEADER EFIHeader; + EFI_PARTITION_HEADER ReadEFIHeader; + BOOLEAN PrimaryValid = FALSE, BackupValid = FALSE; + PAGED_CODE(); + + EFIHeader = ExAllocatePoolWithTag(NonPagedPool, sizeof(EFI_PARTITION_HEADER), TAG_FSTUB); + if (!EFIHeader) + { + return STATUS_INSUFFICIENT_RESOURCES; + } + + Status = FstubReadHeaderEFI(Disk, FALSE, &ReadEFIHeader); + if (NT_SUCCESS(Status)) + { + PrimaryValid = TRUE; + } + + Status = FstubReadHeaderEFI(Disk, TRUE, &ReadEFIHeader); + if (NT_SUCCESS(Status)) + { + BackupValid = TRUE; + } + + if (!PrimaryValid) + { + if (!BackupValid || !FixErrors) + { + ExFreePoolWithTag(EFIHeader, TAG_FSTUB); + return STATUS_DISK_CORRUPT_ERROR; + } + + DPRINT1("EFI::Partition table fixing not yet supported!\n"); + ExFreePoolWithTag(EFIHeader, TAG_FSTUB); + return STATUS_NOT_IMPLEMENTED; + } + else if (!BackupValid) + { + if (!PrimaryValid || !FixErrors) + { + ExFreePoolWithTag(EFIHeader, TAG_FSTUB); + return STATUS_DISK_CORRUPT_ERROR; + } + + DPRINT1("EFI::Partition table fixing not yet supported!\n"); + ExFreePoolWithTag(EFIHeader, TAG_FSTUB); + return STATUS_NOT_IMPLEMENTED; + } + else + { + ExFreePoolWithTag(EFIHeader, TAG_FSTUB); + return STATUS_SUCCESS; + } +} + +NTSTATUS +NTAPI +FstubWriteBootSectorEFI(IN PDISK_INFORMATION Disk) +{ + NTSTATUS Status; + ULONG Signature = 0; + PMASTER_BOOT_RECORD MasterBootRecord; + PAGED_CODE(); + + ASSERT(Disk); + ASSERT(IS_VALID_DISK_INFO(Disk)); + + /* Read if a MBR is already present */ + Status = FstubReadSector(Disk->DeviceObject, + Disk->SectorSize, + 0ULL, + Disk->Buffer); + MasterBootRecord = (PMASTER_BOOT_RECORD)Disk->Buffer; + /* If one has been found */ + if (NT_SUCCESS(Status) && MasterBootRecord->MasterBootRecordMagic == BOOT_RECORD_SIGNATURE) + { + /* Save its signature */ + Signature = MasterBootRecord->Signature; + } + + /* Reset the MBR */ + RtlZeroMemory(MasterBootRecord, Disk->SectorSize); + /* Then create a fake MBR matching those purposes: + * It must have only partition. Type of this partition + * has to be 0xEE to signal a GPT is following. + * This partition has to cover the whole disk. To prevent + * any disk modification by a program that wouldn't + * understand anything to GPT. + */ + MasterBootRecord->Signature = Signature; + MasterBootRecord->PartitionTable[0].StartSector = 2; + MasterBootRecord->PartitionTable[0].SystemIndicator = EFI_PMBR_OSTYPE_EFI; + MasterBootRecord->PartitionTable[0].EndHead = 0xFF; + MasterBootRecord->PartitionTable[0].EndSector = 0xFF; + MasterBootRecord->PartitionTable[0].EndCylinder = 0xFF; + MasterBootRecord->PartitionTable[0].SectorCountBeforePartition = 1; + MasterBootRecord->PartitionTable[0].PartitionSectorCount = 0xFFFFFFFF; + MasterBootRecord->MasterBootRecordMagic = BOOT_RECORD_SIGNATURE; + + /* Finally, write that MBR */ + return FstubWriteSector(Disk->DeviceObject, + Disk->SectorSize, + 0, + Disk->Buffer); +} + +NTSTATUS +NTAPI +FstubWriteEntryEFI(IN PDISK_INFORMATION Disk, + IN ULONG PartitionsSizeSector, + IN ULONG PartitionEntryNumber, + IN PEFI_PARTITION_ENTRY PartitionEntry, + IN BOOLEAN WriteBackupTable, + IN BOOLEAN ForceWrite, + OUT PULONG PartitionEntryCRC32 OPTIONAL) +{ + ULONG Offset; + ULONGLONG FirstEntryLBA; + NTSTATUS Status = STATUS_SUCCESS; + PAGED_CODE(); + + ASSERT(Disk); + ASSERT(IS_VALID_DISK_INFO(Disk)); + + /* Get the first LBA where the partition table is: + * On primary table, it's sector 2 (skip MBR & Header) + * On backup table, it's ante last sector (Header) minus partition table size + */ + if (!WriteBackupTable) + { + FirstEntryLBA = 2ULL; + } + else + { + FirstEntryLBA = Disk->SectorCount - PartitionsSizeSector - 1; + } + + /* Copy the entry at the proper place into the buffer + * That way, we don't erase previous entries + */ + RtlCopyMemory(Disk->Buffer + (((PartitionEntryNumber * PARTITION_ENTRY_SIZE) % Disk->SectorSize) / sizeof(PUSHORT)), + PartitionEntry, + sizeof(EFI_PARTITION_ENTRY)); + /* Compute size of buffer */ + Offset = (PartitionEntryNumber * PARTITION_ENTRY_SIZE) % Disk->SectorSize + PARTITION_ENTRY_SIZE; + ASSERT(Offset <= Disk->SectorSize); + + /* If it's full of partition entries, or if call ask for it, write down the data */ + if (Offset == Disk->SectorSize || ForceWrite) + { + /* We will write at first entry LBA + a shift made by already present/written entries */ + Status = FstubWriteSector(Disk->DeviceObject, + Disk->SectorSize, + FirstEntryLBA + ((PartitionEntryNumber * PARTITION_ENTRY_SIZE) / Disk->SectorSize), + Disk->Buffer); + if (!NT_SUCCESS(Status)) + { + return Status; + } + /* We clean buffer */ + RtlZeroMemory(Disk->Buffer, Disk->SectorSize); + } + + /* If we have a buffer for CRC32, then compute it */ + if (PartitionEntryCRC32) + { + *PartitionEntryCRC32 = RtlComputeCrc32(*PartitionEntryCRC32, (PUCHAR)PartitionEntry, PARTITION_ENTRY_SIZE); + } + + return Status; +} + +NTSTATUS +NTAPI +FstubWriteHeaderEFI(IN PDISK_INFORMATION Disk, + IN ULONG PartitionsSizeSector, + IN GUID DiskGUID, + IN ULONG NumberOfEntries, + IN ULONGLONG FirstUsableLBA, + IN ULONGLONG LastUsableLBA, + IN ULONG PartitionEntryCRC32, + IN BOOLEAN WriteBackupTable) +{ + PEFI_PARTITION_HEADER EFIHeader; + PAGED_CODE(); + + ASSERT(Disk); + ASSERT(IS_VALID_DISK_INFO(Disk)); + + /* Let's use read buffer as EFI_PARTITION_HEADER */ + EFIHeader = (PEFI_PARTITION_HEADER)Disk->Buffer; + + /* Complete standard header information */ + EFIHeader->Signature = EFI_HEADER_SIGNATURE; + EFIHeader->Revision = EFI_HEADER_REVISION_1; + EFIHeader->HeaderSize = sizeof(EFI_PARTITION_HEADER); + /* Set no CRC32 checksum at the moment */ + EFIHeader->HeaderCRC32 = 0; + EFIHeader->Reserved = 0; + /* Check whether we're writing primary or backup + * That way, we can ajust LBA setting: + * Primary is on first sector + * Backup is on last sector + */ + if (!WriteBackupTable) + { + EFIHeader->MyLBA = 1ULL; + EFIHeader->AlternateLBA = Disk->SectorCount - 1ULL; + } + else + { + EFIHeader->MyLBA = Disk->SectorCount - 1ULL; + EFIHeader->AlternateLBA = 1ULL; + } + /* Fill in with received data */ + EFIHeader->FirstUsableLBA = FirstUsableLBA; + EFIHeader->LastUsableLBA = LastUsableLBA; + EFIHeader->DiskGUID = DiskGUID; + /* Check whether we're writing primary or backup + * That way, we can ajust LBA setting: + * On primary, partition entries are just after header, so sector 2 + * On backup, partition entries are just before header, so, last sector minus partition table size + */ + if (!WriteBackupTable) + { + EFIHeader->PartitionEntryLBA = EFIHeader->MyLBA + 1ULL; + } + else + { + EFIHeader->PartitionEntryLBA = EFIHeader->MyLBA - PartitionsSizeSector; + } + /* Complete filling in */ + EFIHeader->NumberOfEntries = NumberOfEntries; + EFIHeader->SizeOfPartitionEntry = PARTITION_ENTRY_SIZE; + EFIHeader->PartitionEntryCRC32 = PartitionEntryCRC32; + /* Finally, compute header checksum */ + EFIHeader->HeaderCRC32 = RtlComputeCrc32(0, (PUCHAR)EFIHeader, sizeof(EFI_PARTITION_HEADER)); + + /* Debug the way we'll break disk, to let user pray */ + DPRINT("FSTUB: About to write the following header for %s table\n", (WriteBackupTable ? "backup" : "primary")); + DPRINT(" Signature: %I64x\n Revision: %x\n HeaderSize: %x\n HeaderCRC32: %x\n", + EFIHeader->Signature, EFIHeader->Revision, EFIHeader->HeaderSize, EFIHeader->HeaderCRC32); + DPRINT(" MyLBA: %I64x\n AlternateLBA: %I64x\n FirstUsableLBA: %I64x\n LastUsableLBA: %I64x\n", + EFIHeader->MyLBA, EFIHeader->AlternateLBA, EFIHeader->FirstUsableLBA, EFIHeader->LastUsableLBA); + DPRINT(" PartitionEntryLBA: %I64x\n NumberOfEntries: %x\n SizeOfPartitionEntry: %x\n PartitionEntryCRC32: %x\n", + EFIHeader->PartitionEntryLBA, EFIHeader->NumberOfEntries, + EFIHeader->SizeOfPartitionEntry, EFIHeader->PartitionEntryCRC32); + + /* Write header to disk */ + return FstubWriteSector(Disk->DeviceObject, + Disk->SectorSize, + EFIHeader->MyLBA, + Disk->Buffer); +} + +NTSTATUS +NTAPI +FstubWritePartitionTableEFI(IN PDISK_INFORMATION Disk, + IN GUID DiskGUID, + IN ULONG MaxPartitionCount, + IN ULONGLONG FirstUsableLBA, + IN ULONGLONG LastUsableLBA, + IN BOOLEAN WriteBackupTable, + IN ULONG PartitionCount, + IN PPARTITION_INFORMATION_EX PartitionEntries OPTIONAL) +{ + NTSTATUS Status; + EFI_PARTITION_ENTRY Entry; + ULONG i, WrittenPartitions, SectoredPartitionEntriesSize, PartitionEntryCRC32; + PAGED_CODE(); + + ASSERT(Disk); + ASSERT(MaxPartitionCount >= 128); + ASSERT(PartitionCount <= MaxPartitionCount); + + PartitionEntryCRC32 = 0; + /* Count how much sectors we'll have to read to read the whole partition table */ + SectoredPartitionEntriesSize = (MaxPartitionCount * PARTITION_ENTRY_SIZE) / Disk->SectorSize; + + for (i = 0, WrittenPartitions = 0; i < PartitionCount; i++) + { + /* If partition GUID is 00000000-0000-0000-0000-000000000000, then it's unused, skip it */ + if (PartitionEntries[i].Gpt.PartitionType.Data1 == 0 && + PartitionEntries[i].Gpt.PartitionType.Data2 == 0 && + PartitionEntries[i].Gpt.PartitionType.Data3 == 0 && + ((PULONGLONG)PartitionEntries[i].Gpt.PartitionType.Data4)[0] == 0) + { + continue; + } + + /* Copy the entry in the partition entry format */ + FstubCopyEntryEFI(&Entry, &PartitionEntries[i], Disk->SectorSize); + /* Then write the entry to the disk */ + Status = FstubWriteEntryEFI(Disk, + SectoredPartitionEntriesSize, + WrittenPartitions, + &Entry, + WriteBackupTable, + FALSE, + &PartitionEntryCRC32); + if (!NT_SUCCESS(Status)) + { + return Status; + } + WrittenPartitions++; + } + + /* Zero the buffer to write zeros to the disk */ + RtlZeroMemory(&Entry, sizeof(EFI_PARTITION_ENTRY)); + /* Write the disks with zeros for every unused remaining partition entry */ + for (i = WrittenPartitions; i < MaxPartitionCount; i++) + { + Status = FstubWriteEntryEFI(Disk, + SectoredPartitionEntriesSize, + i, + &Entry, + WriteBackupTable, + FALSE, + &PartitionEntryCRC32); + if (!NT_SUCCESS(Status)) + { + return Status; + } + } + + /* Once we're done, write the GPT header */ + return FstubWriteHeaderEFI(Disk, + SectoredPartitionEntriesSize, + DiskGUID, + MaxPartitionCount, + FirstUsableLBA, + LastUsableLBA, + PartitionEntryCRC32, + WriteBackupTable); +} + +NTSTATUS +NTAPI +FstubWritePartitionTableMBR(IN PDISK_INFORMATION Disk, + IN PDRIVE_LAYOUT_INFORMATION_EX LayoutEx) +{ + NTSTATUS Status; + PDRIVE_LAYOUT_INFORMATION DriveLayout; + PAGED_CODE(); + + ASSERT(IS_VALID_DISK_INFO(Disk)); + ASSERT(LayoutEx); + + /* Convert data to the correct format */ + DriveLayout = FstubConvertExtendedToLayout(LayoutEx); + if (!DriveLayout) + { + return STATUS_INSUFFICIENT_RESOURCES; + } + + /* Really write information */ + Status = IoWritePartitionTable(Disk->DeviceObject, + Disk->SectorSize, + Disk->DiskGeometry.Geometry.SectorsPerTrack, + Disk->DiskGeometry.Geometry.TracksPerCylinder, + DriveLayout); + + /* Free allocated structure and return */ + ExFreePool(DriveLayout); + return Status; +} + +NTSTATUS +NTAPI +FstubWriteSector(IN PDEVICE_OBJECT DeviceObject, + IN ULONG SectorSize, + IN ULONGLONG StartingSector OPTIONAL, + IN PUSHORT Buffer) +{ + PIRP Irp; + KEVENT Event; + NTSTATUS Status; + LARGE_INTEGER StartingOffset; + IO_STATUS_BLOCK IoStatusBlock; + PIO_STACK_LOCATION IoStackLocation; + PAGED_CODE(); + + ASSERT(DeviceObject); + ASSERT(Buffer); + ASSERT(SectorSize); + + /* Compute starting offset */ + StartingOffset.QuadPart = StartingSector * SectorSize; + + /* Initialize waiting event */ + KeInitializeEvent(&Event, NotificationEvent, FALSE); + + /* Prepare IRP */ + Irp = IoBuildSynchronousFsdRequest(IRP_MJ_WRITE, + DeviceObject, + Buffer, + SectorSize, + &StartingOffset, + &Event, + &IoStatusBlock); + if (!Irp) + { + return STATUS_INSUFFICIENT_RESOURCES; + } + + /* Override volume verify */ + IoStackLocation = IoGetNextIrpStackLocation(Irp); + IoStackLocation->Flags |= SL_OVERRIDE_VERIFY_VOLUME; + + /* Then call driver, and wait for completion if needed */ + Status = IoCallDriver(DeviceObject, Irp); + if (Status == STATUS_PENDING) + { + KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL); + Status = IoStatusBlock.Status; + } + + return Status; +} + /* FUNCTIONS *****************************************************************/ /* - * @unimplemented + * @implemented */ NTSTATUS NTAPI IoCreateDisk(IN PDEVICE_OBJECT DeviceObject, IN struct _CREATE_DISK* Disk) { - UNIMPLEMENTED; - return STATUS_NOT_IMPLEMENTED; + PARTITION_STYLE PartitionStyle; + PAGED_CODE(); + + ASSERT(DeviceObject); + + /* Get partition style. If caller didn't provided data, assume it's raw */ + PartitionStyle = ((Disk) ? Disk->PartitionStyle : PARTITION_STYLE_RAW); + /* Then, call appropriate internal function */ + switch (PartitionStyle) + { + case PARTITION_STYLE_MBR: + return FstubCreateDiskMBR(DeviceObject, &(Disk->Mbr)); + case PARTITION_STYLE_GPT: + return FstubCreateDiskEFI(DeviceObject, &(Disk->Gpt)); + case PARTITION_STYLE_RAW: + return FstubCreateDiskRaw(DeviceObject); + default: + return STATUS_NOT_SUPPORTED; + } } /* - * @unimplemented + * @implemented */ NTSTATUS NTAPI IoGetBootDiskInformation(IN OUT PBOOTDISK_INFORMATION BootDiskInformation, IN ULONG Size) { - UNIMPLEMENTED; - return STATUS_NOT_IMPLEMENTED; + PIRP Irp; + KEVENT Event; + PLIST_ENTRY NextEntry; + PFILE_OBJECT FileObject; + DISK_GEOMETRY DiskGeometry; + PDEVICE_OBJECT DeviceObject; + UNICODE_STRING DeviceStringW; + IO_STATUS_BLOCK IoStatusBlock; + CHAR Buffer[128], ArcBuffer[128]; + NTSTATUS Status = STATUS_SUCCESS; + BOOLEAN SingleDisk, IsBootDiskInfoEx; + PARC_DISK_SIGNATURE ArcDiskSignature; + PARC_DISK_INFORMATION ArcDiskInformation; + PARTITION_INFORMATION_EX PartitionInformation; + PDRIVE_LAYOUT_INFORMATION_EX DriveLayout = NULL; + ULONG DiskCount, DiskNumber, Signature, PartitionNumber; + ANSI_STRING ArcBootString, ArcSystemString, DeviceStringA, ArcNameStringA; + extern PLOADER_PARAMETER_BLOCK IopLoaderBlock; + PAGED_CODE(); + + /* Get loader block. If it's null, we come to late */ + if (!IopLoaderBlock) + { + return STATUS_TOO_LATE; + } + + /* Check buffer size */ + if (Size < sizeof(BOOTDISK_INFORMATION)) + { + return STATUS_INVALID_PARAMETER; + } + + /* Init some useful stuff: + * Get arc disks information + * Check whether we have a single disk + * Check received structure size (extended or not?) + * Init boot strings (system/boot) + * Finaly, get disk count + */ + ArcDiskInformation = IopLoaderBlock->ArcDiskInformation; + SingleDisk = IsListEmpty(&(ArcDiskInformation->DiskSignatureListHead)); + IsBootDiskInfoEx = (Size >= sizeof(BOOTDISK_INFORMATION_EX)); + RtlInitAnsiString(&ArcBootString, IopLoaderBlock->ArcBootDeviceName); + RtlInitAnsiString(&ArcSystemString, IopLoaderBlock->ArcHalDeviceName); + DiskCount = IoGetConfigurationInformation()->DiskCount; + + /* If no disk, return success */ + if (DiskCount == 0) + { + return STATUS_SUCCESS; + } + + /* Now, browse all disks */ + for (DiskNumber = 0; DiskNumber < DiskCount; DiskNumber++) + { + /* Create the device name */ + sprintf(Buffer, "\\Device\\Harddisk%lu\\Partition0", DiskNumber); + RtlInitAnsiString(&DeviceStringA, Buffer); + Status = RtlAnsiStringToUnicodeString(&DeviceStringW, &DeviceStringA, TRUE); + if (!NT_SUCCESS(Status)) + { + continue; + } + + /* Get its device object */ + Status = IoGetDeviceObjectPointer(&DeviceStringW, + FILE_READ_ATTRIBUTES, + &FileObject, + &DeviceObject); + RtlFreeUnicodeString(&DeviceStringW); + if (!NT_SUCCESS(Status)) + { + continue; + } + + /* Prepare for getting disk geometry */ + Irp = IoBuildDeviceIoControlRequest(IOCTL_DISK_GET_DRIVE_GEOMETRY, + DeviceObject, + NULL, + 0, + &DiskGeometry, + sizeof(DISK_GEOMETRY), + FALSE, + &Event, + &IoStatusBlock); + if (!Irp) + { + ObDereferenceObject(FileObject); + continue; + } + + /* Then, call the drive, and wait for it if needed */ + KeInitializeEvent(&Event, NotificationEvent, FALSE); + Status = IoCallDriver(DeviceObject, Irp); + if (Status == STATUS_PENDING) + { + KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL); + Status = IoStatusBlock.Status; + } + if (!NT_SUCCESS(Status)) + { + ObDereferenceObject(FileObject); + continue; + } + + /* Read partition table */ + Status = IoReadPartitionTableEx(DeviceObject, + &DriveLayout); + + /* FileObject, you can go! */ + ObDereferenceObject(FileObject); + + if (!NT_SUCCESS(Status)) + { + continue; + } + + /* Ensure we have at least 512 bytes per sector */ + if (DiskGeometry.BytesPerSector < 512) + { + DiskGeometry.BytesPerSector = 512; + } + + /* Now, for each arc disk, try to find the matching */ + for (NextEntry = ArcDiskInformation->DiskSignatureListHead.Flink; + NextEntry != &ArcDiskInformation->DiskSignatureListHead; + NextEntry = NextEntry->Flink) + { + ArcDiskSignature = CONTAINING_RECORD(NextEntry, + ARC_DISK_SIGNATURE, + ListEntry); + /* If they matches, ie + * - There's only one disk for both BIOS and detected + * - Signatures are matching + * - This is MBR + * (We don't check checksums here) + */ + if (((SingleDisk && DiskCount == 1) || + (IopVerifyDiskSignature(DriveLayout, ArcDiskSignature, &Signature))) && + (DriveLayout->PartitionStyle == PARTITION_STYLE_MBR)) + { + /* Create arc name */ + sprintf(ArcBuffer, "\\ArcName\\%s", ArcDiskSignature->ArcName); + RtlInitAnsiString(&ArcNameStringA, ArcBuffer); + + /* Browse all partitions */ + for (PartitionNumber = 1; PartitionNumber <= DriveLayout->PartitionCount; PartitionNumber++) + { + /* Create its device name */ + sprintf(Buffer, "\\Device\\Harddisk%lu\\Partition%lu", DiskNumber, PartitionNumber); + RtlInitAnsiString(&DeviceStringA, Buffer); + Status = RtlAnsiStringToUnicodeString(&DeviceStringW, &DeviceStringA, TRUE); + if (!NT_SUCCESS(Status)) + { + continue; + } + + /* If IopVerifyDiskSignature returned no signature, take the one from DriveLayout */ + if (!Signature) + { + Signature = DriveLayout->Mbr.Signature; + } + + /* Create partial arc name */ + sprintf(ArcBuffer, "%spartition(%lu)", ArcDiskSignature->ArcName, PartitionNumber); + RtlInitAnsiString(&ArcNameStringA, ArcBuffer); + + /* If it's matching boot string */ + if (RtlEqualString(&ArcNameStringA, &ArcBootString, TRUE)) + { + /* Then, fill in information about boot device */ + BootDiskInformation->BootDeviceSignature = Signature; + + /* Get its device object */ + Status = IoGetDeviceObjectPointer(&DeviceStringW, + FILE_READ_ATTRIBUTES, + &FileObject, + &DeviceObject); + if (!NT_SUCCESS(Status)) + { + RtlFreeUnicodeString(&DeviceStringW); + continue; + } + + /* And call the drive to get information about partition */ + Irp = IoBuildDeviceIoControlRequest(IOCTL_DISK_GET_PARTITION_INFO_EX, + DeviceObject, + NULL, + 0, + &PartitionInformation, + sizeof(PARTITION_INFORMATION_EX), + FALSE, + &Event, + &IoStatusBlock); + if (!Irp) + { + ObDereferenceObject(FileObject); + RtlFreeUnicodeString(&DeviceStringW); + continue; + } + + /* Call & wait if needed */ + KeInitializeEvent(&Event, NotificationEvent, FALSE); + Status = IoCallDriver(DeviceObject, Irp); + if (Status == STATUS_PENDING) + { + KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL); + Status = IoStatusBlock.Status; + } + if (!NT_SUCCESS(Status)) + { + ObDereferenceObject(FileObject); + RtlFreeUnicodeString(&DeviceStringW); + continue; + } + + /* We get partition offset as demanded and return it */ + BootDiskInformation->BootPartitionOffset = PartitionInformation.StartingOffset.QuadPart; + + /* If called passed a BOOTDISK_INFORMATION_EX structure, give more intel */ + if (IsBootDiskInfoEx) + { + /* Is PT MBR or GPT? */ + if (DriveLayout->PartitionStyle == PARTITION_STYLE_GPT) + { + ((PBOOTDISK_INFORMATION_EX)BootDiskInformation)->BootDeviceGuid = DriveLayout->Gpt.DiskId; + ((PBOOTDISK_INFORMATION_EX)BootDiskInformation)->BootDeviceIsGpt = TRUE; + } + else + { + ((PBOOTDISK_INFORMATION_EX)BootDiskInformation)->BootDeviceIsGpt = FALSE; + } + } + + /* Dereference FileObject */ + ObDereferenceObject(FileObject); + } + + /* If it's matching system string */ + if (RtlEqualString(&ArcNameStringA, &ArcSystemString, TRUE)) + { + /* Then, fill in information about the system device */ + BootDiskInformation->SystemDeviceSignature = Signature; + + /* Get its device object */ + Status = IoGetDeviceObjectPointer(&DeviceStringW, + FILE_READ_ATTRIBUTES, + &FileObject, + &DeviceObject); + if (!NT_SUCCESS(Status)) + { + RtlFreeUnicodeString(&DeviceStringW); + continue; + } + + /* And call the drive to get information about partition */ + Irp = IoBuildDeviceIoControlRequest(IOCTL_DISK_GET_PARTITION_INFO_EX, + DeviceObject, + NULL, + 0, + &PartitionInformation, + sizeof(PARTITION_INFORMATION_EX), + FALSE, + &Event, + &IoStatusBlock); + if (!Irp) + { + ObDereferenceObject(FileObject); + RtlFreeUnicodeString(&DeviceStringW); + continue; + } + + /* Call & wait if needed */ + KeInitializeEvent(&Event, NotificationEvent, FALSE); + Status = IoCallDriver(DeviceObject, Irp); + if (Status == STATUS_PENDING) + { + KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL); + Status = IoStatusBlock.Status; + } + if (!NT_SUCCESS(Status)) + { + ObDereferenceObject(FileObject); + RtlFreeUnicodeString(&DeviceStringW); + continue; + } + + /* We get partition offset as demanded and return it */ + BootDiskInformation->SystemPartitionOffset = PartitionInformation.StartingOffset.QuadPart; + + /* If called passed a BOOTDISK_INFORMATION_EX structure, give more intel */ + if (IsBootDiskInfoEx) + { + /* Is PT MBR or GPT? */ + if (DriveLayout->PartitionStyle == PARTITION_STYLE_GPT) + { + ((PBOOTDISK_INFORMATION_EX)BootDiskInformation)->SystemDeviceGuid = DriveLayout->Gpt.DiskId; + ((PBOOTDISK_INFORMATION_EX)BootDiskInformation)->SystemDeviceIsGpt = TRUE; + } + else + { + ((PBOOTDISK_INFORMATION_EX)BootDiskInformation)->SystemDeviceIsGpt = FALSE; + } + } + + /* Dereference FileObject */ + ObDereferenceObject(FileObject); + } + + /* Release device string */ + RtlFreeUnicodeString(&DeviceStringW); + } + } + } + + /* Finally, release drive layout structure */ + ExFreePool(DriveLayout); + } + + /* And return */ + return Status; } /* - * @unimplemented + * @implemented */ NTSTATUS NTAPI @@ -538,8 +2054,101 @@ IoReadDiskSignature(IN PDEVICE_OBJECT DeviceObject, IN ULONG BytesPerSector, OUT PDISK_SIGNATURE Signature) { - UNIMPLEMENTED; - return STATUS_NOT_IMPLEMENTED; + PULONG Buffer; + NTSTATUS Status; + ULONG HeaderCRC32, i, CheckSum; + PEFI_PARTITION_HEADER EFIHeader; + PPARTITION_DESCRIPTOR PartitionDescriptor; + PAGED_CODE(); + + /* Ensure we'll read at least 512 bytes */ + if (BytesPerSector < 512) + { + BytesPerSector = 512; + } + + /* Allocate a buffer for reading operations */ + Buffer = ExAllocatePoolWithTag(NonPagedPoolCacheAligned, BytesPerSector, TAG_FSTUB); + if (!Buffer) + { + return STATUS_NO_MEMORY; + } + + /* Read first sector (sector 0) for MBR */ + Status = FstubReadSector(DeviceObject, + BytesPerSector, + 0ULL, + (PUSHORT)Buffer); + if (!NT_SUCCESS(Status)) + { + goto Cleanup; + } + + /* Get the partition descriptor array */ + PartitionDescriptor = (PPARTITION_DESCRIPTOR) + &(Buffer[PARTITION_TABLE_OFFSET]); + /* Check partitions types: if first is 0xEE and all the others 0, we have GPT */ + if (PartitionDescriptor[0].PartitionType == EFI_PMBR_OSTYPE_EFI && + PartitionDescriptor[1].PartitionType == 0 && + PartitionDescriptor[2].PartitionType == 0 && + PartitionDescriptor[3].PartitionType == 0) + { + /* If we have GPT, read second sector (sector 1) for GPT header */ + Status = FstubReadSector(DeviceObject, + BytesPerSector, + 1ULL, + (PUSHORT)Buffer); + if (!NT_SUCCESS(Status)) + { + goto Cleanup; + } + EFIHeader = (PEFI_PARTITION_HEADER)Buffer; + + /* First check signature + * Then, check version (we only support v1 + * Finally check header size + */ + if (EFIHeader->Signature != EFI_HEADER_SIGNATURE || + EFIHeader->Revision != EFI_HEADER_REVISION_1 || + EFIHeader->HeaderSize != sizeof(EFI_PARTITION_HEADER)) + { + Status = STATUS_DISK_CORRUPT_ERROR; + goto Cleanup; + } + + /* Save current checksum */ + HeaderCRC32 = EFIHeader->HeaderCRC32; + /* Then zero the one in EFI header. This is needed to compute header checksum */ + EFIHeader->HeaderCRC32 = 0; + /* Compute header checksum and compare with the one present in partition table */ + if (RtlComputeCrc32(0, (PUCHAR)Buffer, sizeof(EFI_PARTITION_HEADER)) != HeaderCRC32) + { + Status = STATUS_DISK_CORRUPT_ERROR; + goto Cleanup; + } + + /* Set partition table style to GPT and return disk GUID */ + Signature->PartitionStyle = PARTITION_STYLE_GPT; + Signature->Gpt.DiskId = EFIHeader->DiskGUID; + } + else + { + /* Compute MBR checksum */ + for (i = 0, CheckSum = 0; i < 512 / sizeof(ULONG) ; i++) + { + CheckSum += Buffer[i]; + } + + /* Set partition table style to MBR and return signature (offset 440) and checksum */ + Signature->PartitionStyle = PARTITION_STYLE_MBR; + Signature->Mbr.Signature = Buffer[PARTITION_TABLE_OFFSET / 2 - 1]; + Signature->Mbr.CheckSum = CheckSum; + } + +Cleanup: + /* Free buffer and return */ + ExFreePoolWithTag(Buffer, TAG_FSTUB); + return Status; } /* @@ -610,7 +2219,7 @@ IoReadPartitionTableEx(IN PDEVICE_OBJECT DeviceObject, } /* - * @unimplemented + * @implemented */ NTSTATUS NTAPI @@ -618,32 +2227,207 @@ IoSetPartitionInformationEx(IN PDEVICE_OBJECT DeviceObject, IN ULONG PartitionNumber, IN struct _SET_PARTITION_INFORMATION_EX* PartitionInfo) { - UNIMPLEMENTED; - return STATUS_NOT_IMPLEMENTED; + NTSTATUS Status; + PDISK_INFORMATION Disk; + PARTITION_STYLE PartitionStyle; + PAGED_CODE(); + + ASSERT(DeviceObject); + ASSERT(PartitionInfo); + + /* Debug given modifications */ + FstubDbgPrintSetPartitionEx(PartitionInfo, PartitionNumber); + + /* Allocate internal structure */ + Status = FstubAllocateDiskInformation(DeviceObject, &Disk, NULL); + if (!NT_SUCCESS(Status)) + { + return Status; + } + + /* Get partition table style on disk */ + Status = FstubDetectPartitionStyle(Disk, &PartitionStyle); + if (!NT_SUCCESS(Status)) + { + FstubFreeDiskInformation(Disk); + return Status; + } + + /* If it's not matching partition style given in modifications, give up */ + if (PartitionInfo->PartitionStyle != PartitionStyle) + { + FstubFreeDiskInformation(Disk); + return STATUS_INVALID_PARAMETER; + } + + /* Finally, handle modifications using proper function */ + switch (PartitionStyle) + { + case PARTITION_STYLE_MBR: + Status = IoSetPartitionInformation(DeviceObject, + Disk->SectorSize, + PartitionNumber, + PartitionInfo->Mbr.PartitionType); + break; + case PARTITION_STYLE_GPT: + Status = FstubSetPartitionInformationEFI(Disk, + PartitionNumber, + &(PartitionInfo->Gpt)); + break; + default: + Status = STATUS_NOT_SUPPORTED; + } + + /* Release internal structure and return */ + FstubFreeDiskInformation(Disk); + return Status; } /* - * @unimplemented + * @implemented */ NTSTATUS NTAPI IoVerifyPartitionTable(IN PDEVICE_OBJECT DeviceObject, IN BOOLEAN FixErrors) { - UNIMPLEMENTED; - return STATUS_NOT_IMPLEMENTED; + NTSTATUS Status; + PDISK_INFORMATION Disk; + PARTITION_STYLE PartitionStyle; + PAGED_CODE(); + + ASSERT(DeviceObject); + + /* Allocate internal structure */ + Status = FstubAllocateDiskInformation(DeviceObject, &Disk, NULL); + if (!NT_SUCCESS(Status)) + { + return Status; + } + ASSERT(Disk); + + /* Get partition table style on disk */ + Status = FstubDetectPartitionStyle(Disk, &PartitionStyle); + if (!NT_SUCCESS(Status)) + { + FstubFreeDiskInformation(Disk); + return Status; + } + + /* Action will depend on partition style */ + switch (PartitionStyle) + { + /* For MBR, assume it's always OK */ + case PARTITION_STYLE_MBR: + Status = STATUS_SUCCESS; + break; + /* For GPT, call internal function */ + case PARTITION_STYLE_GPT: + Status = FstubVerifyPartitionTableEFI(Disk, FixErrors); + break; + /* Otherwise, signal we can't work */ + default: + Status = STATUS_NOT_SUPPORTED; + } + + /* Release internal structure and return */ + FstubFreeDiskInformation(Disk); + return Status; } /* - * @unimplemented + * @implemented */ NTSTATUS NTAPI IoWritePartitionTableEx(IN PDEVICE_OBJECT DeviceObject, - IN struct _DRIVE_LAYOUT_INFORMATION_EX* DriveLayfout) + IN struct _DRIVE_LAYOUT_INFORMATION_EX* DriveLayout) { - UNIMPLEMENTED; - return STATUS_NOT_IMPLEMENTED; + NTSTATUS Status; + PDISK_INFORMATION Disk; + ULONGLONG SectorsForPartitions; + EFI_PARTITION_HEADER EfiHeader; + PAGED_CODE(); + + ASSERT(DeviceObject); + ASSERT(DriveLayout); + + /* Debug partition table that must be written */ + FstubDbgPrintDriveLayoutEx(DriveLayout); + + /* Allocate internal structure */ + Status = FstubAllocateDiskInformation(DeviceObject, &Disk, 0); + if (!NT_SUCCESS(Status)) + { + return Status; + } + ASSERT(Disk); + + switch (DriveLayout->PartitionStyle) + { + case PARTITION_STYLE_MBR: + Status = FstubWritePartitionTableMBR(Disk, DriveLayout); + break; + + case PARTITION_STYLE_GPT: + /* Read primary table header */ + Status = FstubReadHeaderEFI(Disk, + FALSE, + &EfiHeader); + /* If it failed, try reading back table header */ + if (!NT_SUCCESS(Status)) + { + Status = FstubReadHeaderEFI(Disk, + TRUE, + &EfiHeader); + } + + /* We have a header! */ + if (NT_SUCCESS(Status)) + { + /* Check if there are enough places for the partitions to be written */ + if (DriveLayout->PartitionCount <= EfiHeader.NumberOfEntries) + { + /* Count number of sectors needed to store partitions */ + SectorsForPartitions = (EfiHeader.NumberOfEntries * PARTITION_ENTRY_SIZE) / Disk->SectorSize; + /* Set first usable LBA: Legacy MBR + GPT header + Partitions entries */ + EfiHeader.FirstUsableLBA = SectorsForPartitions + 2; + /* Set last usable LBA: Last sector - GPT header - Partitions entries */ + EfiHeader.LastUsableLBA = Disk->SectorCount - SectorsForPartitions - 1; + /* Write primary table */ + Status = FstubWritePartitionTableEFI(Disk, + EfiHeader.DiskGUID, + EfiHeader.NumberOfEntries, + EfiHeader.FirstUsableLBA, + EfiHeader.LastUsableLBA, + FALSE, + DriveLayout->PartitionCount, + DriveLayout->PartitionEntry); + /* If it succeed, also update backup table */ + if (NT_SUCCESS(Status)) + { + Status = FstubWritePartitionTableEFI(Disk, + EfiHeader.DiskGUID, + EfiHeader.NumberOfEntries, + EfiHeader.FirstUsableLBA, + EfiHeader.LastUsableLBA, + TRUE, + DriveLayout->PartitionCount, + DriveLayout->PartitionEntry); + } + } + } + break; + + default: + DPRINT("Unsupported partition style: %ld\n", DriveLayout->PartitionStyle); + Status = STATUS_NOT_SUPPORTED; + } + + /* It's over, internal structure not needed anymore */ + FstubFreeDiskInformation(Disk); + + return Status; } /* EOF */