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