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