* 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 <unistd.h>
#include <stdio.h>
#include <string.h>
#include <KernelExport.h>
#ifdef _KERNEL_MODE
# include <util/kernel_cpp.h>
#else
# include <new>
#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)
{
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();
}
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 (status != B_OK && fStatus != B_OK)
return;
if (fStatus != B_OK) {
fHeader = fBackupHeader;
fHeader.SetAbsoluteBlock(EFI_HEADER_LOCATION);
fHeader.SetEntriesBlock(EFI_PARTITION_ENTRIES_BLOCK);
fHeader.SetAlternateBlock(lastBlock);
fDirty = true;
} else if (status != B_OK) {
_SetBackupHeaderFromPrimary(lastBlock);
}
fEntries = new (std::nothrow) uint8[_EntryArraySize()];
if (fEntries == NULL) {
fStatus = B_NO_MEMORY;
return;
}
fStatus = _Read(fd, fHeader.EntriesBlock() * blockSize,
fEntries, _EntryArraySize());
if (fStatus != B_OK || !_ValidateEntriesCRC()) {
fStatus = _Read(fd, fBackupHeader.EntriesBlock() * blockSize,
fEntries, _EntryArraySize());
if (fStatus != B_OK)
return;
if (!_ValidateEntriesCRC()) {
fStatus = B_BAD_DATA;
return;
}
}
#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));
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);
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
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;
status = _WriteHeader(fd);
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)
{
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);
status_t status = _Write(fd, fHeader.EntriesBlock() * fBlockSize, fEntries,
_EntryArraySize());
if (status != B_OK)
return status;
status = _WriteHeader(fd);
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
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
}