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