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 if (device.IsReadOnlyMedia()) 329 return B_READ_ONLY_DEVICE; 330 331 return B_OK; 332 } 333 334 335 status_t 336 LegacyBootMenu::CollectPartitions(const BootDrive& drive, BMessage& settings) 337 { 338 status_t status = B_ERROR; 339 340 // Remove previous partitions, if any 341 settings.RemoveName("partition"); 342 343 BDiskDeviceRoster diskDeviceRoster; 344 BDiskDevice device; 345 bool partitionsFound = false; 346 347 while (diskDeviceRoster.GetNextDevice(&device) == B_OK) { 348 BPath path; 349 status_t status = device.GetPath(&path); 350 if (status != B_OK) 351 continue; 352 353 // Skip not from BIOS bootable drives that are not the target disk 354 int8 biosDrive = 0; 355 if (path != drive.Path() 356 && _GetBIOSDrive(path.Path(), biosDrive) != B_OK) 357 continue; 358 359 PartitionRecorder recorder(settings, biosDrive); 360 device.VisitEachDescendant(&recorder); 361 362 partitionsFound |= recorder.FoundPartitions(); 363 } 364 365 return partitionsFound ? B_OK : status; 366 } 367 368 369 status_t 370 LegacyBootMenu::Install(const BootDrive& drive, BMessage& settings) 371 { 372 int32 defaultPartitionIndex; 373 if (settings.FindInt32("defaultPartition", &defaultPartitionIndex) != B_OK) 374 return B_BAD_VALUE; 375 376 int32 timeout; 377 if (settings.FindInt32("timeout", &timeout) != B_OK) 378 return B_BAD_VALUE; 379 380 int fd = open(drive.Path(), O_RDWR); 381 if (fd < 0) 382 return B_IO_ERROR; 383 384 MasterBootRecord oldMBR; 385 if (read(fd, &oldMBR, sizeof(oldMBR)) != sizeof(oldMBR)) { 386 close(fd); 387 return B_IO_ERROR; 388 } 389 390 if (!_IsValid(&oldMBR)) { 391 close(fd); 392 return B_BAD_VALUE; 393 } 394 395 LittleEndianMallocIO newBootLoader; 396 ssize_t size = sizeof(kBootLoader); 397 if (newBootLoader.Write(kBootLoader, size) != size) { 398 close(fd); 399 return B_NO_MEMORY; 400 } 401 402 MasterBootRecord* newMBR = (MasterBootRecord*)newBootLoader.Buffer(); 403 _CopyPartitionTable(newMBR, &oldMBR); 404 405 int menuEntries = 0; 406 int defaultMenuEntry = 0; 407 BMessage partition; 408 int32 index; 409 for (index = 0; settings.FindMessage("partition", index, 410 &partition) == B_OK; index ++) { 411 bool show; 412 partition.FindBool("show", &show); 413 if (!show) 414 continue; 415 if (index == defaultPartitionIndex) 416 defaultMenuEntry = menuEntries; 417 418 menuEntries ++; 419 } 420 newBootLoader.WriteInt16(menuEntries); 421 newBootLoader.WriteInt16(defaultMenuEntry); 422 newBootLoader.WriteInt16(timeout); 423 424 for (index = 0; settings.FindMessage("partition", index, 425 &partition) == B_OK; index ++) { 426 bool show; 427 BString name; 428 BString path; 429 int64 offset; 430 int8 drive; 431 partition.FindBool("show", &show); 432 partition.FindString("name", &name); 433 partition.FindString("path", &path); 434 // LegacyBootMenu specific data 435 partition.FindInt64("offset", &offset); 436 partition.FindInt8("drive", &drive); 437 if (!show) 438 continue; 439 440 BString biosName; 441 _ConvertToBIOSText(name.String(), biosName); 442 443 newBootLoader.WriteString(biosName.String()); 444 newBootLoader.WriteInt8(drive); 445 newBootLoader.WriteInt64(offset / kBlockSize); 446 } 447 448 if (!newBootLoader.Align(kBlockSize)) { 449 close(fd); 450 return B_ERROR; 451 } 452 453 lseek(fd, 0, SEEK_SET); 454 const uint8* buffer = (const uint8*)newBootLoader.Buffer(); 455 status_t status = _WriteBlocks(fd, buffer, newBootLoader.Position()); 456 close(fd); 457 return status; 458 } 459 460 461 status_t 462 LegacyBootMenu::SaveMasterBootRecord(BMessage* settings, BFile* file) 463 { 464 BString path; 465 466 if (settings->FindString("disk", &path) != B_OK) 467 return B_BAD_VALUE; 468 469 int fd = open(path.String(), O_RDONLY); 470 if (fd < 0) 471 return B_IO_ERROR; 472 473 ssize_t size = kBlockSize * kNumberOfBootLoaderBlocks; 474 uint8* buffer = new(std::nothrow) uint8[size]; 475 if (buffer == NULL) { 476 close(fd); 477 return B_NO_MEMORY; 478 } 479 480 status_t status = _ReadBlocks(fd, buffer, size); 481 if (status != B_OK) { 482 close(fd); 483 delete[] buffer; 484 return B_IO_ERROR; 485 } 486 487 MasterBootRecord* mbr = (MasterBootRecord*)buffer; 488 if (!_IsValid(mbr)) { 489 close(fd); 490 delete[] buffer; 491 return B_BAD_VALUE; 492 } 493 494 if (file->Write(buffer, size) != size) 495 status = B_IO_ERROR; 496 delete[] buffer; 497 close(fd); 498 return status; 499 } 500 501 502 status_t 503 LegacyBootMenu::RestoreMasterBootRecord(BMessage* settings, BFile* file) 504 { 505 BString path; 506 if (settings->FindString("disk", &path) != B_OK) 507 return B_BAD_VALUE; 508 509 int fd = open(path.String(), O_RDWR); 510 if (fd < 0) 511 return B_IO_ERROR; 512 513 MasterBootRecord oldMBR; 514 if (read(fd, &oldMBR, sizeof(oldMBR)) != sizeof(oldMBR)) { 515 close(fd); 516 return B_IO_ERROR; 517 } 518 if (!_IsValid(&oldMBR)) { 519 close(fd); 520 return B_BAD_VALUE; 521 } 522 523 lseek(fd, 0, SEEK_SET); 524 525 size_t size = kBlockSize * kNumberOfBootLoaderBlocks; 526 uint8* buffer = new(std::nothrow) uint8[size]; 527 if (buffer == NULL) { 528 close(fd); 529 return B_NO_MEMORY; 530 } 531 532 if (file->Read(buffer, size) != (ssize_t)size) { 533 close(fd); 534 delete[] buffer; 535 return B_IO_ERROR; 536 } 537 538 MasterBootRecord* newMBR = (MasterBootRecord*)buffer; 539 if (!_IsValid(newMBR)) { 540 close(fd); 541 delete[] buffer; 542 return B_BAD_VALUE; 543 } 544 545 _CopyPartitionTable(newMBR, &oldMBR); 546 547 status_t status = _WriteBlocks(fd, buffer, size); 548 delete[] buffer; 549 close(fd); 550 return status; 551 } 552 553 554 status_t 555 LegacyBootMenu::GetDisplayText(const char* text, BString& displayText) 556 { 557 BString biosText; 558 if (!_ConvertToBIOSText(text, biosText)) { 559 displayText = "???"; 560 return B_ERROR; 561 } 562 563 // convert back to UTF-8 564 int32 biosTextLength = biosText.Length(); 565 int32 bufferLength = strlen(text); 566 char* buffer = displayText.LockBuffer(bufferLength + 1); 567 int32 state = 0; 568 if (convert_to_utf8(B_MS_DOS_CONVERSION, 569 biosText.String(), &biosTextLength, 570 buffer, &bufferLength, &state) != B_OK) { 571 displayText.UnlockBuffer(0); 572 displayText = "???"; 573 return B_ERROR; 574 } 575 576 buffer[bufferLength] = '\0'; 577 displayText.UnlockBuffer(bufferLength); 578 return B_OK; 579 } 580 581 582 bool 583 LegacyBootMenu::_ConvertToBIOSText(const char* text, BString& biosText) 584 { 585 // convert text in UTF-8 to 'code page 437' 586 int32 textLength = strlen(text); 587 588 int32 biosTextLength = textLength; 589 char* buffer = biosText.LockBuffer(biosTextLength + 1); 590 if (buffer == NULL) { 591 biosText.UnlockBuffer(0); 592 return false; 593 } 594 595 int32 state = 0; 596 if (convert_from_utf8(B_MS_DOS_CONVERSION, text, &textLength, 597 buffer, &biosTextLength, &state) != B_OK) { 598 biosText.UnlockBuffer(0); 599 return false; 600 } 601 602 buffer[biosTextLength] = '\0'; 603 biosText.UnlockBuffer(biosTextLength); 604 return biosTextLength < kMaxBootMenuItemLength; 605 } 606 607 608 status_t 609 LegacyBootMenu::_GetBIOSDrive(const char* device, int8& drive) 610 { 611 int fd = open(device, O_RDONLY); 612 if (fd < 0) 613 return errno; 614 615 status_t status = ioctl(fd, B_GET_BIOS_DRIVE_ID, drive, 1); 616 close(fd); 617 return status; 618 } 619 620 621 status_t 622 LegacyBootMenu::_ReadBlocks(int fd, uint8* buffer, size_t size) 623 { 624 if (size % kBlockSize != 0) { 625 fprintf(stderr, "_ReadBlocks buffer size must be a multiple of %d\n", 626 (int)kBlockSize); 627 return B_BAD_VALUE; 628 } 629 const size_t blocks = size / kBlockSize; 630 uint8* block = buffer; 631 for (size_t i = 0; i < blocks; i ++, block += kBlockSize) { 632 if (read(fd, block, kBlockSize) != (ssize_t)kBlockSize) 633 return B_IO_ERROR; 634 } 635 return B_OK; 636 } 637 638 639 status_t 640 LegacyBootMenu::_WriteBlocks(int fd, const uint8* buffer, size_t size) 641 { 642 if (size % kBlockSize != 0) { 643 fprintf(stderr, "_WriteBlocks buffer size must be a multiple of %d\n", 644 (int)kBlockSize); 645 return B_BAD_VALUE; 646 } 647 const size_t blocks = size / kBlockSize; 648 const uint8* block = buffer; 649 for (size_t i = 0; i < blocks; i ++, block += kBlockSize) { 650 if (write(fd, block, kBlockSize) != (ssize_t)kBlockSize) 651 return B_IO_ERROR; 652 } 653 return B_OK; 654 } 655 656 657 void 658 LegacyBootMenu::_CopyPartitionTable(MasterBootRecord* destination, 659 const MasterBootRecord* source) 660 { 661 memcpy(destination->diskSignature, source->diskSignature, 662 sizeof(source->diskSignature) + sizeof(source->reserved) 663 + sizeof(source->partition)); 664 } 665 666 667 bool 668 LegacyBootMenu::_IsValid(const MasterBootRecord* mbr) 669 { 670 return mbr->signature[0] == (kMBRSignature & 0xff) 671 && mbr->signature[1] == (kMBRSignature >> 8); 672 } 673