xref: /haiku/src/add-ons/kernel/partitioning_systems/gpt/Header.cpp (revision 1a76488fc88584bf66b9751d7fb9b6527ac20d87)
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 
47 namespace EFI {
48 
49 
50 Header::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)
136 Header::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 
187 Header::~Header()
188 {
189 	delete[] fEntries;
190 }
191 
192 
193 status_t
194 Header::InitCheck() const
195 {
196 	return fStatus;
197 }
198 
199 
200 bool
201 Header::IsDirty() const
202 {
203 	return fDirty;
204 }
205 
206 
207 #ifndef _BOOT_MODE
208 status_t
209 Header::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 
233 status_t
234 Header::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 
272 status_t
273 Header::_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 
287 status_t
288 Header::_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 
300 void
301 Header::_UpdateCRC()
302 {
303 	_UpdateCRC(fHeader);
304 	_UpdateCRC(fBackupHeader);
305 }
306 
307 
308 void
309 Header::_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 
318 status_t
319 Header::_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 
331 bool
332 Header::_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 
340 bool
341 Header::_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 
354 bool
355 Header::_ValidateEntriesCRC() const
356 {
357 	return fHeader.EntriesCRC() == crc32(fEntries, _EntryArraySize());
358 }
359 
360 
361 void
362 Header::_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
373 const char *
374 Header::_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 
387 void
388 Header::_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 
406 void
407 Header::_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