2 * Copyright 2008-2011, Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
6 * Axel Dörfler, axeld@pinc-software.de
7 * Michael Pfeiffer <laplace@users.sourceforge.net>
11 #include "LegacyBootMenu.h"
20 #include <DiskDevice.h>
21 #include <DiskDeviceRoster.h>
22 #include <DiskDeviceVisitor.h>
25 #include <Partition.h>
30 #include "BootDrive.h"
31 #include "BootLoader.h"
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
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
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.
48 #undef B_TRANSLATION_CONTEXT
49 #define B_TRANSLATION_CONTEXT "LegacyBootMenu"
52 struct MasterBootRecord
{
53 uint8 bootLoader
[440];
54 uint8 diskSignature
[4];
61 class LittleEndianMallocIO
: public BMallocIO
{
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
);
73 class PartitionVisitor
: public BDiskDeviceVisitor
{
77 virtual bool Visit(BDiskDevice
* device
);
78 virtual bool Visit(BPartition
* partition
, int32 level
);
80 bool HasPartitions() const;
81 off_t
FirstOffset() const;
88 class PartitionRecorder
: public BDiskDeviceVisitor
{
90 PartitionRecorder(BMessage
& settings
,
93 virtual bool Visit(BDiskDevice
* device
);
94 virtual bool Visit(BPartition
* partition
, int32 level
);
96 bool FoundPartitions() const;
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.
111 static const uint32 kMBRSignature
= 0xAA55;
113 static const int32 kMaxBootMenuItemLength
= 70;
117 LittleEndianMallocIO::WriteInt8(int8 value
)
119 return Write(&value
, sizeof(value
)) == sizeof(value
);
124 LittleEndianMallocIO::WriteInt16(int16 value
)
126 return WriteInt8(value
& 0xff)
127 && WriteInt8(value
>> 8);
132 LittleEndianMallocIO::WriteInt32(int32 value
)
134 return WriteInt8(value
& 0xff)
135 && WriteInt8(value
>> 8)
136 && WriteInt8(value
>> 16)
137 && WriteInt8(value
>> 24);
142 LittleEndianMallocIO::WriteInt64(int64 value
)
144 return WriteInt32(value
) && WriteInt32(value
>> 32);
149 LittleEndianMallocIO::WriteString(const char* value
)
151 int len
= strlen(value
) + 1;
152 return WriteInt8(len
)
153 && Write(value
, len
) == len
;
158 LittleEndianMallocIO::Align(int16 alignment
)
160 if ((Position() % alignment
) == 0)
162 return Fill(alignment
- (Position() % alignment
), 0);
167 LittleEndianMallocIO::Fill(int16 size
, int8 fillByte
)
169 for (int i
= 0; i
< size
; i
++) {
170 if (!WriteInt8(fillByte
))
180 PartitionVisitor::PartitionVisitor()
182 fFirstOffset(LONGLONG_MAX
)
188 PartitionVisitor::Visit(BDiskDevice
* device
)
195 PartitionVisitor::Visit(BPartition
* partition
, int32 level
)
197 if (partition
->Offset() < fFirstOffset
)
198 fFirstOffset
= partition
->Offset();
205 PartitionVisitor::HasPartitions() const
207 return fFirstOffset
!= LONGLONG_MAX
;
212 PartitionVisitor::FirstOffset() const
221 PartitionRecorder::PartitionRecorder(BMessage
& settings
, int8 biosDrive
)
225 fBIOSDrive(biosDrive
),
232 PartitionRecorder::Visit(BDiskDevice
* device
)
239 PartitionRecorder::Visit(BPartition
* partition
, int32 level
)
241 if (partition
->ContainsPartitioningSystem())
245 partition
->GetPath(&partitionPath
);
248 const char* name
= partition
->ContentName();
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();
259 const char* type
= partition
->Type();
261 type
= B_TRANSLATE_COMMENT("Unknown", "Text is shown for an unknown "
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());
271 message
.AddInt8("drive", fBIOSDrive
);
272 message
.AddInt64("size", partition
->Size());
273 message
.AddInt64("offset", partition
->Offset());
275 fSettings
.AddMessage("partition", &message
);
283 PartitionRecorder::FoundPartitions() const
292 LegacyBootMenu::LegacyBootMenu()
297 LegacyBootMenu::~LegacyBootMenu()
303 LegacyBootMenu::IsInstalled(const BootDrive
& drive
)
305 // TODO: detect bootman
311 LegacyBootMenu::CanBeInstalled(const BootDrive
& drive
)
314 status_t status
= drive
.GetDiskDevice(device
);
318 PartitionVisitor visitor
;
319 device
.VisitEachDescendant(&visitor
);
321 if (!visitor
.HasPartitions())
322 return B_ENTRY_NOT_FOUND
;
324 // Enough space to write boot menu to drive?
325 if (visitor
.FirstOffset() < (int)sizeof(kBootLoader
))
326 return B_PARTITION_TOO_SMALL
;
333 LegacyBootMenu::CollectPartitions(const BootDrive
& drive
, BMessage
& settings
)
335 status_t status
= B_ERROR
;
337 // Remove previous partitions, if any
338 settings
.RemoveName("partition");
340 BDiskDeviceRoster diskDeviceRoster
;
342 bool partitionsFound
= false;
344 while (diskDeviceRoster
.GetNextDevice(&device
) == B_OK
) {
346 status_t status
= device
.GetPath(&path
);
350 // Skip not from BIOS bootable drives that are not the target disk
352 if (path
!= drive
.Path()
353 && _GetBIOSDrive(path
.Path(), biosDrive
) != B_OK
)
356 PartitionRecorder
recorder(settings
, biosDrive
);
357 device
.VisitEachDescendant(&recorder
);
359 partitionsFound
|= recorder
.FoundPartitions();
362 return partitionsFound
? B_OK
: status
;
367 LegacyBootMenu::Install(const BootDrive
& drive
, BMessage
& settings
)
369 int32 defaultPartitionIndex
;
370 if (settings
.FindInt32("defaultPartition", &defaultPartitionIndex
) != B_OK
)
374 if (settings
.FindInt32("timeout", &timeout
) != B_OK
)
377 int fd
= open(drive
.Path(), O_RDWR
);
381 MasterBootRecord oldMBR
;
382 if (read(fd
, &oldMBR
, sizeof(oldMBR
)) != sizeof(oldMBR
)) {
387 if (!_IsValid(&oldMBR
)) {
392 LittleEndianMallocIO newBootLoader
;
393 ssize_t size
= sizeof(kBootLoader
);
394 if (newBootLoader
.Write(kBootLoader
, size
) != size
) {
399 MasterBootRecord
* newMBR
= (MasterBootRecord
*)newBootLoader
.Buffer();
400 _CopyPartitionTable(newMBR
, &oldMBR
);
403 int defaultMenuEntry
= 0;
406 for (index
= 0; settings
.FindMessage("partition", index
,
407 &partition
) == B_OK
; index
++) {
409 partition
.FindBool("show", &show
);
412 if (index
== defaultPartitionIndex
)
413 defaultMenuEntry
= menuEntries
;
417 newBootLoader
.WriteInt16(menuEntries
);
418 newBootLoader
.WriteInt16(defaultMenuEntry
);
419 newBootLoader
.WriteInt16(timeout
);
421 for (index
= 0; settings
.FindMessage("partition", index
,
422 &partition
) == B_OK
; index
++) {
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
);
438 _ConvertToBIOSText(name
.String(), biosName
);
440 newBootLoader
.WriteString(biosName
.String());
441 newBootLoader
.WriteInt8(drive
);
442 newBootLoader
.WriteInt64(offset
/ kBlockSize
);
445 if (!newBootLoader
.Align(kBlockSize
)) {
450 lseek(fd
, 0, SEEK_SET
);
451 const uint8
* buffer
= (const uint8
*)newBootLoader
.Buffer();
452 status_t status
= _WriteBlocks(fd
, buffer
, newBootLoader
.Position());
459 LegacyBootMenu::SaveMasterBootRecord(BMessage
* settings
, BFile
* file
)
463 if (settings
->FindString("disk", &path
) != B_OK
)
466 int fd
= open(path
.String(), O_RDONLY
);
470 ssize_t size
= kBlockSize
* kNumberOfBootLoaderBlocks
;
471 uint8
* buffer
= new(std::nothrow
) uint8
[size
];
472 if (buffer
== NULL
) {
477 status_t status
= _ReadBlocks(fd
, buffer
, size
);
478 if (status
!= B_OK
) {
484 MasterBootRecord
* mbr
= (MasterBootRecord
*)buffer
;
485 if (!_IsValid(mbr
)) {
491 if (file
->Write(buffer
, size
) != size
)
500 LegacyBootMenu::RestoreMasterBootRecord(BMessage
* settings
, BFile
* file
)
503 if (settings
->FindString("disk", &path
) != B_OK
)
506 int fd
= open(path
.String(), O_RDWR
);
510 MasterBootRecord oldMBR
;
511 if (read(fd
, &oldMBR
, sizeof(oldMBR
)) != sizeof(oldMBR
)) {
515 if (!_IsValid(&oldMBR
)) {
520 lseek(fd
, 0, SEEK_SET
);
522 size_t size
= kBlockSize
* kNumberOfBootLoaderBlocks
;
523 uint8
* buffer
= new(std::nothrow
) uint8
[size
];
524 if (buffer
== NULL
) {
529 if (file
->Read(buffer
, size
) != (ssize_t
)size
) {
535 MasterBootRecord
* newMBR
= (MasterBootRecord
*)buffer
;
536 if (!_IsValid(newMBR
)) {
542 _CopyPartitionTable(newMBR
, &oldMBR
);
544 status_t status
= _WriteBlocks(fd
, buffer
, size
);
552 LegacyBootMenu::GetDisplayText(const char* text
, BString
& displayText
)
555 if (!_ConvertToBIOSText(text
, biosText
)) {
560 // convert back to UTF-8
561 int32 biosTextLength
= biosText
.Length();
562 int32 bufferLength
= strlen(text
);
563 char* buffer
= displayText
.LockBuffer(bufferLength
+ 1);
565 if (convert_to_utf8(B_MS_DOS_CONVERSION
,
566 biosText
.String(), &biosTextLength
,
567 buffer
, &bufferLength
, &state
) != B_OK
) {
568 displayText
.UnlockBuffer(0);
573 buffer
[bufferLength
] = '\0';
574 displayText
.UnlockBuffer(bufferLength
);
580 LegacyBootMenu::_ConvertToBIOSText(const char* text
, BString
& biosText
)
582 // convert text in UTF-8 to 'code page 437'
583 int32 textLength
= strlen(text
);
585 int32 biosTextLength
= textLength
;
586 char* buffer
= biosText
.LockBuffer(biosTextLength
+ 1);
587 if (buffer
== NULL
) {
588 biosText
.UnlockBuffer(0);
593 if (convert_from_utf8(B_MS_DOS_CONVERSION
, text
, &textLength
,
594 buffer
, &biosTextLength
, &state
) != B_OK
) {
595 biosText
.UnlockBuffer(0);
599 buffer
[biosTextLength
] = '\0';
600 biosText
.UnlockBuffer(biosTextLength
);
601 return biosTextLength
< kMaxBootMenuItemLength
;
606 LegacyBootMenu::_GetBIOSDrive(const char* device
, int8
& drive
)
608 int fd
= open(device
, O_RDONLY
);
612 status_t status
= ioctl(fd
, B_GET_BIOS_DRIVE_ID
, drive
, 1);
619 LegacyBootMenu::_ReadBlocks(int fd
, uint8
* buffer
, size_t size
)
621 if (size
% kBlockSize
!= 0) {
622 fprintf(stderr
, "_ReadBlocks buffer size must be a multiple of %d\n",
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
)
637 LegacyBootMenu::_WriteBlocks(int fd
, const uint8
* buffer
, size_t size
)
639 if (size
% kBlockSize
!= 0) {
640 fprintf(stderr
, "_WriteBlocks buffer size must be a multiple of %d\n",
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
)
655 LegacyBootMenu::_CopyPartitionTable(MasterBootRecord
* destination
,
656 const MasterBootRecord
* source
)
658 memcpy(destination
->diskSignature
, source
->diskSignature
,
659 sizeof(source
->diskSignature
) + sizeof(source
->reserved
)
660 + sizeof(source
->partition
));
665 LegacyBootMenu::_IsValid(const MasterBootRecord
* mbr
)
667 return mbr
->signature
[0] == (kMBRSignature
& 0xff)
668 && mbr
->signature
[1] == (kMBRSignature
>> 8);