/*++ Copyright (c) 2014 Minoca Corp. All Rights Reserved Module Name: gpt.c Abstract: This module contains GPT support for the partition library. Author: Evan Green 6-Feb-2014 Environment: Any --*/ // // ------------------------------------------------------------------- Includes // #include "partlibp.h" // // ---------------------------------------------------------------- Definitions // // // ------------------------------------------------------ Data Type Definitions // // // ----------------------------------------------- Internal Function Prototypes // KSTATUS PartpGptReadEntries ( PPARTITION_CONTEXT Context, PVOID Block, PGPT_PARTITION_ENTRY *Entries, PVOID *EntriesAllocation, PULONG EntryCount, PULONG ValidCount ); KSTATUS PartpGptWriteBlocks ( PPARTITION_CONTEXT Context, ULONGLONG BlockAddress, ULONG BlockCount, PVOID Buffer ); KSTATUS PartpGptConvertPartitionTypeToGuid ( PARTITION_TYPE PartitionType, UCHAR TypeGuid[GPT_GUID_SIZE] ); BOOL PartpGptIsGuidEmpty ( UCHAR Guid[GPT_GUID_SIZE] ); BOOL PartpGptAreGuidsEqual ( UCHAR FirstGuid[GPT_GUID_SIZE], UCHAR SecondGuid[GPT_GUID_SIZE] ); // // -------------------------------------------------------------------- Globals // // // Define the mapping of known partition types. The first entry here is // assumed to be the empty GUID. // PARTITION_TYPE_GUID_MAPPING PartTypeGuidToPartitionTypeTable[] = { {GPT_PARTITION_TYPE_EMPTY, PartitionTypeEmpty}, {GPT_PARTITION_TYPE_EFI_SYSTEM, PartitionTypeEfiSystem}, {GPT_PARTITION_TYPE_MINOCA, PartitionTypeMinoca}, }; // // ------------------------------------------------------------------ Functions // BOOL PartpGptIsProtectiveMbr ( PARTITION_TABLE_ENTRY Entry[PARTITION_TABLE_SIZE] ) /*++ Routine Description: This routine determines if the given partition table is a protective MBR for a GPT disk. Arguments: Entry - Supplies the array of MBR partition table entries. Return Value: TRUE if this is a GPT disk. FALSE if this is not a GPT disk. --*/ { BOOL FoundEfiEntry; ULONG Index; FoundEfiEntry = FALSE; for (Index = 0; Index < PARTITION_TABLE_SIZE; Index += 1) { if (Entry->SystemId == PARTITION_ID_EMPTY) { continue; } if ((Entry->SystemId == PARTITION_ID_EFI_GPT) && (Entry->StartingLba == 1)) { FoundEfiEntry = TRUE; // // Anything other than empty, GPT, or EFI system partitions mean it's // not a GPT disk. // } else { return FALSE; } } return FoundEfiEntry; } KSTATUS PartpGptEnumeratePartitions ( PPARTITION_CONTEXT Context ) /*++ Routine Description: This routine is called to read the partition information from the GPT-formatted disk and enumerate the list of partitions. Arguments: Context - Supplies a pointer to the initialized context. Return Value: STATUS_SUCCESS if the partition information could be determined. There could still be zero partitions in this case. STATUS_NO_ELIGIBLE_DEVICES if the partition table is invalid. Error codes on device read or allocation failure. --*/ { ULONG AllocationSize; PVOID Block; PVOID BlockAllocation; ULONG BlockSize; ULONG EntryCount; ULONG EntryIndex; PGPT_PARTITION_ENTRY GptEntry; ULONGLONG GptHeaderLba; PGPT_HEADER Header; PPARTITION_INFORMATION Information; ULONG InformationIndex; PPARTITION_INFORMATION Partition; PGPT_PARTITION_ENTRY PartitionEntries; PVOID PartitionEntriesAllocation; KSTATUS Status; ULONG ValidCount; BlockSize = Context->BlockSize; Information = NULL; PartitionEntriesAllocation = NULL; BlockAllocation = PartpAllocateIo(Context, BlockSize, &Block); if (BlockAllocation == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto GptEnumeratePartitionsEnd; } // // Read LBA 1 to get the GPT header. // GptHeaderLba = 1; Status = Context->ReadFunction(Context, GptHeaderLba, Block); if (!KSUCCESS(Status)) { goto GptEnumeratePartitionsEnd; } // // Validate the header here. If it wasn't valid, try the header that's at // the last sector of the disk. // Status = PartpGptReadEntries(Context, Block, &PartitionEntries, &PartitionEntriesAllocation, &EntryCount, &ValidCount); if ((!KSUCCESS(Status)) && (Context->BlockCount != 0)) { GptHeaderLba = Context->BlockCount - 1; Status = Context->ReadFunction(Context, GptHeaderLba, Block); if (!KSUCCESS(Status)) { goto GptEnumeratePartitionsEnd; } Status = PartpGptReadEntries(Context, Block, &PartitionEntries, &PartitionEntriesAllocation, &EntryCount, &ValidCount); } // // If neither header is valid, abort. // if (!KSUCCESS(Status)) { goto GptEnumeratePartitionsEnd; } Header = (PGPT_HEADER)Block; ASSERT(sizeof(Context->DiskIdentifier) >= sizeof(Header->DiskGuid)); RtlCopyMemory(Context->DiskIdentifier, Header->DiskGuid, sizeof(Context->DiskIdentifier)); if (ValidCount == 0) { Status = STATUS_SUCCESS; goto GptEnumeratePartitionsEnd; } // // Allocate space for the partition information structures. // AllocationSize = sizeof(PARTITION_INFORMATION) * ValidCount; Information = Context->AllocateFunction(AllocationSize); if (Information == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto GptEnumeratePartitionsEnd; } RtlZeroMemory(Information, AllocationSize); // // Loop through and convert all the GPT entries to partition information // structures. // InformationIndex = 0; for (EntryIndex = 0; EntryIndex < EntryCount; EntryIndex += 1) { GptEntry = &(PartitionEntries[EntryIndex]); if ((GptEntry->FirstLba == 0) || (GptEntry->LastLba == 0)) { continue; } if (PartpGptIsGuidEmpty(GptEntry->TypeGuid) != FALSE) { continue; } ASSERT(InformationIndex < ValidCount); Partition = &(Information[InformationIndex]); Partition->StartOffset = GptEntry->FirstLba; Partition->EndOffset = GptEntry->LastLba + 1; Partition->Number = EntryIndex + 1; ASSERT(PARTITION_IDENTIFIER_SIZE >= GPT_GUID_SIZE); RtlCopyMemory(Partition->TypeIdentifier, GptEntry->TypeGuid, sizeof(Partition->TypeIdentifier)); RtlCopyMemory(Partition->Identifier, GptEntry->Guid, sizeof(Partition->TypeIdentifier)); Partition->PartitionType = PartpGptConvertTypeGuidToPartitionType(GptEntry->TypeGuid); InformationIndex += 1; } ASSERT(InformationIndex == ValidCount); Context->Partitions = Information; Context->PartitionCount = InformationIndex; Information = NULL; Status = STATUS_SUCCESS; GptEnumeratePartitionsEnd: if (PartitionEntriesAllocation != NULL) { Context->FreeFunction(PartitionEntriesAllocation); } if (Information != NULL) { Context->FreeFunction(Information); } if (BlockAllocation != NULL) { Context->FreeFunction(BlockAllocation); } return Status; } KSTATUS PartpGptWritePartitionLayout ( PPARTITION_CONTEXT Context, PPARTITION_INFORMATION Partitions, ULONG PartitionCount, BOOL CleanMbr ) /*++ Routine Description: This routine writes a GPT partition layout to the disk. This usually wipes out all data on the disk. Arguments: Context - Supplies a pointer to the partition context. Partitions - Supplies a pointer to the new partition layout. PartitionCount - Supplies the number of partitions in the new layout. CleanMbr - Supplies a boolean indicating if only the partition entries of the MBR should be modified (FALSE) or if the whole MBR should be zeroed before being written (TRUE). Return Value: Status code. --*/ { ULONG BlockSize; ULONG EntriesBlockCount; ULONG EntriesSize; ULONG EntryCount; ULONGLONG FirstUsableBlock; PGPT_PARTITION_ENTRY GptEntries; PGPT_PARTITION_ENTRY GptEntry; PGPT_HEADER Header; ULONGLONG LastUsableBlock; PPARTITION_TABLE_ENTRY MbrEntry; PPARTITION_INFORMATION Partition; ULONG PartitionIndex; PUSHORT Pointer16; KSTATUS Status; PVOID Table; PVOID TableAllocation; TableAllocation = NULL; if (Context->BlockCount < 12) { Status = STATUS_INVALID_CONFIGURATION; goto GptWritePartitionLayoutEnd; } if (Context->FillRandomFunction == NULL) { Status = STATUS_NOT_INITIALIZED; goto GptWritePartitionLayoutEnd; } BlockSize = Context->BlockSize; ASSERT(POWER_OF_2(BlockSize)); EntriesSize = PartitionCount * sizeof(GPT_PARTITION_ENTRY); if (EntriesSize < GPT_MINIMUM_PARTITION_ENTRIES_SIZE) { EntriesSize = GPT_MINIMUM_PARTITION_ENTRIES_SIZE; } EntriesSize = ALIGN_RANGE_UP(EntriesSize, BlockSize); EntriesBlockCount = (EntriesSize / BlockSize); EntryCount = EntriesSize / sizeof(GPT_PARTITION_ENTRY); // // Make sure the first usable block is aligned to 4KB so that disks that // internally use 4KB sectors but report 512 byte sectors don't have // performance issues doing read/modify/writes all the time within the // partition. Remember that the first two blocks are reserved for the // protective MBR and the GPT header. // FirstUsableBlock = 2 + EntriesBlockCount; FirstUsableBlock = ALIGN_RANGE_UP(FirstUsableBlock * BlockSize, GPT_PARTITION_ALIGNMENT); FirstUsableBlock /= BlockSize; // // Allocate space for the entire table. // TableAllocation = PartpAllocateIo(Context, FirstUsableBlock * BlockSize, &Table); if (TableAllocation == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto GptWritePartitionLayoutEnd; } RtlZeroMemory(Table, FirstUsableBlock * BlockSize); Header = (PGPT_HEADER)(Table + BlockSize); LastUsableBlock = (Context->BlockCount - 1) - (EntriesBlockCount + 1); // // First fill out the partition entries array. // GptEntries = (PGPT_PARTITION_ENTRY)(Table + (2 * BlockSize)); for (PartitionIndex = 0; PartitionIndex < PartitionCount; PartitionIndex += 1) { GptEntry = &(GptEntries[PartitionIndex]); Partition = &(Partitions[PartitionIndex]); // // Convert the type enum if it's set, otherwise, just copy the type // identifier GUID. // if ((Partition->PartitionType != PartitionTypeInvalid) && (Partition->PartitionType != PartitionTypeUnknown)) { Status = PartpGptConvertPartitionTypeToGuid( Partition->PartitionType, GptEntry->TypeGuid); if (!KSUCCESS(Status)) { goto GptWritePartitionLayoutEnd; } } else { ASSERT(sizeof(GptEntry->TypeGuid) <= sizeof(Partition->TypeIdentifier)); RtlCopyMemory(GptEntry->TypeGuid, Partition->TypeIdentifier, sizeof(GptEntry->TypeGuid)); } // // Copy the GUID if it's not the empty one. If it is, create a random // one. // if (PartpGptIsGuidEmpty(Partition->Identifier) != FALSE) { Context->FillRandomFunction(Context, GptEntry->Guid, sizeof(GptEntry->Guid)); } else { ASSERT(sizeof(GptEntry->Guid) <= sizeof(Partition->Identifier)); RtlCopyMemory(GptEntry->Guid, Partition->Identifier, sizeof(GptEntry->Guid)); } GptEntry->Attributes = Partition->Attributes; GptEntry->FirstLba = Partition->StartOffset; if (Partition->EndOffset != 0) { GptEntry->LastLba = Partition->EndOffset - 1; } if ((GptEntry->FirstLba != GptEntry->LastLba) && ((GptEntry->FirstLba < FirstUsableBlock) || (GptEntry->FirstLba > LastUsableBlock) || (GptEntry->LastLba < FirstUsableBlock) || (GptEntry->LastLba > LastUsableBlock))) { Status = STATUS_INVALID_CONFIGURATION; goto GptWritePartitionLayoutEnd; } } // // Create the backup copy first as requested by the specification. // Header->Signature = GPT_HEADER_SIGNATURE; Header->Revision = GPT_HEADER_REVISION_1; Header->HeaderSize = sizeof(GPT_HEADER); Header->CurrentLba = Context->BlockCount - 1; Header->BackupLba = 1; Header->FirstUsableLba = FirstUsableBlock; Header->LastUsableLba = LastUsableBlock; if (PartpGptIsGuidEmpty(Context->DiskIdentifier) != FALSE) { Context->FillRandomFunction(Context, Header->DiskGuid, sizeof(Header->DiskGuid)); } else { RtlCopyMemory(Header->DiskGuid, Context->DiskIdentifier, sizeof(Header->DiskGuid)); } Header->PartitionEntriesLba = Header->CurrentLba - EntriesBlockCount; Header->PartitionEntryCount = EntryCount; Header->PartitionEntrySize = sizeof(GPT_PARTITION_ENTRY); Header->PartitionArrayCrc32 = RtlComputeCrc32( 0, GptEntries, EntryCount * sizeof(GPT_PARTITION_ENTRY)); Header->HeaderCrc32 = RtlComputeCrc32(0, Header, Header->HeaderSize); // // Write out the header, then the partition entries array. // Status = PartpGptWriteBlocks(Context, Header->CurrentLba, 1, Header); if (!KSUCCESS(Status)) { goto GptWritePartitionLayoutEnd; } Status = PartpGptWriteBlocks(Context, Header->PartitionEntriesLba, EntriesBlockCount, GptEntries); if (!KSUCCESS(Status)) { goto GptWritePartitionLayoutEnd; } // // Create the protective MBR. // Pointer16 = (PUSHORT)(Table + PARTITION_SIGNATURE_OFFSET); *Pointer16 = PARTITION_SIGNATURE; MbrEntry = (PPARTITION_TABLE_ENTRY)(Table + PARTITION_TABLE_OFFSET); MbrEntry->StartingSector = 1; MbrEntry->SystemId = PARTITION_ID_EFI_GPT; MbrEntry->EndingHead = 0xFE; MbrEntry->EndingSector = 0xFF; MbrEntry->EndingCylinder = 0xFF; MbrEntry->StartingLba = 1; if (Context->BlockCount - 1 > MAX_ULONG) { MbrEntry->SectorCount = MAX_ULONG; } else { MbrEntry->SectorCount = Context->BlockCount - 1; } // // Fix up the GPT header for the beginning of the disk. // Header->HeaderCrc32 = 0; Header->BackupLba = Header->CurrentLba; Header->CurrentLba = 1; Header->PartitionEntriesLba = 2; Header->HeaderCrc32 = RtlComputeCrc32(0, Header, Header->HeaderSize); // // Finally, write out the MBR, GPT header, and entries all at once. // Status = PartpGptWriteBlocks(Context, 0, FirstUsableBlock, Table); if (!KSUCCESS(Status)) { goto GptWritePartitionLayoutEnd; } GptWritePartitionLayoutEnd: if (TableAllocation != NULL) { Context->FreeFunction(TableAllocation); } return Status; } PARTITION_TYPE PartpGptConvertTypeGuidToPartitionType ( UCHAR TypeGuid[GPT_GUID_SIZE] ) /*++ Routine Description: This routine converts a partition type GUID into a partition type to the best of its abilities. Arguments: TypeGuid - Supplies a pointer to the partition type GUID. Return Value: Returns a partition type for this type GUID. --*/ { ULONG EntryCount; ULONG EntryIndex; BOOL Match; EntryCount = sizeof(PartTypeGuidToPartitionTypeTable) / sizeof(PartTypeGuidToPartitionTypeTable[0]); for (EntryIndex = 0; EntryIndex < EntryCount; EntryIndex += 1) { Match = PartpGptAreGuidsEqual( PartTypeGuidToPartitionTypeTable[EntryIndex].TypeGuid, TypeGuid); if (Match != FALSE) { return PartTypeGuidToPartitionTypeTable[EntryIndex].PartitionType; } } return PartitionTypeUnknown; } // // --------------------------------------------------------- Internal Functions // KSTATUS PartpGptReadEntries ( PPARTITION_CONTEXT Context, PVOID Block, PGPT_PARTITION_ENTRY *Entries, PVOID *EntriesAllocation, PULONG EntryCount, PULONG ValidCount ) /*++ Routine Description: This routine determines if the GPT header is valid, and reads the partition entries if so. Arguments: Context - Supplies a pointer to the initialized context. Block - Supplies a pointer to the block containing the GPT header. Entries - Supplies a pointer where a pointer will be returned to the GPT partition entries on success. EntriesAllocation - Supplies a pointer where a pointer to the actual entries allocation will be returned. This is what should be passed to the free function. EntryCount - Supplies a pointer where the number of GPT entries in the array will be returned. ValidCount - Supplies a pointer where the number of non-empty partitions will be returned on success. Return Value: STATUS_SUCCESS if the partition information could be determined. There could still be zero partitions in this case. STATUS_NO_ELIGIBLE_DEVICES if the header is invalid. Other errors on read or allocation failures. --*/ { ULONG AllocationSize; ULONG BlockCount; ULONG BlockIndex; ULONG BlockSize; ULONG ComputedCrc; PGPT_PARTITION_ENTRY Entry; ULONG EntryIndex; PGPT_HEADER Header; ULONG HeaderCrc; PGPT_PARTITION_ENTRY PartitionEntries; PVOID PartitionEntriesAllocation; ULONG PartitionEntryCount; KSTATUS Status; ULONG ValidPartitionCount; Status = STATUS_NO_ELIGIBLE_DEVICES; BlockSize = Context->BlockSize; Header = (PGPT_HEADER)Block; PartitionEntriesAllocation = NULL; PartitionEntryCount = 0; ValidPartitionCount = 0; if (Header->Signature != GPT_HEADER_SIGNATURE) { goto GptValidateHeaderEnd; } if (Header->Revision < GPT_HEADER_REVISION_1) { goto GptValidateHeaderEnd; } // // Validate that the reported sizes of the header and partition entry are // reasonable. // if ((Header->HeaderSize < sizeof(GPT_HEADER)) || (Header->HeaderSize > Context->BlockSize)) { goto GptValidateHeaderEnd; } if ((Header->PartitionEntrySize < sizeof(GPT_PARTITION_ENTRY)) || (Header->PartitionEntrySize > Context->BlockSize)) { goto GptValidateHeaderEnd; } // // Validate that the partition entries live outside the usable data. // if ((Header->PartitionEntriesLba >= Header->FirstUsableLba) && (Header->PartitionEntriesLba <= Header->LastUsableLba)) { goto GptValidateHeaderEnd; } HeaderCrc = Header->HeaderCrc32; Header->HeaderCrc32 = 0; ComputedCrc = RtlComputeCrc32(0, Header, Header->HeaderSize); Header->HeaderCrc32 = HeaderCrc; if (ComputedCrc != HeaderCrc) { goto GptValidateHeaderEnd; } // // Allocate space for the partition entries. // PartitionEntryCount = Header->PartitionEntryCount; AllocationSize = PartitionEntryCount * Header->PartitionEntrySize; if (AllocationSize == 0) { Status = STATUS_SUCCESS; goto GptValidateHeaderEnd; } AllocationSize = ALIGN_RANGE_UP(AllocationSize, BlockSize); PartitionEntriesAllocation = PartpAllocateIo(Context, AllocationSize, (PVOID *)&PartitionEntries); if (PartitionEntriesAllocation == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto GptValidateHeaderEnd; } BlockCount = AllocationSize / BlockSize; for (BlockIndex = 0; BlockIndex < BlockCount; BlockIndex += 1) { Status = Context->ReadFunction( Context, Header->PartitionEntriesLba + BlockIndex, (PUCHAR)PartitionEntries + (BlockIndex * BlockSize)); if (!KSUCCESS(Status)) { goto GptValidateHeaderEnd; } } // // Validate the CRC for the partition entries. // ComputedCrc = RtlComputeCrc32( 0, PartitionEntries, Header->PartitionEntryCount * Header->PartitionEntrySize); if (ComputedCrc != Header->PartitionArrayCrc32) { Status = STATUS_NO_ELIGIBLE_DEVICES; goto GptValidateHeaderEnd; } // // Loop through and count the valid entries. // for (EntryIndex = 0; EntryIndex < PartitionEntryCount; EntryIndex += 1) { Entry = &(PartitionEntries[EntryIndex]); if ((Entry->FirstLba == 0) || (Entry->LastLba == 0)) { continue; } if (PartpGptIsGuidEmpty(Entry->TypeGuid) != FALSE) { continue; } ValidPartitionCount += 1; } Status = STATUS_SUCCESS; GptValidateHeaderEnd: if (!KSUCCESS(Status)) { if (PartitionEntriesAllocation != NULL) { Context->FreeFunction(PartitionEntriesAllocation); PartitionEntries = NULL; PartitionEntriesAllocation = NULL; } PartitionEntryCount = 0; ValidPartitionCount = 0; } *Entries = PartitionEntries; *EntriesAllocation = PartitionEntriesAllocation; *EntryCount = PartitionEntryCount; *ValidCount = ValidPartitionCount; return Status; } KSTATUS PartpGptWriteBlocks ( PPARTITION_CONTEXT Context, ULONGLONG BlockAddress, ULONG BlockCount, PVOID Buffer ) /*++ Routine Description: This routine writes multiple blocks to the disk. Arguments: Context - Supplies a pointer to the partition context. BlockAddress - Supplies the block address to write to. BlockCount - Supplies the number of blocks to write. Buffer - Supplies a pointer to the buffer containing the data to write. Return Value: Status code. --*/ { ULONG BlockIndex; KSTATUS Status; Status = STATUS_SUCCESS; for (BlockIndex = 0; BlockIndex < BlockCount; BlockIndex += 1) { Status = Context->WriteFunction(Context, BlockAddress, Buffer); if (!KSUCCESS(Status)) { goto GptWriteBlocksEnd; } Buffer += Context->BlockSize; BlockAddress += 1; } GptWriteBlocksEnd: return Status; } KSTATUS PartpGptConvertPartitionTypeToGuid ( PARTITION_TYPE PartitionType, UCHAR TypeGuid[GPT_GUID_SIZE] ) /*++ Routine Description: This routine converts a partition type enum into its corresponding GPT GUID. Arguments: PartitionType - Supplies the partition type. TypeGuid - Supplies a pointer where the GUID will be returned. Return Value: STATUS_SUCCESS on success. STATUS_INVALID_PARAMETER if the partition type could not be converted. --*/ { ULONG EntryCount; ULONG EntryIndex; EntryCount = sizeof(PartTypeGuidToPartitionTypeTable) / sizeof(PartTypeGuidToPartitionTypeTable[0]); for (EntryIndex = 0; EntryIndex < EntryCount; EntryIndex += 1) { if (PartTypeGuidToPartitionTypeTable[EntryIndex].PartitionType == PartitionType) { RtlCopyMemory(TypeGuid, PartTypeGuidToPartitionTypeTable[EntryIndex].TypeGuid, GPT_GUID_SIZE); return STATUS_SUCCESS; } } return STATUS_INVALID_PARAMETER; } BOOL PartpGptIsGuidEmpty ( UCHAR Guid[GPT_GUID_SIZE] ) /*++ Routine Description: This routine determines if the given GUID is all zero. Arguments: Guid - Supplies the guid to compare. Return Value: TRUE if the GUID is all zero. FALSE if the GUID has something in there. --*/ { BOOL Match; // // The first entry is known to be the empty entry. // Match = PartpGptAreGuidsEqual(Guid, PartTypeGuidToPartitionTypeTable[0].TypeGuid); return Match; } BOOL PartpGptAreGuidsEqual ( UCHAR FirstGuid[GPT_GUID_SIZE], UCHAR SecondGuid[GPT_GUID_SIZE] ) /*++ Routine Description: This routine compares two GPT GUIDs. Arguments: FirstGuid - Supplies the first GUID to compare. SecondGuid - Supplies the second GUID to compare. Return Value: TRUE if the GUIDs are equal. FALSE if the GUIDs are not equal. --*/ { ULONG Index; for (Index = 0; Index < GPT_GUID_SIZE; Index += 1) { if (FirstGuid[Index] != SecondGuid[Index]) { return FALSE; } } return TRUE; }