1/*
2 * Copyright 2007-2013, Axel D��rfler, axeld@pinc-software.de.
3 * Copyright 2009, Michael Lotz, mmlr@mlotz.ch. All rights reserved.
4 *
5 * Distributed under the terms of the MIT License.
6 */
7
8
9#include "Header.h"
10
11#include <unistd.h>
12#include <stdio.h>
13#include <string.h>
14
15#include <KernelExport.h>
16
17#ifdef _KERNEL_MODE
18#	include <util/kernel_cpp.h>
19#else
20#	include <new>
21#endif
22
23#ifndef _BOOT_MODE
24#include "PartitionMap.h"
25#include "PartitionMapWriter.h"
26#endif
27
28#if !defined(_BOOT_MODE) && !defined(_USER_MODE)
29#include "uuid.h"
30#endif
31
32#include "crc32.h"
33#include "utility.h"
34
35
36#define TRACE_EFI_GPT
37#ifdef TRACE_EFI_GPT
38#	ifndef _KERNEL_MODE
39#		define dprintf printf
40#	endif
41#	define TRACE(x) dprintf x
42#else
43#	define TRACE(x) ;
44#endif
45
46
47namespace EFI {
48
49
50Header::Header(int fd, uint64 lastBlock, uint32 blockSize)
51	:
52	fBlockSize(blockSize),
53	fStatus(B_NO_INIT),
54	fEntries(NULL),
55	fDirty(false)
56{
57	// TODO: check the correctness of the protective MBR and warn if invalid
58
59	// Read and check the partition table header
60
61	fStatus = _Read(fd, (uint64)EFI_HEADER_LOCATION * blockSize,
62		&fHeader, sizeof(gpt_table_header));
63	if (fStatus == B_OK) {
64		if (!_IsHeaderValid(fHeader, EFI_HEADER_LOCATION))
65			fStatus = B_BAD_DATA;
66	}
67
68	if (fStatus == B_OK && lastBlock != fHeader.AlternateBlock()) {
69		dprintf("gpt: alternate header not in last block (%" B_PRIu64 " vs. %"
70			B_PRIu64 ")\n", fHeader.AlternateBlock(), lastBlock);
71		lastBlock = fHeader.AlternateBlock();
72	}
73
74	// Read backup header, too
75	status_t status = _Read(fd, lastBlock * blockSize, &fBackupHeader,
76		sizeof(gpt_table_header));
77	if (status == B_OK) {
78		if (!_IsHeaderValid(fBackupHeader, lastBlock))
79			status = B_BAD_DATA;
80	}
81
82	// If both headers are invalid, bail out -- this is probably not a GPT disk
83	if (status != B_OK && fStatus != B_OK)
84		return;
85
86	if (fStatus != B_OK) {
87		// Recreate primary header from the backup
88		fHeader = fBackupHeader;
89		fHeader.SetAbsoluteBlock(EFI_HEADER_LOCATION);
90		fHeader.SetEntriesBlock(EFI_PARTITION_ENTRIES_BLOCK);
91		fHeader.SetAlternateBlock(lastBlock);
92		fDirty = true;
93	} else if (status != B_OK) {
94		// Recreate backup header from primary
95		_SetBackupHeaderFromPrimary(lastBlock);
96	}
97
98	// allocate, read, and check partition entry array
99
100	fEntries = new (std::nothrow) uint8[_EntryArraySize()];
101	if (fEntries == NULL) {
102		// TODO: if there cannot be allocated enough (ie. the boot loader's
103		//	heap is limited), try a smaller size before failing
104		fStatus = B_NO_MEMORY;
105		return;
106	}
107
108	fStatus = _Read(fd, fHeader.EntriesBlock() * blockSize,
109		fEntries, _EntryArraySize());
110	if (fStatus != B_OK || !_ValidateEntriesCRC()) {
111		// Read backup entries instead
112		fStatus = _Read(fd, fBackupHeader.EntriesBlock() * blockSize,
113			fEntries, _EntryArraySize());
114		if (fStatus != B_OK)
115			return;
116
117		if (!_ValidateEntriesCRC()) {
118			fStatus = B_BAD_DATA;
119			return;
120		}
121	}
122
123	// TODO: check overlapping or out of range partitions
124
125#ifdef TRACE_EFI_GPT
126	_Dump(fHeader);
127	_Dump(fBackupHeader);
128	_DumpPartitions();
129#endif
130
131	fStatus = B_OK;
132}
133
134
135#if !defined(_BOOT_MODE) && !defined(_USER_MODE)
136Header::Header(uint64 lastBlock, uint32 blockSize)
137	:
138	fBlockSize(blockSize),
139	fStatus(B_NO_INIT),
140	fEntries(NULL),
141	fDirty(true)
142{
143	TRACE(("EFI::Header: Initialize GPT, block size %" B_PRIu32 "\n",
144		blockSize));
145
146	// Initialize to an empty header
147	memcpy(fHeader.header, EFI_PARTITION_HEADER, sizeof(fHeader.header));
148	fHeader.SetRevision(EFI_TABLE_REVISION);
149	fHeader.SetHeaderSize(sizeof(fHeader));
150	fHeader.SetHeaderCRC(0);
151	fHeader.SetAbsoluteBlock(EFI_HEADER_LOCATION);
152	fHeader.SetAlternateBlock(lastBlock);
153	uuid_t uuid;
154	uuid_generate_random(uuid);
155	memcpy((uint8*)&fHeader.disk_guid, uuid, sizeof(guid_t));
156	fHeader.SetEntriesBlock(EFI_PARTITION_ENTRIES_BLOCK);
157	fHeader.SetEntryCount(EFI_PARTITION_ENTRY_COUNT);
158	fHeader.SetEntrySize(EFI_PARTITION_ENTRY_SIZE);
159	fHeader.SetEntriesCRC(0);
160
161	size_t arraySize = _EntryArraySize();
162	fEntries = new (std::nothrow) uint8[arraySize];
163	if (fEntries == NULL) {
164		fStatus = B_NO_MEMORY;
165		return;
166	}
167
168	memset(fEntries, 0, arraySize);
169		// TODO: initialize the entry guids
170
171	uint32 entryBlocks = (arraySize + fBlockSize - 1) / fBlockSize;
172	fHeader.SetFirstUsableBlock(EFI_PARTITION_ENTRIES_BLOCK + entryBlocks);
173	fHeader.SetLastUsableBlock(lastBlock - 1 - entryBlocks);
174
175	_SetBackupHeaderFromPrimary(lastBlock);
176
177#ifdef TRACE_EFI_GPT
178	_Dump(fHeader);
179	_DumpPartitions();
180#endif
181
182	fStatus = B_OK;
183}
184#endif // !_BOOT_MODE && !_USER_MODE
185
186
187Header::~Header()
188{
189	delete[] fEntries;
190}
191
192
193status_t
194Header::InitCheck() const
195{
196	return fStatus;
197}
198
199
200bool
201Header::IsDirty() const
202{
203	return fDirty;
204}
205
206
207#ifndef _BOOT_MODE
208status_t
209Header::WriteEntry(int fd, uint32 entryIndex)
210{
211	off_t entryOffset = entryIndex * fHeader.EntrySize();
212
213	status_t status = _Write(fd,
214		fHeader.EntriesBlock() * fBlockSize + entryOffset,
215		fEntries + entryOffset, fHeader.EntrySize());
216	if (status != B_OK)
217		return status;
218
219	// Update header, too -- the entries CRC changed
220	status = _WriteHeader(fd);
221
222	// Write backup
223	status_t backupStatus = _Write(fd,
224		fBackupHeader.EntriesBlock() * fBlockSize + entryOffset,
225		fEntries + entryOffset, fHeader.EntrySize());
226
227	if (status == B_OK && backupStatus == B_OK)
228		fDirty = false;
229	return status == B_OK ? backupStatus : status;
230}
231
232
233status_t
234Header::Write(int fd)
235{
236	// Try to write the protective MBR
237	PartitionMap partitionMap;
238	PrimaryPartition *partition = NULL;
239	uint32 index = 0;
240	while ((partition = partitionMap.PrimaryPartitionAt(index)) != NULL) {
241		if (index == 0) {
242			uint64 deviceSize = fHeader.AlternateBlock() * fBlockSize;
243			partition->SetTo(fBlockSize, deviceSize, 0xEE, false, fBlockSize);
244		} else
245			partition->Unset();
246		++index;
247	}
248	PartitionMapWriter writer(fd, fBlockSize);
249	writer.WriteMBR(&partitionMap, true);
250		// We also write the bootcode, so we can boot GPT disks from BIOS
251
252	status_t status = _Write(fd, fHeader.EntriesBlock() * fBlockSize, fEntries,
253		_EntryArraySize());
254	if (status != B_OK)
255		return status;
256
257	// First write the header, so that we have at least one completely correct
258	// data set
259	status = _WriteHeader(fd);
260
261
262	// Write backup entries
263	status_t backupStatus = _Write(fd,
264		fBackupHeader.EntriesBlock() * fBlockSize, fEntries, _EntryArraySize());
265
266	if (status == B_OK && backupStatus == B_OK)
267		fDirty = false;
268	return status == B_OK ? backupStatus : status;
269}
270
271
272status_t
273Header::_WriteHeader(int fd)
274{
275	_UpdateCRC();
276
277	status_t status = _Write(fd, fHeader.AbsoluteBlock() * fBlockSize,
278		&fHeader, sizeof(gpt_table_header));
279	if (status != B_OK)
280		return status;
281
282	return _Write(fd, fBackupHeader.AbsoluteBlock() * fBlockSize,
283		&fBackupHeader, sizeof(gpt_table_header));
284}
285
286
287status_t
288Header::_Write(int fd, off_t offset, const void* data, size_t size) const
289{
290	ssize_t bytesWritten = write_pos(fd, offset, data, size);
291	if (bytesWritten < 0)
292		return bytesWritten;
293	if (bytesWritten != (ssize_t)size)
294		return B_IO_ERROR;
295
296	return B_OK;
297}
298
299
300void
301Header::_UpdateCRC()
302{
303	_UpdateCRC(fHeader);
304	_UpdateCRC(fBackupHeader);
305}
306
307
308void
309Header::_UpdateCRC(gpt_table_header& header)
310{
311	header.SetEntriesCRC(crc32(fEntries, _EntryArraySize()));
312	header.SetHeaderCRC(0);
313	header.SetHeaderCRC(crc32((uint8*)&header, sizeof(gpt_table_header)));
314}
315#endif // !_BOOT_MODE
316
317
318status_t
319Header::_Read(int fd, off_t offset, void* data, size_t size) const
320{
321	ssize_t bytesRead = read_pos(fd, offset, data, size);
322	if (bytesRead < 0)
323		return bytesRead;
324	if (bytesRead != (ssize_t)size)
325		return B_IO_ERROR;
326
327	return B_OK;
328}
329
330
331bool
332Header::_IsHeaderValid(gpt_table_header& header, uint64 block)
333{
334	return !memcmp(header.header, EFI_PARTITION_HEADER, sizeof(header.header))
335		&& _ValidateHeaderCRC(header)
336		&& header.AbsoluteBlock() == block;
337}
338
339
340bool
341Header::_ValidateHeaderCRC(gpt_table_header& header)
342{
343	uint32 originalCRC = header.HeaderCRC();
344	header.SetHeaderCRC(0);
345
346	bool matches = originalCRC == crc32((const uint8*)&header,
347		sizeof(gpt_table_header));
348
349	header.SetHeaderCRC(originalCRC);
350	return matches;
351}
352
353
354bool
355Header::_ValidateEntriesCRC() const
356{
357	return fHeader.EntriesCRC() == crc32(fEntries, _EntryArraySize());
358}
359
360
361void
362Header::_SetBackupHeaderFromPrimary(uint64 lastBlock)
363{
364	fBackupHeader = fHeader;
365	fBackupHeader.SetAbsoluteBlock(lastBlock);
366	fBackupHeader.SetEntriesBlock(
367		lastBlock - _EntryArraySize() / fBlockSize);
368	fBackupHeader.SetAlternateBlock(1);
369}
370
371
372#ifdef TRACE_EFI_GPT
373const char *
374Header::_PrintGUID(const guid_t &id)
375{
376	static char guid[48];
377	snprintf(guid, sizeof(guid),
378		"%08" B_PRIx32 "-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
379		B_LENDIAN_TO_HOST_INT32(id.data1), B_LENDIAN_TO_HOST_INT16(id.data2),
380		B_LENDIAN_TO_HOST_INT16(id.data3), id.data4[0], id.data4[1],
381		id.data4[2], id.data4[3], id.data4[4], id.data4[5], id.data4[6],
382		id.data4[7]);
383	return guid;
384}
385
386
387void
388Header::_Dump(const gpt_table_header& header)
389{
390	dprintf("EFI header: %.8s\n", header.header);
391	dprintf("EFI revision: %" B_PRIx32 "\n", header.Revision());
392	dprintf("header size: %" B_PRId32 "\n", header.HeaderSize());
393	dprintf("header CRC: %" B_PRIx32 "\n", header.HeaderCRC());
394	dprintf("absolute block: %" B_PRIu64 "\n", header.AbsoluteBlock());
395	dprintf("alternate block: %" B_PRIu64 "\n", header.AlternateBlock());
396	dprintf("first usable block: %" B_PRIu64 "\n", header.FirstUsableBlock());
397	dprintf("last usable block: %" B_PRIu64 "\n", header.LastUsableBlock());
398	dprintf("disk GUID: %s\n", _PrintGUID(header.disk_guid));
399	dprintf("entries block: %" B_PRIu64 "\n", header.EntriesBlock());
400	dprintf("entry size:  %" B_PRIu32 "\n", header.EntrySize());
401	dprintf("entry count: %" B_PRIu32 "\n", header.EntryCount());
402	dprintf("entries CRC: %" B_PRIx32 "\n", header.EntriesCRC());
403}
404
405
406void
407Header::_DumpPartitions()
408{
409	for (uint32 i = 0; i < EntryCount(); i++) {
410		const gpt_partition_entry &entry = EntryAt(i);
411
412		if (entry.partition_type == kEmptyGUID)
413			continue;
414
415		dprintf("[%3" B_PRIu32 "] partition type: %s\n", i,
416			_PrintGUID(entry.partition_type));
417		dprintf("      unique id: %s\n", _PrintGUID(entry.unique_guid));
418		dprintf("      start block: %" B_PRIu64 "\n", entry.StartBlock());
419		dprintf("      end block: %" B_PRIu64 "\n", entry.EndBlock());
420		dprintf("      size: %g MB\n", (entry.EndBlock() - entry.StartBlock())
421			* 512 / 1024.0 / 1024.0);
422		dprintf("      attributes: %" B_PRIx64 "\n", entry.Attributes());
423
424		char name[64];
425		to_utf8(entry.name, EFI_PARTITION_NAME_LENGTH, name, sizeof(name));
426		dprintf("      name: %s\n", name);
427	}
428}
429#endif	// TRACE_EFI_GPT
430
431
432}	// namespace EFI
433