xref: /haiku/src/apps/bootmanager/LegacyBootMenu.cpp (revision 5ac9b506412b11afb993bb52d161efe7666958a5)
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 	if (!visitor.HasPartitions())
322 		return B_ENTRY_NOT_FOUND;
323 
324 	// Enough space to write boot menu to drive?
325 	if (visitor.FirstOffset() < (int)sizeof(kBootLoader))
326 		return B_PARTITION_TOO_SMALL;
327 
328 	if (device.IsReadOnlyMedia())
329 		return B_READ_ONLY_DEVICE;
330 
331 	return B_OK;
332 }
333 
334 
335 status_t
336 LegacyBootMenu::CollectPartitions(const BootDrive& drive, BMessage& settings)
337 {
338 	status_t status = B_ERROR;
339 
340 	// Remove previous partitions, if any
341 	settings.RemoveName("partition");
342 
343 	BDiskDeviceRoster diskDeviceRoster;
344 	BDiskDevice device;
345 	bool partitionsFound = false;
346 
347 	while (diskDeviceRoster.GetNextDevice(&device) == B_OK) {
348 		BPath path;
349 		status_t status = device.GetPath(&path);
350 		if (status != B_OK)
351 			continue;
352 
353 		// Skip not from BIOS bootable drives that are not the target disk
354 		int8 biosDrive = 0;
355 		if (path != drive.Path()
356 			&& _GetBIOSDrive(path.Path(), biosDrive) != B_OK)
357 			continue;
358 
359 		PartitionRecorder recorder(settings, biosDrive);
360 		device.VisitEachDescendant(&recorder);
361 
362 		partitionsFound |= recorder.FoundPartitions();
363 	}
364 
365 	return partitionsFound ? B_OK : status;
366 }
367 
368 
369 status_t
370 LegacyBootMenu::Install(const BootDrive& drive, BMessage& settings)
371 {
372 	int32 defaultPartitionIndex;
373 	if (settings.FindInt32("defaultPartition", &defaultPartitionIndex) != B_OK)
374 		return B_BAD_VALUE;
375 
376 	int32 timeout;
377 	if (settings.FindInt32("timeout", &timeout) != B_OK)
378 		return B_BAD_VALUE;
379 
380 	int fd = open(drive.Path(), O_RDWR);
381 	if (fd < 0)
382 		return B_IO_ERROR;
383 
384 	MasterBootRecord oldMBR;
385 	if (read(fd, &oldMBR, sizeof(oldMBR)) != sizeof(oldMBR)) {
386 		close(fd);
387 		return B_IO_ERROR;
388 	}
389 
390 	if (!_IsValid(&oldMBR)) {
391 		close(fd);
392 		return B_BAD_VALUE;
393 	}
394 
395 	LittleEndianMallocIO newBootLoader;
396 	ssize_t size = sizeof(kBootLoader);
397 	if (newBootLoader.Write(kBootLoader, size) != size) {
398 		close(fd);
399 		return B_NO_MEMORY;
400 	}
401 
402 	MasterBootRecord* newMBR = (MasterBootRecord*)newBootLoader.Buffer();
403 	_CopyPartitionTable(newMBR, &oldMBR);
404 
405 	int menuEntries = 0;
406 	int defaultMenuEntry = 0;
407 	BMessage partition;
408 	int32 index;
409 	for (index = 0; settings.FindMessage("partition", index,
410 			&partition) == B_OK; index ++) {
411 		bool show;
412 		partition.FindBool("show", &show);
413 		if (!show)
414 			continue;
415 		if (index == defaultPartitionIndex)
416 			defaultMenuEntry = menuEntries;
417 
418 		menuEntries ++;
419 	}
420 	newBootLoader.WriteInt16(menuEntries);
421 	newBootLoader.WriteInt16(defaultMenuEntry);
422 	newBootLoader.WriteInt16(timeout);
423 
424 	for (index = 0; settings.FindMessage("partition", index,
425 			&partition) == B_OK; index ++) {
426 		bool show;
427 		BString name;
428 		BString path;
429 		int64 offset;
430 		int8 drive;
431 		partition.FindBool("show", &show);
432 		partition.FindString("name", &name);
433 		partition.FindString("path", &path);
434 		// LegacyBootMenu specific data
435 		partition.FindInt64("offset", &offset);
436 		partition.FindInt8("drive", &drive);
437 		if (!show)
438 			continue;
439 
440 		BString biosName;
441 		_ConvertToBIOSText(name.String(), biosName);
442 
443 		newBootLoader.WriteString(biosName.String());
444 		newBootLoader.WriteInt8(drive);
445 		newBootLoader.WriteInt64(offset / kBlockSize);
446 	}
447 
448 	if (!newBootLoader.Align(kBlockSize)) {
449 		close(fd);
450 		return B_ERROR;
451 	}
452 
453 	lseek(fd, 0, SEEK_SET);
454 	const uint8* buffer = (const uint8*)newBootLoader.Buffer();
455 	status_t status = _WriteBlocks(fd, buffer, newBootLoader.Position());
456 	close(fd);
457 	return status;
458 }
459 
460 
461 status_t
462 LegacyBootMenu::SaveMasterBootRecord(BMessage* settings, BFile* file)
463 {
464 	BString path;
465 
466 	if (settings->FindString("disk", &path) != B_OK)
467 		return B_BAD_VALUE;
468 
469 	int fd = open(path.String(), O_RDONLY);
470 	if (fd < 0)
471 		return B_IO_ERROR;
472 
473 	ssize_t size = kBlockSize * kNumberOfBootLoaderBlocks;
474 	uint8* buffer = new(std::nothrow) uint8[size];
475 	if (buffer == NULL) {
476 		close(fd);
477 		return B_NO_MEMORY;
478 	}
479 
480 	status_t status = _ReadBlocks(fd, buffer, size);
481 	if (status != B_OK) {
482 		close(fd);
483 		delete[] buffer;
484 		return B_IO_ERROR;
485 	}
486 
487 	MasterBootRecord* mbr = (MasterBootRecord*)buffer;
488 	if (!_IsValid(mbr)) {
489 		close(fd);
490 		delete[] buffer;
491 		return B_BAD_VALUE;
492 	}
493 
494 	if (file->Write(buffer, size) != size)
495 		status = B_IO_ERROR;
496 	delete[] buffer;
497 	close(fd);
498 	return status;
499 }
500 
501 
502 status_t
503 LegacyBootMenu::RestoreMasterBootRecord(BMessage* settings, BFile* file)
504 {
505 	BString path;
506 	if (settings->FindString("disk", &path) != B_OK)
507 		return B_BAD_VALUE;
508 
509 	int fd = open(path.String(), O_RDWR);
510 	if (fd < 0)
511 		return B_IO_ERROR;
512 
513 	MasterBootRecord oldMBR;
514 	if (read(fd, &oldMBR, sizeof(oldMBR)) != sizeof(oldMBR)) {
515 		close(fd);
516 		return B_IO_ERROR;
517 	}
518 	if (!_IsValid(&oldMBR)) {
519 		close(fd);
520 		return B_BAD_VALUE;
521 	}
522 
523 	lseek(fd, 0, SEEK_SET);
524 
525 	size_t size = kBlockSize * kNumberOfBootLoaderBlocks;
526 	uint8* buffer = new(std::nothrow) uint8[size];
527 	if (buffer == NULL) {
528 		close(fd);
529 		return B_NO_MEMORY;
530 	}
531 
532 	if (file->Read(buffer, size) != (ssize_t)size) {
533 		close(fd);
534 		delete[] buffer;
535 		return B_IO_ERROR;
536 	}
537 
538 	MasterBootRecord* newMBR = (MasterBootRecord*)buffer;
539 	if (!_IsValid(newMBR)) {
540 		close(fd);
541 		delete[] buffer;
542 		return B_BAD_VALUE;
543 	}
544 
545 	_CopyPartitionTable(newMBR, &oldMBR);
546 
547 	status_t status = _WriteBlocks(fd, buffer, size);
548 	delete[] buffer;
549 	close(fd);
550 	return status;
551 }
552 
553 
554 status_t
555 LegacyBootMenu::GetDisplayText(const char* text, BString& displayText)
556 {
557 	BString biosText;
558 	if (!_ConvertToBIOSText(text, biosText)) {
559 		displayText = "???";
560 		return B_ERROR;
561 	}
562 
563 	// convert back to UTF-8
564 	int32 biosTextLength = biosText.Length();
565 	int32 bufferLength = strlen(text);
566 	char* buffer = displayText.LockBuffer(bufferLength + 1);
567 	int32 state = 0;
568 	if (convert_to_utf8(B_MS_DOS_CONVERSION,
569 		biosText.String(), &biosTextLength,
570 		buffer, &bufferLength, &state) != B_OK) {
571 		displayText.UnlockBuffer(0);
572 		displayText = "???";
573 		return B_ERROR;
574 	}
575 
576 	buffer[bufferLength] = '\0';
577 	displayText.UnlockBuffer(bufferLength);
578 	return B_OK;
579 }
580 
581 
582 bool
583 LegacyBootMenu::_ConvertToBIOSText(const char* text, BString& biosText)
584 {
585 	// convert text in UTF-8 to 'code page 437'
586 	int32 textLength = strlen(text);
587 
588 	int32 biosTextLength = textLength;
589 	char* buffer = biosText.LockBuffer(biosTextLength + 1);
590 	if (buffer == NULL) {
591 		biosText.UnlockBuffer(0);
592 		return false;
593 	}
594 
595 	int32 state = 0;
596 	if (convert_from_utf8(B_MS_DOS_CONVERSION, text, &textLength,
597 		buffer, &biosTextLength, &state) != B_OK) {
598 		biosText.UnlockBuffer(0);
599 		return false;
600 	}
601 
602 	buffer[biosTextLength] = '\0';
603 	biosText.UnlockBuffer(biosTextLength);
604 	return biosTextLength < kMaxBootMenuItemLength;
605 }
606 
607 
608 status_t
609 LegacyBootMenu::_GetBIOSDrive(const char* device, int8& drive)
610 {
611 	int fd = open(device, O_RDONLY);
612 	if (fd < 0)
613 		return errno;
614 
615 	status_t status = ioctl(fd, B_GET_BIOS_DRIVE_ID, drive, 1);
616 	close(fd);
617 	return status;
618 }
619 
620 
621 status_t
622 LegacyBootMenu::_ReadBlocks(int fd, uint8* buffer, size_t size)
623 {
624 	if (size % kBlockSize != 0) {
625 		fprintf(stderr, "_ReadBlocks buffer size must be a multiple of %d\n",
626 			(int)kBlockSize);
627 		return B_BAD_VALUE;
628 	}
629 	const size_t blocks = size / kBlockSize;
630 	uint8* block = buffer;
631 	for (size_t i = 0; i < blocks; i ++, block += kBlockSize) {
632 		if (read(fd, block, kBlockSize) != (ssize_t)kBlockSize)
633 			return B_IO_ERROR;
634 	}
635 	return B_OK;
636 }
637 
638 
639 status_t
640 LegacyBootMenu::_WriteBlocks(int fd, const uint8* buffer, size_t size)
641 {
642 	if (size % kBlockSize != 0) {
643 		fprintf(stderr, "_WriteBlocks buffer size must be a multiple of %d\n",
644 			(int)kBlockSize);
645 		return B_BAD_VALUE;
646 	}
647 	const size_t blocks = size / kBlockSize;
648 	const uint8* block = buffer;
649 	for (size_t i = 0; i < blocks; i ++, block += kBlockSize) {
650 		if (write(fd, block, kBlockSize) != (ssize_t)kBlockSize)
651 			return B_IO_ERROR;
652 	}
653 	return B_OK;
654 }
655 
656 
657 void
658 LegacyBootMenu::_CopyPartitionTable(MasterBootRecord* destination,
659 		const MasterBootRecord* source)
660 {
661 	memcpy(destination->diskSignature, source->diskSignature,
662 		sizeof(source->diskSignature) + sizeof(source->reserved)
663 			+ sizeof(source->partition));
664 }
665 
666 
667 bool
668 LegacyBootMenu::_IsValid(const MasterBootRecord* mbr)
669 {
670 	return mbr->signature[0] == (kMBRSignature & 0xff)
671 		&& mbr->signature[1] == (kMBRSignature >> 8);
672 }
673