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