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 */