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