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