⛏️ index : haiku.git

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