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 if (!visitor.HasPartitions()) 322 return B_ENTRY_NOT_FOUND; 323 324 // Enough space to write boot menu to drive? 325 if (visitor.FirstOffset() < (int)sizeof(kBootLoader)) 326 return B_PARTITION_TOO_SMALL; 327 328 return B_OK; 329 } 330 331 332 status_t 333 LegacyBootMenu::CollectPartitions(const BootDrive& drive, BMessage& settings) 334 { 335 status_t status = B_ERROR; 336 337 // Remove previous partitions, if any 338 settings.RemoveName("partition"); 339 340 BDiskDeviceRoster diskDeviceRoster; 341 BDiskDevice device; 342 bool partitionsFound = false; 343 344 while (diskDeviceRoster.GetNextDevice(&device) == B_OK) { 345 BPath path; 346 status_t status = device.GetPath(&path); 347 if (status != B_OK) 348 continue; 349 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; 355 356 PartitionRecorder recorder(settings, biosDrive); 357 device.VisitEachDescendant(&recorder); 358 359 partitionsFound |= recorder.FoundPartitions(); 360 } 361 362 return partitionsFound ? B_OK : status; 363 } 364 365 366 status_t 367 LegacyBootMenu::Install(const BootDrive& drive, BMessage& settings) 368 { 369 int32 defaultPartitionIndex; 370 if (settings.FindInt32("defaultPartition", &defaultPartitionIndex) != B_OK) 371 return B_BAD_VALUE; 372 373 int32 timeout; 374 if (settings.FindInt32("timeout", &timeout) != B_OK) 375 return B_BAD_VALUE; 376 377 int fd = open(drive.Path(), O_RDWR); 378 if (fd < 0) 379 return B_IO_ERROR; 380 381 MasterBootRecord oldMBR; 382 if (read(fd, &oldMBR, sizeof(oldMBR)) != sizeof(oldMBR)) { 383 close(fd); 384 return B_IO_ERROR; 385 } 386 387 if (!_IsValid(&oldMBR)) { 388 close(fd); 389 return B_BAD_VALUE; 390 } 391 392 LittleEndianMallocIO newBootLoader; 393 ssize_t size = sizeof(kBootLoader); 394 if (newBootLoader.Write(kBootLoader, size) != size) { 395 close(fd); 396 return B_NO_MEMORY; 397 } 398 399 MasterBootRecord* newMBR = (MasterBootRecord*)newBootLoader.Buffer(); 400 _CopyPartitionTable(newMBR, &oldMBR); 401 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; 414 415 menuEntries ++; 416 } 417 newBootLoader.WriteInt16(menuEntries); 418 newBootLoader.WriteInt16(defaultMenuEntry); 419 newBootLoader.WriteInt16(timeout); 420 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; 436 437 BString biosName; 438 _ConvertToBIOSText(name.String(), biosName); 439 440 newBootLoader.WriteString(biosName.String()); 441 newBootLoader.WriteInt8(drive); 442 newBootLoader.WriteInt64(offset / kBlockSize); 443 } 444 445 if (!newBootLoader.Align(kBlockSize)) { 446 close(fd); 447 return B_ERROR; 448 } 449 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; 455 } 456 457 458 status_t 459 LegacyBootMenu::SaveMasterBootRecord(BMessage* settings, BFile* file) 460 { 461 BString path; 462 463 if (settings->FindString("disk", &path) != B_OK) 464 return B_BAD_VALUE; 465 466 int fd = open(path.String(), O_RDONLY); 467 if (fd < 0) 468 return B_IO_ERROR; 469 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; 475 } 476 477 status_t status = _ReadBlocks(fd, buffer, size); 478 if (status != B_OK) { 479 close(fd); 480 delete[] buffer; 481 return B_IO_ERROR; 482 } 483 484 MasterBootRecord* mbr = (MasterBootRecord*)buffer; 485 if (!_IsValid(mbr)) { 486 close(fd); 487 delete[] buffer; 488 return B_BAD_VALUE; 489 } 490 491 if (file->Write(buffer, size) != size) 492 status = B_IO_ERROR; 493 delete[] buffer; 494 close(fd); 495 return status; 496 } 497 498 499 status_t 500 LegacyBootMenu::RestoreMasterBootRecord(BMessage* settings, BFile* file) 501 { 502 BString path; 503 if (settings->FindString("disk", &path) != B_OK) 504 return B_BAD_VALUE; 505 506 int fd = open(path.String(), O_RDWR); 507 if (fd < 0) 508 return B_IO_ERROR; 509 510 MasterBootRecord oldMBR; 511 if (read(fd, &oldMBR, sizeof(oldMBR)) != sizeof(oldMBR)) { 512 close(fd); 513 return B_IO_ERROR; 514 } 515 if (!_IsValid(&oldMBR)) { 516 close(fd); 517 return B_BAD_VALUE; 518 } 519 520 lseek(fd, 0, SEEK_SET); 521 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; 527 } 528 529 if (file->Read(buffer, size) != (ssize_t)size) { 530 close(fd); 531 delete[] buffer; 532 return B_IO_ERROR; 533 } 534 535 MasterBootRecord* newMBR = (MasterBootRecord*)buffer; 536 if (!_IsValid(newMBR)) { 537 close(fd); 538 delete[] buffer; 539 return B_BAD_VALUE; 540 } 541 542 _CopyPartitionTable(newMBR, &oldMBR); 543 544 status_t status = _WriteBlocks(fd, buffer, size); 545 delete[] buffer; 546 close(fd); 547 return status; 548 } 549 550 551 status_t 552 LegacyBootMenu::GetDisplayText(const char* text, BString& displayText) 553 { 554 BString biosText; 555 if (!_ConvertToBIOSText(text, biosText)) { 556 displayText = "???"; 557 return B_ERROR; 558 } 559 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; 571 } 572 573 buffer[bufferLength] = '\0'; 574 displayText.UnlockBuffer(bufferLength); 575 return B_OK; 576 } 577 578 579 bool 580 LegacyBootMenu::_ConvertToBIOSText(const char* text, BString& biosText) 581 { 582 // convert text in UTF-8 to 'code page 437' 583 int32 textLength = strlen(text); 584 585 int32 biosTextLength = textLength; 586 char* buffer = biosText.LockBuffer(biosTextLength + 1); 587 if (buffer == NULL) { 588 biosText.UnlockBuffer(0); 589 return false; 590 } 591 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; 597 } 598 599 buffer[biosTextLength] = '\0'; 600 biosText.UnlockBuffer(biosTextLength); 601 return biosTextLength < kMaxBootMenuItemLength; 602 } 603 604 605 status_t 606 LegacyBootMenu::_GetBIOSDrive(const char* device, int8& drive) 607 { 608 int fd = open(device, O_RDONLY); 609 if (fd < 0) 610 return errno; 611 612 status_t status = ioctl(fd, B_GET_BIOS_DRIVE_ID, drive, 1); 613 close(fd); 614 return status; 615 } 616 617 618 status_t 619 LegacyBootMenu::_ReadBlocks(int fd, uint8* buffer, size_t size) 620 { 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; 625 } 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; 631 } 632 return B_OK; 633 } 634 635 636 status_t 637 LegacyBootMenu::_WriteBlocks(int fd, const uint8* buffer, size_t size) 638 { 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; 643 } 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; 649 } 650 return B_OK; 651 } 652 653 654 void 655 LegacyBootMenu::_CopyPartitionTable(MasterBootRecord* destination, 656 const MasterBootRecord* source) 657 { 658 memcpy(destination->diskSignature, source->diskSignature, 659 sizeof(source->diskSignature) + sizeof(source->reserved) 660 + sizeof(source->partition)); 661 } 662 663 664 bool 665 LegacyBootMenu::_IsValid(const MasterBootRecord* mbr) 666 { 667 return mbr->signature[0] == (kMBRSignature & 0xff) 668 && mbr->signature[1] == (kMBRSignature >> 8); 669 } 670