xref: /haiku/src/apps/bootmanager/LegacyBootMenu.cpp (revision 2710b4f5d4251c5cf88c82b0114ea99b0ef46d22)
1 /*
2  * Copyright 2008-2011, Haiku, Inc. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Axel Dörfler, axeld@pinc-software.de
7  *		Michael Pfeiffer <laplace@users.sourceforge.net>
8  */
9 
10 
11 #include "LegacyBootMenu.h"
12 
13 #include <new>
14 
15 #include <errno.h>
16 #include <stdio.h>
17 
18 #include <Catalog.h>
19 #include <DataIO.h>
20 #include <DiskDevice.h>
21 #include <DiskDeviceTypes.h>
22 #include <DiskDeviceRoster.h>
23 #include <DiskDeviceVisitor.h>
24 #include <Drivers.h>
25 #include <File.h>
26 #include <Partition.h>
27 #include <Path.h>
28 #include <String.h>
29 #include <UTF8.h>
30 
31 #include "BootDrive.h"
32 #include "BootLoader.h"
33 
34 
35 /*
36 	Note: for testing, the best way is to create a small file image, and
37 	register it, for example via the "diskimage" tool (mountvolume might also
38 	work).
39 	You can then create partitions on it via DriveSetup, or simply copy an
40 	existing drive to it, for example via:
41 		$ dd if=/dev/disk/ata/0/master/raw of=test.image bs=1M count=10
42 
43 	It will automatically appear in BootManager once it is registered, and,
44 	depending on its parition layout, you can then install the boot menu on
45 	it, and directly test it via qemu.
46 */
47 
48 
49 #undef B_TRANSLATION_CONTEXT
50 #define B_TRANSLATION_CONTEXT "LegacyBootMenu"
51 
52 
53 struct MasterBootRecord {
54 	uint8 bootLoader[440];
55 	uint8 diskSignature[4];
56 	uint8 reserved[2];
57 	uint8 partition[64];
58 	uint8 signature[2];
59 };
60 
61 
62 class LittleEndianMallocIO : public BMallocIO {
63 public:
64 			bool				WriteInt8(int8 value);
65 			bool				WriteInt16(int16 value);
66 			bool				WriteInt32(int32 value);
67 			bool				WriteInt64(int64 value);
68 			bool				WriteString(const char* value);
69 			bool				Align(int16 alignment);
70 			bool				Fill(int16 size, int8 fillByte);
71 };
72 
73 
74 class PartitionVisitor : public BDiskDeviceVisitor {
75 public:
76 								PartitionVisitor();
77 
78 	virtual	bool				Visit(BDiskDevice* device);
79 	virtual	bool				Visit(BPartition* partition, int32 level);
80 
81 			bool				HasPartitions() const;
82 			off_t				FirstOffset() const;
83 
84 private:
85 			off_t				fFirstOffset;
86 };
87 
88 
89 class PartitionRecorder : public BDiskDeviceVisitor {
90 public:
91 								PartitionRecorder(BMessage& settings,
92 									int8 biosDrive);
93 
94 	virtual	bool				Visit(BDiskDevice* device);
95 	virtual	bool				Visit(BPartition* partition, int32 level);
96 
97 			bool				FoundPartitions() const;
98 
99 private:
100 			BMessage&			fSettings;
101 			int32				fUnnamedIndex;
102 			int8				fBIOSDrive;
103 			bool				fFound;
104 };
105 
106 
107 static const uint32 kBlockSize = 512;
108 static const uint32 kNumberOfBootLoaderBlocks = 4;
109 	// The number of blocks required to store the
110 	// MBR including the Haiku boot loader.
111 
112 static const uint32 kMBRSignature = 0xAA55;
113 
114 static const int32 kMaxBootMenuItemLength = 70;
115 
116 
117 bool
WriteInt8(int8 value)118 LittleEndianMallocIO::WriteInt8(int8 value)
119 {
120 	return Write(&value, sizeof(value)) == sizeof(value);
121 }
122 
123 
124 bool
WriteInt16(int16 value)125 LittleEndianMallocIO::WriteInt16(int16 value)
126 {
127 	return WriteInt8(value & 0xff)
128 		&& WriteInt8(value >> 8);
129 }
130 
131 
132 bool
WriteInt32(int32 value)133 LittleEndianMallocIO::WriteInt32(int32 value)
134 {
135 	return WriteInt8(value & 0xff)
136 		&& WriteInt8(value >> 8)
137 		&& WriteInt8(value >> 16)
138 		&& WriteInt8(value >> 24);
139 }
140 
141 
142 bool
WriteInt64(int64 value)143 LittleEndianMallocIO::WriteInt64(int64 value)
144 {
145 	return WriteInt32(value) && WriteInt32(value >> 32);
146 }
147 
148 
149 bool
WriteString(const char * value)150 LittleEndianMallocIO::WriteString(const char* value)
151 {
152 	int len = strlen(value) + 1;
153 	return WriteInt8(len)
154 		&& Write(value, len) == len;
155 }
156 
157 
158 bool
Align(int16 alignment)159 LittleEndianMallocIO::Align(int16 alignment)
160 {
161 	if ((Position() % alignment) == 0)
162 		return true;
163 	return Fill(alignment - (Position() % alignment), 0);
164 }
165 
166 
167 bool
Fill(int16 size,int8 fillByte)168 LittleEndianMallocIO::Fill(int16 size, int8 fillByte)
169 {
170 	for (int i = 0; i < size; i ++) {
171 		if (!WriteInt8(fillByte))
172 			return false;
173 	}
174 	return true;
175 }
176 
177 
178 // #pragma mark -
179 
180 
PartitionVisitor()181 PartitionVisitor::PartitionVisitor()
182 	:
183 	fFirstOffset(LONGLONG_MAX)
184 {
185 }
186 
187 
188 bool
Visit(BDiskDevice * device)189 PartitionVisitor::Visit(BDiskDevice* device)
190 {
191 	return false;
192 }
193 
194 
195 bool
Visit(BPartition * partition,int32 level)196 PartitionVisitor::Visit(BPartition* partition, int32 level)
197 {
198 	if (partition->Offset() < fFirstOffset)
199 		fFirstOffset = partition->Offset();
200 
201 	return false;
202 }
203 
204 
205 bool
HasPartitions() const206 PartitionVisitor::HasPartitions() const
207 {
208 	return fFirstOffset != LONGLONG_MAX;
209 }
210 
211 
212 off_t
FirstOffset() const213 PartitionVisitor::FirstOffset() const
214 {
215 	return fFirstOffset;
216 }
217 
218 
219 // #pragma mark -
220 
221 
PartitionRecorder(BMessage & settings,int8 biosDrive)222 PartitionRecorder::PartitionRecorder(BMessage& settings, int8 biosDrive)
223 	:
224 	fSettings(settings),
225 	fUnnamedIndex(0),
226 	fBIOSDrive(biosDrive),
227 	fFound(false)
228 {
229 }
230 
231 
232 bool
Visit(BDiskDevice * device)233 PartitionRecorder::Visit(BDiskDevice* device)
234 {
235 	return false;
236 }
237 
238 
239 bool
Visit(BPartition * partition,int32 level)240 PartitionRecorder::Visit(BPartition* partition, int32 level)
241 {
242 	if (partition->ContainsPartitioningSystem())
243 		return false;
244 
245 	BPath partitionPath;
246 	partition->GetPath(&partitionPath);
247 
248 	BString buffer;
249 	BString name = partition->ContentName();
250 	if (name.Length() == 0) {
251 		BString number;
252 		number << ++fUnnamedIndex;
253 		buffer << B_TRANSLATE_COMMENT("Unnamed %d",
254 			"Default name of a partition whose name could not be read from "
255 			"disk; characters in codepage 437 are allowed only");
256 		buffer.ReplaceFirst("%d", number);
257 		name = buffer.String();
258 	}
259 
260 	const char* type = partition->Type();
261 	if (type == NULL)
262 		type = B_TRANSLATE_COMMENT("Unknown", "Text is shown for an unknown "
263 			"partition type");
264 
265 	BMessage message;
266 	// Data as required by BootLoader.h
267 	message.AddBool("show", true);
268 	message.AddString("name", name);
269 	message.AddString("type", type);
270 	message.AddString("path", partitionPath.Path());
271 	if (fBIOSDrive != 0)
272 		message.AddInt8("drive", fBIOSDrive);
273 	message.AddInt64("size", partition->Size());
274 	message.AddInt64("offset", partition->Offset());
275 
276 	fSettings.AddMessage("partition", &message);
277 	fFound = true;
278 
279 	return false;
280 }
281 
282 
283 bool
FoundPartitions() const284 PartitionRecorder::FoundPartitions() const
285 {
286 	return fFound;
287 }
288 
289 
290 // #pragma mark -
291 
292 
LegacyBootMenu()293 LegacyBootMenu::LegacyBootMenu()
294 {
295 }
296 
297 
~LegacyBootMenu()298 LegacyBootMenu::~LegacyBootMenu()
299 {
300 }
301 
302 
303 bool
IsInstalled(const BootDrive & drive)304 LegacyBootMenu::IsInstalled(const BootDrive& drive)
305 {
306 	// TODO: detect bootman
307 	return false;
308 }
309 
310 
311 status_t
CanBeInstalled(const BootDrive & drive)312 LegacyBootMenu::CanBeInstalled(const BootDrive& drive)
313 {
314 	BDiskDevice device;
315 	status_t status = drive.GetDiskDevice(device);
316 	if (status != B_OK)
317 		return status;
318 
319 	PartitionVisitor visitor;
320 	device.VisitEachDescendant(&visitor);
321 
322 	if (!visitor.HasPartitions()
323 			|| strcmp(device.ContentType(), kPartitionTypeIntel) != 0)
324 		return B_ENTRY_NOT_FOUND;
325 
326 	// Enough space to write boot menu to drive?
327 	if (visitor.FirstOffset() < (int)sizeof(kBootLoader))
328 		return B_PARTITION_TOO_SMALL;
329 
330 	if (device.IsReadOnlyMedia())
331 		return B_READ_ONLY_DEVICE;
332 
333 	return B_OK;
334 }
335 
336 
337 status_t
CollectPartitions(const BootDrive & drive,BMessage & settings)338 LegacyBootMenu::CollectPartitions(const BootDrive& drive, BMessage& settings)
339 {
340 	status_t status = B_ERROR;
341 
342 	// Remove previous partitions, if any
343 	settings.RemoveName("partition");
344 
345 	BDiskDeviceRoster diskDeviceRoster;
346 	BDiskDevice device;
347 	bool partitionsFound = false;
348 
349 	while (diskDeviceRoster.GetNextDevice(&device) == B_OK) {
350 		BPath path;
351 		status_t status = device.GetPath(&path);
352 		if (status != B_OK)
353 			continue;
354 
355 		// Skip not from BIOS bootable drives that are not the target disk
356 		int8 biosDrive = 0;
357 		if (path != drive.Path()
358 			&& _GetBIOSDrive(path.Path(), biosDrive) != B_OK)
359 			continue;
360 
361 		PartitionRecorder recorder(settings, biosDrive);
362 		device.VisitEachDescendant(&recorder);
363 
364 		partitionsFound |= recorder.FoundPartitions();
365 	}
366 
367 	return partitionsFound ? B_OK : status;
368 }
369 
370 
371 status_t
Install(const BootDrive & drive,BMessage & settings)372 LegacyBootMenu::Install(const BootDrive& drive, BMessage& settings)
373 {
374 	int32 defaultPartitionIndex;
375 	if (settings.FindInt32("defaultPartition", &defaultPartitionIndex) != B_OK)
376 		return B_BAD_VALUE;
377 
378 	int32 timeout;
379 	if (settings.FindInt32("timeout", &timeout) != B_OK)
380 		return B_BAD_VALUE;
381 
382 	int fd = open(drive.Path(), O_RDWR);
383 	if (fd < 0)
384 		return B_IO_ERROR;
385 
386 	MasterBootRecord oldMBR;
387 	if (read(fd, &oldMBR, sizeof(oldMBR)) != sizeof(oldMBR)) {
388 		close(fd);
389 		return B_IO_ERROR;
390 	}
391 
392 	if (!_IsValid(&oldMBR)) {
393 		close(fd);
394 		return B_BAD_VALUE;
395 	}
396 
397 	LittleEndianMallocIO newBootLoader;
398 	ssize_t size = sizeof(kBootLoader);
399 	if (newBootLoader.Write(kBootLoader, size) != size) {
400 		close(fd);
401 		return B_NO_MEMORY;
402 	}
403 
404 	MasterBootRecord* newMBR = (MasterBootRecord*)newBootLoader.Buffer();
405 	_CopyPartitionTable(newMBR, &oldMBR);
406 
407 	int menuEntries = 0;
408 	int defaultMenuEntry = 0;
409 	BMessage partition;
410 	int32 index;
411 	for (index = 0; settings.FindMessage("partition", index,
412 			&partition) == B_OK; index ++) {
413 		bool show;
414 		partition.FindBool("show", &show);
415 		if (!show)
416 			continue;
417 		if (index == defaultPartitionIndex)
418 			defaultMenuEntry = menuEntries;
419 
420 		menuEntries ++;
421 	}
422 	newBootLoader.WriteInt16(menuEntries);
423 	newBootLoader.WriteInt16(defaultMenuEntry);
424 	newBootLoader.WriteInt16(timeout);
425 
426 	for (index = 0; settings.FindMessage("partition", index,
427 			&partition) == B_OK; index ++) {
428 		bool show;
429 		BString name;
430 		BString path;
431 		int64 offset;
432 		int8 drive;
433 		partition.FindBool("show", &show);
434 		partition.FindString("name", &name);
435 		partition.FindString("path", &path);
436 		// LegacyBootMenu specific data
437 		partition.FindInt64("offset", &offset);
438 		partition.FindInt8("drive", &drive);
439 		if (!show)
440 			continue;
441 
442 		BString biosName;
443 		_ConvertToBIOSText(name.String(), biosName);
444 
445 		newBootLoader.WriteString(biosName.String());
446 		newBootLoader.WriteInt8(drive);
447 		newBootLoader.WriteInt64(offset / kBlockSize);
448 	}
449 
450 	if (!newBootLoader.Align(kBlockSize)) {
451 		close(fd);
452 		return B_ERROR;
453 	}
454 
455 	lseek(fd, 0, SEEK_SET);
456 	const uint8* buffer = (const uint8*)newBootLoader.Buffer();
457 	status_t status = _WriteBlocks(fd, buffer, newBootLoader.Position());
458 	close(fd);
459 	return status;
460 }
461 
462 
463 status_t
SaveMasterBootRecord(BMessage * settings,BFile * file)464 LegacyBootMenu::SaveMasterBootRecord(BMessage* settings, BFile* file)
465 {
466 	BString path;
467 
468 	if (settings->FindString("disk", &path) != B_OK)
469 		return B_BAD_VALUE;
470 
471 	int fd = open(path.String(), O_RDONLY);
472 	if (fd < 0)
473 		return B_IO_ERROR;
474 
475 	ssize_t size = kBlockSize * kNumberOfBootLoaderBlocks;
476 	uint8* buffer = new(std::nothrow) uint8[size];
477 	if (buffer == NULL) {
478 		close(fd);
479 		return B_NO_MEMORY;
480 	}
481 
482 	status_t status = _ReadBlocks(fd, buffer, size);
483 	if (status != B_OK) {
484 		close(fd);
485 		delete[] buffer;
486 		return B_IO_ERROR;
487 	}
488 
489 	MasterBootRecord* mbr = (MasterBootRecord*)buffer;
490 	if (!_IsValid(mbr)) {
491 		close(fd);
492 		delete[] buffer;
493 		return B_BAD_VALUE;
494 	}
495 
496 	if (file->Write(buffer, size) != size)
497 		status = B_IO_ERROR;
498 	delete[] buffer;
499 	close(fd);
500 	return status;
501 }
502 
503 
504 status_t
RestoreMasterBootRecord(BMessage * settings,BFile * file)505 LegacyBootMenu::RestoreMasterBootRecord(BMessage* settings, BFile* file)
506 {
507 	BString path;
508 	if (settings->FindString("disk", &path) != B_OK)
509 		return B_BAD_VALUE;
510 
511 	int fd = open(path.String(), O_RDWR);
512 	if (fd < 0)
513 		return B_IO_ERROR;
514 
515 	MasterBootRecord oldMBR;
516 	if (read(fd, &oldMBR, sizeof(oldMBR)) != sizeof(oldMBR)) {
517 		close(fd);
518 		return B_IO_ERROR;
519 	}
520 	if (!_IsValid(&oldMBR)) {
521 		close(fd);
522 		return B_BAD_VALUE;
523 	}
524 
525 	lseek(fd, 0, SEEK_SET);
526 
527 	size_t size = kBlockSize * kNumberOfBootLoaderBlocks;
528 	uint8* buffer = new(std::nothrow) uint8[size];
529 	if (buffer == NULL) {
530 		close(fd);
531 		return B_NO_MEMORY;
532 	}
533 
534 	if (file->Read(buffer, size) != (ssize_t)size) {
535 		close(fd);
536 		delete[] buffer;
537 		return B_IO_ERROR;
538 	}
539 
540 	MasterBootRecord* newMBR = (MasterBootRecord*)buffer;
541 	if (!_IsValid(newMBR)) {
542 		close(fd);
543 		delete[] buffer;
544 		return B_BAD_VALUE;
545 	}
546 
547 	_CopyPartitionTable(newMBR, &oldMBR);
548 
549 	status_t status = _WriteBlocks(fd, buffer, size);
550 	delete[] buffer;
551 	close(fd);
552 	return status;
553 }
554 
555 
556 status_t
GetDisplayText(const char * text,BString & displayText)557 LegacyBootMenu::GetDisplayText(const char* text, BString& displayText)
558 {
559 	BString biosText;
560 	if (!_ConvertToBIOSText(text, biosText)) {
561 		displayText = "???";
562 		return B_ERROR;
563 	}
564 
565 	// convert back to UTF-8
566 	int32 biosTextLength = biosText.Length();
567 	int32 bufferLength = strlen(text);
568 	char* buffer = displayText.LockBuffer(bufferLength + 1);
569 	int32 state = 0;
570 	if (convert_to_utf8(B_MS_DOS_CONVERSION,
571 		biosText.String(), &biosTextLength,
572 		buffer, &bufferLength, &state) != B_OK) {
573 		displayText.UnlockBuffer(0);
574 		displayText = "???";
575 		return B_ERROR;
576 	}
577 
578 	buffer[bufferLength] = '\0';
579 	displayText.UnlockBuffer(bufferLength);
580 	return B_OK;
581 }
582 
583 
584 bool
_ConvertToBIOSText(const char * text,BString & biosText)585 LegacyBootMenu::_ConvertToBIOSText(const char* text, BString& biosText)
586 {
587 	// convert text in UTF-8 to 'code page 437'
588 	int32 textLength = strlen(text);
589 
590 	int32 biosTextLength = textLength;
591 	char* buffer = biosText.LockBuffer(biosTextLength + 1);
592 	if (buffer == NULL) {
593 		biosText.UnlockBuffer(0);
594 		return false;
595 	}
596 
597 	int32 state = 0;
598 	if (convert_from_utf8(B_MS_DOS_CONVERSION, text, &textLength,
599 		buffer, &biosTextLength, &state) != B_OK) {
600 		biosText.UnlockBuffer(0);
601 		return false;
602 	}
603 
604 	buffer[biosTextLength] = '\0';
605 	biosText.UnlockBuffer(biosTextLength);
606 	return biosTextLength < kMaxBootMenuItemLength;
607 }
608 
609 
610 status_t
_GetBIOSDrive(const char * device,int8 & drive)611 LegacyBootMenu::_GetBIOSDrive(const char* device, int8& drive)
612 {
613 	int fd = open(device, O_RDONLY);
614 	if (fd < 0)
615 		return errno;
616 
617 	status_t status = ioctl(fd, B_GET_BIOS_DRIVE_ID, &drive, 1);
618 	close(fd);
619 	return status;
620 }
621 
622 
623 status_t
_ReadBlocks(int fd,uint8 * buffer,size_t size)624 LegacyBootMenu::_ReadBlocks(int fd, uint8* buffer, size_t size)
625 {
626 	if (size % kBlockSize != 0) {
627 		fprintf(stderr, "_ReadBlocks buffer size must be a multiple of %d\n",
628 			(int)kBlockSize);
629 		return B_BAD_VALUE;
630 	}
631 	const size_t blocks = size / kBlockSize;
632 	uint8* block = buffer;
633 	for (size_t i = 0; i < blocks; i ++, block += kBlockSize) {
634 		if (read(fd, block, kBlockSize) != (ssize_t)kBlockSize)
635 			return B_IO_ERROR;
636 	}
637 	return B_OK;
638 }
639 
640 
641 status_t
_WriteBlocks(int fd,const uint8 * buffer,size_t size)642 LegacyBootMenu::_WriteBlocks(int fd, const uint8* buffer, size_t size)
643 {
644 	if (size % kBlockSize != 0) {
645 		fprintf(stderr, "_WriteBlocks buffer size must be a multiple of %d\n",
646 			(int)kBlockSize);
647 		return B_BAD_VALUE;
648 	}
649 	const size_t blocks = size / kBlockSize;
650 	const uint8* block = buffer;
651 	for (size_t i = 0; i < blocks; i ++, block += kBlockSize) {
652 		if (write(fd, block, kBlockSize) != (ssize_t)kBlockSize)
653 			return B_IO_ERROR;
654 	}
655 	return B_OK;
656 }
657 
658 
659 void
_CopyPartitionTable(MasterBootRecord * destination,const MasterBootRecord * source)660 LegacyBootMenu::_CopyPartitionTable(MasterBootRecord* destination,
661 		const MasterBootRecord* source)
662 {
663 	memcpy(destination->diskSignature, source->diskSignature,
664 		sizeof(source->diskSignature) + sizeof(source->reserved)
665 			+ sizeof(source->partition));
666 }
667 
668 
669 bool
_IsValid(const MasterBootRecord * mbr)670 LegacyBootMenu::_IsValid(const MasterBootRecord* mbr)
671 {
672 	return mbr->signature[0] == (kMBRSignature & 0xff)
673 		&& mbr->signature[1] == (kMBRSignature >> 8);
674 }
675