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