repository_infos: Enable automatic updates on the main Haiku repostiory.
[haiku.git] / src / apps / bootmanager / LegacyBootMenu.cpp
blob185b31d3761700ef1864c71e3699f5308561f5d6
1 /*
2 * Copyright 2008-2011, Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
5 * Authors:
6 * Axel Dörfler, axeld@pinc-software.de
7 * Michael Pfeiffer <laplace@users.sourceforge.net>
8 */
11 #include "LegacyBootMenu.h"
13 #include <new>
15 #include <errno.h>
16 #include <stdio.h>
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>
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
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
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];
55 uint8 reserved[2];
56 uint8 partition[64];
57 uint8 signature[2];
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);
73 class PartitionVisitor : public BDiskDeviceVisitor {
74 public:
75 PartitionVisitor();
77 virtual bool Visit(BDiskDevice* device);
78 virtual bool Visit(BPartition* partition, int32 level);
80 bool HasPartitions() const;
81 off_t FirstOffset() const;
83 private:
84 off_t fFirstOffset;
88 class PartitionRecorder : public BDiskDeviceVisitor {
89 public:
90 PartitionRecorder(BMessage& settings,
91 int8 biosDrive);
93 virtual bool Visit(BDiskDevice* device);
94 virtual bool Visit(BPartition* partition, int32 level);
96 bool FoundPartitions() const;
98 private:
99 BMessage& fSettings;
100 int32 fUnnamedIndex;
101 int8 fBIOSDrive;
102 bool fFound;
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;
116 bool
117 LittleEndianMallocIO::WriteInt8(int8 value)
119 return Write(&value, sizeof(value)) == sizeof(value);
123 bool
124 LittleEndianMallocIO::WriteInt16(int16 value)
126 return WriteInt8(value & 0xff)
127 && WriteInt8(value >> 8);
131 bool
132 LittleEndianMallocIO::WriteInt32(int32 value)
134 return WriteInt8(value & 0xff)
135 && WriteInt8(value >> 8)
136 && WriteInt8(value >> 16)
137 && WriteInt8(value >> 24);
141 bool
142 LittleEndianMallocIO::WriteInt64(int64 value)
144 return WriteInt32(value) && WriteInt32(value >> 32);
148 bool
149 LittleEndianMallocIO::WriteString(const char* value)
151 int len = strlen(value) + 1;
152 return WriteInt8(len)
153 && Write(value, len) == len;
157 bool
158 LittleEndianMallocIO::Align(int16 alignment)
160 if ((Position() % alignment) == 0)
161 return true;
162 return Fill(alignment - (Position() % alignment), 0);
166 bool
167 LittleEndianMallocIO::Fill(int16 size, int8 fillByte)
169 for (int i = 0; i < size; i ++) {
170 if (!WriteInt8(fillByte))
171 return false;
173 return true;
177 // #pragma mark -
180 PartitionVisitor::PartitionVisitor()
182 fFirstOffset(LONGLONG_MAX)
187 bool
188 PartitionVisitor::Visit(BDiskDevice* device)
190 return false;
194 bool
195 PartitionVisitor::Visit(BPartition* partition, int32 level)
197 if (partition->Offset() < fFirstOffset)
198 fFirstOffset = partition->Offset();
200 return false;
204 bool
205 PartitionVisitor::HasPartitions() const
207 return fFirstOffset != LONGLONG_MAX;
211 off_t
212 PartitionVisitor::FirstOffset() const
214 return fFirstOffset;
218 // #pragma mark -
221 PartitionRecorder::PartitionRecorder(BMessage& settings, int8 biosDrive)
223 fSettings(settings),
224 fUnnamedIndex(0),
225 fBIOSDrive(biosDrive),
226 fFound(false)
231 bool
232 PartitionRecorder::Visit(BDiskDevice* device)
234 return false;
238 bool
239 PartitionRecorder::Visit(BPartition* partition, int32 level)
241 if (partition->ContainsPartitioningSystem())
242 return false;
244 BPath partitionPath;
245 partition->GetPath(&partitionPath);
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();
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");
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());
275 fSettings.AddMessage("partition", &message);
276 fFound = true;
278 return false;
282 bool
283 PartitionRecorder::FoundPartitions() const
285 return fFound;
289 // #pragma mark -
292 LegacyBootMenu::LegacyBootMenu()
297 LegacyBootMenu::~LegacyBootMenu()
302 bool
303 LegacyBootMenu::IsInstalled(const BootDrive& drive)
305 // TODO: detect bootman
306 return false;
310 status_t
311 LegacyBootMenu::CanBeInstalled(const BootDrive& drive)
313 BDiskDevice device;
314 status_t status = drive.GetDiskDevice(device);
315 if (status != B_OK)
316 return status;
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;
328 return B_OK;
332 status_t
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;
341 BDiskDevice device;
342 bool partitionsFound = false;
344 while (diskDeviceRoster.GetNextDevice(&device) == B_OK) {
345 BPath path;
346 status_t status = device.GetPath(&path);
347 if (status != B_OK)
348 continue;
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;
356 PartitionRecorder recorder(settings, biosDrive);
357 device.VisitEachDescendant(&recorder);
359 partitionsFound |= recorder.FoundPartitions();
362 return partitionsFound ? B_OK : status;
366 status_t
367 LegacyBootMenu::Install(const BootDrive& drive, BMessage& settings)
369 int32 defaultPartitionIndex;
370 if (settings.FindInt32("defaultPartition", &defaultPartitionIndex) != B_OK)
371 return B_BAD_VALUE;
373 int32 timeout;
374 if (settings.FindInt32("timeout", &timeout) != B_OK)
375 return B_BAD_VALUE;
377 int fd = open(drive.Path(), O_RDWR);
378 if (fd < 0)
379 return B_IO_ERROR;
381 MasterBootRecord oldMBR;
382 if (read(fd, &oldMBR, sizeof(oldMBR)) != sizeof(oldMBR)) {
383 close(fd);
384 return B_IO_ERROR;
387 if (!_IsValid(&oldMBR)) {
388 close(fd);
389 return B_BAD_VALUE;
392 LittleEndianMallocIO newBootLoader;
393 ssize_t size = sizeof(kBootLoader);
394 if (newBootLoader.Write(kBootLoader, size) != size) {
395 close(fd);
396 return B_NO_MEMORY;
399 MasterBootRecord* newMBR = (MasterBootRecord*)newBootLoader.Buffer();
400 _CopyPartitionTable(newMBR, &oldMBR);
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;
415 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 ++) {
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;
437 BString biosName;
438 _ConvertToBIOSText(name.String(), biosName);
440 newBootLoader.WriteString(biosName.String());
441 newBootLoader.WriteInt8(drive);
442 newBootLoader.WriteInt64(offset / kBlockSize);
445 if (!newBootLoader.Align(kBlockSize)) {
446 close(fd);
447 return B_ERROR;
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;
458 status_t
459 LegacyBootMenu::SaveMasterBootRecord(BMessage* settings, BFile* file)
461 BString path;
463 if (settings->FindString("disk", &path) != B_OK)
464 return B_BAD_VALUE;
466 int fd = open(path.String(), O_RDONLY);
467 if (fd < 0)
468 return B_IO_ERROR;
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;
477 status_t status = _ReadBlocks(fd, buffer, size);
478 if (status != B_OK) {
479 close(fd);
480 delete[] buffer;
481 return B_IO_ERROR;
484 MasterBootRecord* mbr = (MasterBootRecord*)buffer;
485 if (!_IsValid(mbr)) {
486 close(fd);
487 delete[] buffer;
488 return B_BAD_VALUE;
491 if (file->Write(buffer, size) != size)
492 status = B_IO_ERROR;
493 delete[] buffer;
494 close(fd);
495 return status;
499 status_t
500 LegacyBootMenu::RestoreMasterBootRecord(BMessage* settings, BFile* file)
502 BString path;
503 if (settings->FindString("disk", &path) != B_OK)
504 return B_BAD_VALUE;
506 int fd = open(path.String(), O_RDWR);
507 if (fd < 0)
508 return B_IO_ERROR;
510 MasterBootRecord oldMBR;
511 if (read(fd, &oldMBR, sizeof(oldMBR)) != sizeof(oldMBR)) {
512 close(fd);
513 return B_IO_ERROR;
515 if (!_IsValid(&oldMBR)) {
516 close(fd);
517 return B_BAD_VALUE;
520 lseek(fd, 0, SEEK_SET);
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;
529 if (file->Read(buffer, size) != (ssize_t)size) {
530 close(fd);
531 delete[] buffer;
532 return B_IO_ERROR;
535 MasterBootRecord* newMBR = (MasterBootRecord*)buffer;
536 if (!_IsValid(newMBR)) {
537 close(fd);
538 delete[] buffer;
539 return B_BAD_VALUE;
542 _CopyPartitionTable(newMBR, &oldMBR);
544 status_t status = _WriteBlocks(fd, buffer, size);
545 delete[] buffer;
546 close(fd);
547 return status;
551 status_t
552 LegacyBootMenu::GetDisplayText(const char* text, BString& displayText)
554 BString biosText;
555 if (!_ConvertToBIOSText(text, biosText)) {
556 displayText = "???";
557 return B_ERROR;
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;
573 buffer[bufferLength] = '\0';
574 displayText.UnlockBuffer(bufferLength);
575 return B_OK;
579 bool
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);
589 return false;
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;
599 buffer[biosTextLength] = '\0';
600 biosText.UnlockBuffer(biosTextLength);
601 return biosTextLength < kMaxBootMenuItemLength;
605 status_t
606 LegacyBootMenu::_GetBIOSDrive(const char* device, int8& drive)
608 int fd = open(device, O_RDONLY);
609 if (fd < 0)
610 return errno;
612 status_t status = ioctl(fd, B_GET_BIOS_DRIVE_ID, drive, 1);
613 close(fd);
614 return status;
618 status_t
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",
623 (int)kBlockSize);
624 return B_BAD_VALUE;
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;
632 return B_OK;
636 status_t
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",
641 (int)kBlockSize);
642 return B_BAD_VALUE;
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;
650 return B_OK;
654 void
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));
664 bool
665 LegacyBootMenu::_IsValid(const MasterBootRecord* mbr)
667 return mbr->signature[0] == (kMBRSignature & 0xff)
668 && mbr->signature[1] == (kMBRSignature >> 8);