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 118 LittleEndianMallocIO::WriteInt8(int8 value) 119 { 120 return Write(&value, sizeof(value)) == sizeof(value); 121 } 122 123 124 bool 125 LittleEndianMallocIO::WriteInt16(int16 value) 126 { 127 return WriteInt8(value & 0xff) 128 && WriteInt8(value >> 8); 129 } 130 131 132 bool 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 143 LittleEndianMallocIO::WriteInt64(int64 value) 144 { 145 return WriteInt32(value) && WriteInt32(value >> 32); 146 } 147 148 149 bool 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 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 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 181 PartitionVisitor::PartitionVisitor() 182 : 183 fFirstOffset(LONGLONG_MAX) 184 { 185 } 186 187 188 bool 189 PartitionVisitor::Visit(BDiskDevice* device) 190 { 191 return false; 192 } 193 194 195 bool 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 206 PartitionVisitor::HasPartitions() const 207 { 208 return fFirstOffset != LONGLONG_MAX; 209 } 210 211 212 off_t 213 PartitionVisitor::FirstOffset() const 214 { 215 return fFirstOffset; 216 } 217 218 219 // #pragma mark - 220 221 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 233 PartitionRecorder::Visit(BDiskDevice* device) 234 { 235 return false; 236 } 237 238 239 bool 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 284 PartitionRecorder::FoundPartitions() const 285 { 286 return fFound; 287 } 288 289 290 // #pragma mark - 291 292 293 LegacyBootMenu::LegacyBootMenu() 294 { 295 } 296 297 298 LegacyBootMenu::~LegacyBootMenu() 299 { 300 } 301 302 303 bool 304 LegacyBootMenu::IsInstalled(const BootDrive& drive) 305 { 306 // TODO: detect bootman 307 return false; 308 } 309 310 311 status_t 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 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 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 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 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 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 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 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 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 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 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 670 LegacyBootMenu::_IsValid(const MasterBootRecord* mbr) 671 { 672 return mbr->signature[0] == (kMBRSignature & 0xff) 673 && mbr->signature[1] == (kMBRSignature >> 8); 674 } 675