/* * Copyright 2007-2013, Axel Dörfler, axeld@pinc-software.de. * Copyright 2009, Michael Lotz, mmlr@mlotz.ch. All rights reserved. * * Distributed under the terms of the MIT License. */ #include "Header.h" #include #include #include #include #ifdef _KERNEL_MODE # include #else # include #endif #ifndef _BOOT_MODE #include "PartitionMap.h" #include "PartitionMapWriter.h" #endif #if !defined(_BOOT_MODE) && !defined(_USER_MODE) #include "uuid.h" #endif #include "crc32.h" #include "utility.h" #define TRACE_EFI_GPT #ifdef TRACE_EFI_GPT # ifndef _KERNEL_MODE # define dprintf printf # endif # define TRACE(x) dprintf x #else # define TRACE(x) ; #endif namespace EFI { Header::Header(int fd, uint64 lastBlock, uint32 blockSize) : fBlockSize(blockSize), fStatus(B_NO_INIT), fEntries(NULL), fDirty(false) { // TODO: check the correctness of the protective MBR and warn if invalid // Read and check the partition table header fStatus = _Read(fd, (uint64)EFI_HEADER_LOCATION * blockSize, &fHeader, sizeof(gpt_table_header)); if (fStatus == B_OK) { if (!_IsHeaderValid(fHeader, EFI_HEADER_LOCATION)) fStatus = B_BAD_DATA; } if (fStatus == B_OK && lastBlock != fHeader.AlternateBlock()) { dprintf("gpt: alternate header not in last block (%" B_PRIu64 " vs. %" B_PRIu64 ")\n", fHeader.AlternateBlock(), lastBlock); lastBlock = fHeader.AlternateBlock(); } // Read backup header, too status_t status = _Read(fd, lastBlock * blockSize, &fBackupHeader, sizeof(gpt_table_header)); if (status == B_OK) { if (!_IsHeaderValid(fBackupHeader, lastBlock)) status = B_BAD_DATA; } // If both headers are invalid, bail out -- this is probably not a GPT disk if (status != B_OK && fStatus != B_OK) return; if (fStatus != B_OK) { // Recreate primary header from the backup fHeader = fBackupHeader; fHeader.SetAbsoluteBlock(EFI_HEADER_LOCATION); fHeader.SetEntriesBlock(EFI_PARTITION_ENTRIES_BLOCK); fHeader.SetAlternateBlock(lastBlock); fDirty = true; } else if (status != B_OK) { // Recreate backup header from primary _SetBackupHeaderFromPrimary(lastBlock); } // allocate, read, and check partition entry array fEntries = new (std::nothrow) uint8[_EntryArraySize()]; if (fEntries == NULL) { // TODO: if there cannot be allocated enough (ie. the boot loader's // heap is limited), try a smaller size before failing fStatus = B_NO_MEMORY; return; } fStatus = _Read(fd, fHeader.EntriesBlock() * blockSize, fEntries, _EntryArraySize()); if (fStatus != B_OK || !_ValidateEntriesCRC()) { // Read backup entries instead fStatus = _Read(fd, fBackupHeader.EntriesBlock() * blockSize, fEntries, _EntryArraySize()); if (fStatus != B_OK) return; if (!_ValidateEntriesCRC()) { fStatus = B_BAD_DATA; return; } } // TODO: check overlapping or out of range partitions #ifdef TRACE_EFI_GPT _Dump(fHeader); _Dump(fBackupHeader); _DumpPartitions(); #endif fStatus = B_OK; } #if !defined(_BOOT_MODE) && !defined(_USER_MODE) Header::Header(uint64 lastBlock, uint32 blockSize) : fBlockSize(blockSize), fStatus(B_NO_INIT), fEntries(NULL), fDirty(true) { TRACE(("EFI::Header: Initialize GPT, block size %" B_PRIu32 "\n", blockSize)); // Initialize to an empty header memcpy(fHeader.header, EFI_PARTITION_HEADER, sizeof(fHeader.header)); fHeader.SetRevision(EFI_TABLE_REVISION); fHeader.SetHeaderSize(sizeof(fHeader)); fHeader.SetHeaderCRC(0); fHeader.SetAbsoluteBlock(EFI_HEADER_LOCATION); fHeader.SetAlternateBlock(lastBlock); uuid_t uuid; uuid_generate_random(uuid); memcpy((uint8*)&fHeader.disk_guid, uuid, sizeof(guid_t)); fHeader.SetEntriesBlock(EFI_PARTITION_ENTRIES_BLOCK); fHeader.SetEntryCount(EFI_PARTITION_ENTRY_COUNT); fHeader.SetEntrySize(EFI_PARTITION_ENTRY_SIZE); fHeader.SetEntriesCRC(0); size_t arraySize = _EntryArraySize(); fEntries = new (std::nothrow) uint8[arraySize]; if (fEntries == NULL) { fStatus = B_NO_MEMORY; return; } memset(fEntries, 0, arraySize); // TODO: initialize the entry guids uint32 entryBlocks = (arraySize + fBlockSize - 1) / fBlockSize; fHeader.SetFirstUsableBlock(EFI_PARTITION_ENTRIES_BLOCK + entryBlocks); fHeader.SetLastUsableBlock(lastBlock - 1 - entryBlocks); _SetBackupHeaderFromPrimary(lastBlock); #ifdef TRACE_EFI_GPT _Dump(fHeader); _DumpPartitions(); #endif fStatus = B_OK; } #endif // !_BOOT_MODE && !_USER_MODE Header::~Header() { delete[] fEntries; } status_t Header::InitCheck() const { return fStatus; } bool Header::IsDirty() const { return fDirty; } #ifndef _BOOT_MODE status_t Header::WriteEntry(int fd, uint32 entryIndex) { off_t entryOffset = entryIndex * fHeader.EntrySize(); status_t status = _Write(fd, fHeader.EntriesBlock() * fBlockSize + entryOffset, fEntries + entryOffset, fHeader.EntrySize()); if (status != B_OK) return status; // Update header, too -- the entries CRC changed status = _WriteHeader(fd); // Write backup status_t backupStatus = _Write(fd, fBackupHeader.EntriesBlock() * fBlockSize + entryOffset, fEntries + entryOffset, fHeader.EntrySize()); if (status == B_OK && backupStatus == B_OK) fDirty = false; return status == B_OK ? backupStatus : status; } status_t Header::Write(int fd) { // Try to write the protective MBR PartitionMap partitionMap; PrimaryPartition *partition = NULL; uint32 index = 0; while ((partition = partitionMap.PrimaryPartitionAt(index)) != NULL) { if (index == 0) { uint64 deviceSize = fHeader.AlternateBlock() * fBlockSize; partition->SetTo(fBlockSize, deviceSize, 0xEE, false, fBlockSize); } else partition->Unset(); ++index; } PartitionMapWriter writer(fd, fBlockSize); writer.WriteMBR(&partitionMap, true); // We also write the bootcode, so we can boot GPT disks from BIOS status_t status = _Write(fd, fHeader.EntriesBlock() * fBlockSize, fEntries, _EntryArraySize()); if (status != B_OK) return status; // First write the header, so that we have at least one completely correct // data set status = _WriteHeader(fd); // Write backup entries status_t backupStatus = _Write(fd, fBackupHeader.EntriesBlock() * fBlockSize, fEntries, _EntryArraySize()); if (status == B_OK && backupStatus == B_OK) fDirty = false; return status == B_OK ? backupStatus : status; } status_t Header::_WriteHeader(int fd) { _UpdateCRC(); status_t status = _Write(fd, fHeader.AbsoluteBlock() * fBlockSize, &fHeader, sizeof(gpt_table_header)); if (status != B_OK) return status; return _Write(fd, fBackupHeader.AbsoluteBlock() * fBlockSize, &fBackupHeader, sizeof(gpt_table_header)); } status_t Header::_Write(int fd, off_t offset, const void* data, size_t size) const { ssize_t bytesWritten = write_pos(fd, offset, data, size); if (bytesWritten < 0) return bytesWritten; if (bytesWritten != (ssize_t)size) return B_IO_ERROR; return B_OK; } void Header::_UpdateCRC() { _UpdateCRC(fHeader); _UpdateCRC(fBackupHeader); } void Header::_UpdateCRC(gpt_table_header& header) { header.SetEntriesCRC(crc32(fEntries, _EntryArraySize())); header.SetHeaderCRC(0); header.SetHeaderCRC(crc32((uint8*)&header, sizeof(gpt_table_header))); } #endif // !_BOOT_MODE status_t Header::_Read(int fd, off_t offset, void* data, size_t size) const { ssize_t bytesRead = read_pos(fd, offset, data, size); if (bytesRead < 0) return bytesRead; if (bytesRead != (ssize_t)size) return B_IO_ERROR; return B_OK; } bool Header::_IsHeaderValid(gpt_table_header& header, uint64 block) { return !memcmp(header.header, EFI_PARTITION_HEADER, sizeof(header.header)) && _ValidateHeaderCRC(header) && header.AbsoluteBlock() == block; } bool Header::_ValidateHeaderCRC(gpt_table_header& header) { uint32 originalCRC = header.HeaderCRC(); header.SetHeaderCRC(0); bool matches = originalCRC == crc32((const uint8*)&header, sizeof(gpt_table_header)); header.SetHeaderCRC(originalCRC); return matches; } bool Header::_ValidateEntriesCRC() const { return fHeader.EntriesCRC() == crc32(fEntries, _EntryArraySize()); } void Header::_SetBackupHeaderFromPrimary(uint64 lastBlock) { fBackupHeader = fHeader; fBackupHeader.SetAbsoluteBlock(lastBlock); fBackupHeader.SetEntriesBlock( lastBlock - _EntryArraySize() / fBlockSize); fBackupHeader.SetAlternateBlock(1); } #ifdef TRACE_EFI_GPT const char * Header::_PrintGUID(const guid_t &id) { static char guid[48]; snprintf(guid, sizeof(guid), "%08" B_PRIx32 "-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", B_LENDIAN_TO_HOST_INT32(id.data1), B_LENDIAN_TO_HOST_INT16(id.data2), B_LENDIAN_TO_HOST_INT16(id.data3), id.data4[0], id.data4[1], id.data4[2], id.data4[3], id.data4[4], id.data4[5], id.data4[6], id.data4[7]); return guid; } void Header::_Dump(const gpt_table_header& header) { dprintf("EFI header: %.8s\n", header.header); dprintf("EFI revision: %" B_PRIx32 "\n", header.Revision()); dprintf("header size: %" B_PRId32 "\n", header.HeaderSize()); dprintf("header CRC: %" B_PRIx32 "\n", header.HeaderCRC()); dprintf("absolute block: %" B_PRIu64 "\n", header.AbsoluteBlock()); dprintf("alternate block: %" B_PRIu64 "\n", header.AlternateBlock()); dprintf("first usable block: %" B_PRIu64 "\n", header.FirstUsableBlock()); dprintf("last usable block: %" B_PRIu64 "\n", header.LastUsableBlock()); dprintf("disk GUID: %s\n", _PrintGUID(header.disk_guid)); dprintf("entries block: %" B_PRIu64 "\n", header.EntriesBlock()); dprintf("entry size: %" B_PRIu32 "\n", header.EntrySize()); dprintf("entry count: %" B_PRIu32 "\n", header.EntryCount()); dprintf("entries CRC: %" B_PRIx32 "\n", header.EntriesCRC()); } void Header::_DumpPartitions() { for (uint32 i = 0; i < EntryCount(); i++) { const gpt_partition_entry &entry = EntryAt(i); if (entry.partition_type == kEmptyGUID) continue; dprintf("[%3" B_PRIu32 "] partition type: %s\n", i, _PrintGUID(entry.partition_type)); dprintf(" unique id: %s\n", _PrintGUID(entry.unique_guid)); dprintf(" start block: %" B_PRIu64 "\n", entry.StartBlock()); dprintf(" end block: %" B_PRIu64 "\n", entry.EndBlock()); dprintf(" size: %g MB\n", (entry.EndBlock() - entry.StartBlock()) * 512 / 1024.0 / 1024.0); dprintf(" attributes: %" B_PRIx64 "\n", entry.Attributes()); char name[64]; to_utf8(entry.name, EFI_PARTITION_NAME_LENGTH, name, sizeof(name)); dprintf(" name: %s\n", name); } } #endif // TRACE_EFI_GPT } // namespace EFI