1 /* 2 * Copyright 2003-2013, Axel Dörfler, axeld@pinc-software.de. 3 * Copyright 2011, Rene Gollent, rene@gollent.com. 4 * Copyright 2013-2014, Ingo Weinhold, ingo_weinhold@gmx.de. 5 * Distributed under the terms of the MIT License. 6 */ 7 8 9 #include "menu.h" 10 11 #include <errno.h> 12 #include <strings.h> 13 14 #include <algorithm> 15 16 #include <OS.h> 17 18 #include <AutoDeleter.h> 19 #include <boot/menu.h> 20 #include <boot/PathBlacklist.h> 21 #include <boot/stage2.h> 22 #include <boot/vfs.h> 23 #include <boot/platform.h> 24 #include <boot/platform/generic/text_console.h> 25 #include <boot/stdio.h> 26 #include <safemode.h> 27 #include <util/ring_buffer.h> 28 #include <util/SinglyLinkedList.h> 29 30 #include "kernel_debug_config.h" 31 32 #include "load_driver_settings.h" 33 #include "loader.h" 34 #include "package_support.h" 35 #include "pager.h" 36 #include "RootFileSystem.h" 37 38 39 //#define TRACE_MENU 40 #ifdef TRACE_MENU 41 # define TRACE(x) dprintf x 42 #else 43 # define TRACE(x) ; 44 #endif 45 46 47 // only set while in user_menu() 48 static Menu* sMainMenu = NULL; 49 static Menu* sBlacklistRootMenu = NULL; 50 static BootVolume* sBootVolume = NULL; 51 static PathBlacklist* sPathBlacklist; 52 53 54 MenuItem::MenuItem(const char *label, Menu *subMenu) 55 : 56 fLabel(strdup(label)), 57 fTarget(NULL), 58 fIsMarked(false), 59 fIsSelected(false), 60 fIsEnabled(true), 61 fType(MENU_ITEM_STANDARD), 62 fMenu(NULL), 63 fSubMenu(NULL), 64 fData(NULL), 65 fHelpText(NULL), 66 fShortcut(0) 67 { 68 SetSubmenu(subMenu); 69 } 70 71 72 MenuItem::~MenuItem() 73 { 74 delete fSubMenu; 75 free(const_cast<char *>(fLabel)); 76 } 77 78 79 void 80 MenuItem::SetTarget(menu_item_hook target) 81 { 82 fTarget = target; 83 } 84 85 86 /** Marks or unmarks a menu item. A marked menu item usually gets a visual 87 * clue like a checkmark that distinguishes it from others. 88 * For menus of type CHOICE_MENU, there can only be one marked item - the 89 * chosen one. 90 */ 91 92 void 93 MenuItem::SetMarked(bool marked) 94 { 95 if (marked && fMenu != NULL && fMenu->Type() == CHOICE_MENU) { 96 // always set choice text of parent if we were marked 97 fMenu->SetChoiceText(Label()); 98 } 99 100 if (fIsMarked == marked) 101 return; 102 103 if (marked && fMenu != NULL && fMenu->Type() == CHOICE_MENU) { 104 // unmark previous item 105 MenuItem *markedItem = fMenu->FindMarked(); 106 if (markedItem != NULL) 107 markedItem->SetMarked(false); 108 } 109 110 fIsMarked = marked; 111 112 if (fMenu != NULL) 113 fMenu->Draw(this); 114 } 115 116 117 void 118 MenuItem::Select(bool selected) 119 { 120 if (fIsSelected == selected) 121 return; 122 123 if (selected && fMenu != NULL) { 124 // unselect previous item 125 MenuItem *selectedItem = fMenu->FindSelected(); 126 if (selectedItem != NULL) 127 selectedItem->Select(false); 128 } 129 130 fIsSelected = selected; 131 132 if (fMenu != NULL) 133 fMenu->Draw(this); 134 } 135 136 137 void 138 MenuItem::SetType(menu_item_type type) 139 { 140 fType = type; 141 } 142 143 144 void 145 MenuItem::SetEnabled(bool enabled) 146 { 147 if (fIsEnabled == enabled) 148 return; 149 150 fIsEnabled = enabled; 151 152 if (fMenu != NULL) 153 fMenu->Draw(this); 154 } 155 156 157 void 158 MenuItem::SetData(const void *data) 159 { 160 fData = data; 161 } 162 163 164 /*! This sets a help text that is shown when the item is 165 selected. 166 Note, unlike the label, the string is not copied, it's 167 just referenced and has to stay valid as long as the 168 item's menu is being used. 169 */ 170 void 171 MenuItem::SetHelpText(const char* text) 172 { 173 fHelpText = text; 174 } 175 176 177 void 178 MenuItem::SetShortcut(char key) 179 { 180 fShortcut = key; 181 } 182 183 184 void 185 MenuItem::SetLabel(const char* label) 186 { 187 if (char* newLabel = strdup(label)) { 188 free(const_cast<char*>(fLabel)); 189 fLabel = newLabel; 190 } 191 } 192 193 194 void 195 MenuItem::SetSubmenu(Menu* subMenu) 196 { 197 fSubMenu = subMenu; 198 199 if (fSubMenu != NULL) 200 fSubMenu->fSuperItem = this; 201 } 202 203 204 void 205 MenuItem::SetMenu(Menu* menu) 206 { 207 fMenu = menu; 208 } 209 210 211 // #pragma mark - 212 213 214 Menu::Menu(menu_type type, const char* title) 215 : 216 fTitle(title), 217 fChoiceText(NULL), 218 fCount(0), 219 fIsHidden(true), 220 fType(type), 221 fSuperItem(NULL), 222 fShortcuts(NULL) 223 { 224 } 225 226 227 Menu::~Menu() 228 { 229 // take all remaining items with us 230 231 MenuItem *item; 232 while ((item = fItems.Head()) != NULL) { 233 fItems.Remove(item); 234 delete item; 235 } 236 } 237 238 239 void 240 Menu::Entered() 241 { 242 } 243 244 245 void 246 Menu::Exited() 247 { 248 } 249 250 251 MenuItem* 252 Menu::ItemAt(int32 index) 253 { 254 if (index < 0 || index >= fCount) 255 return NULL; 256 257 MenuItemIterator iterator = ItemIterator(); 258 MenuItem *item; 259 260 while ((item = iterator.Next()) != NULL) { 261 if (index-- == 0) 262 return item; 263 } 264 265 return NULL; 266 } 267 268 269 int32 270 Menu::IndexOf(MenuItem* searchedItem) 271 { 272 int32 index = 0; 273 274 MenuItemIterator iterator = ItemIterator(); 275 while (MenuItem* item = iterator.Next()) { 276 if (item == searchedItem) 277 return index; 278 279 index++; 280 } 281 282 return -1; 283 } 284 285 286 int32 287 Menu::CountItems() const 288 { 289 return fCount; 290 } 291 292 293 MenuItem* 294 Menu::FindItem(const char* label) 295 { 296 MenuItemIterator iterator = ItemIterator(); 297 while (MenuItem* item = iterator.Next()) { 298 if (item->Label() != NULL && !strcmp(item->Label(), label)) 299 return item; 300 } 301 302 return NULL; 303 } 304 305 306 MenuItem* 307 Menu::FindMarked() 308 { 309 MenuItemIterator iterator = ItemIterator(); 310 while (MenuItem* item = iterator.Next()) { 311 if (item->IsMarked()) 312 return item; 313 } 314 315 return NULL; 316 } 317 318 319 MenuItem* 320 Menu::FindSelected(int32* _index) 321 { 322 int32 index = 0; 323 324 MenuItemIterator iterator = ItemIterator(); 325 while (MenuItem* item = iterator.Next()) { 326 if (item->IsSelected()) { 327 if (_index != NULL) 328 *_index = index; 329 return item; 330 } 331 332 index++; 333 } 334 335 return NULL; 336 } 337 338 339 void 340 Menu::AddItem(MenuItem* item) 341 { 342 item->fMenu = this; 343 fItems.Add(item); 344 fCount++; 345 } 346 347 348 status_t 349 Menu::AddSeparatorItem() 350 { 351 MenuItem* item = new(std::nothrow) MenuItem(); 352 if (item == NULL) 353 return B_NO_MEMORY; 354 355 item->SetType(MENU_ITEM_SEPARATOR); 356 357 AddItem(item); 358 return B_OK; 359 } 360 361 362 MenuItem* 363 Menu::RemoveItemAt(int32 index) 364 { 365 if (index < 0 || index >= fCount) 366 return NULL; 367 368 MenuItemIterator iterator = ItemIterator(); 369 while (MenuItem* item = iterator.Next()) { 370 if (index-- == 0) { 371 RemoveItem(item); 372 return item; 373 } 374 } 375 376 return NULL; 377 } 378 379 380 void 381 Menu::RemoveItem(MenuItem* item) 382 { 383 item->fMenu = NULL; 384 fItems.Remove(item); 385 fCount--; 386 } 387 388 389 void 390 Menu::AddShortcut(char key, shortcut_hook function) 391 { 392 Menu::shortcut* shortcut = new(std::nothrow) Menu::shortcut; 393 if (shortcut == NULL) 394 return; 395 396 shortcut->key = key; 397 shortcut->function = function; 398 399 shortcut->next = fShortcuts; 400 fShortcuts = shortcut; 401 } 402 403 404 shortcut_hook 405 Menu::FindShortcut(char key) const 406 { 407 if (key == 0) 408 return NULL; 409 410 const Menu::shortcut* shortcut = fShortcuts; 411 while (shortcut != NULL) { 412 if (shortcut->key == key) 413 return shortcut->function; 414 415 shortcut = shortcut->next; 416 } 417 418 Menu *superMenu = Supermenu(); 419 420 if (superMenu != NULL) 421 return superMenu->FindShortcut(key); 422 423 return NULL; 424 } 425 426 427 MenuItem* 428 Menu::FindItemByShortcut(char key) 429 { 430 if (key == 0) 431 return NULL; 432 433 MenuItemList::Iterator iterator = ItemIterator(); 434 while (MenuItem* item = iterator.Next()) { 435 if (item->Shortcut() == key) 436 return item; 437 } 438 439 Menu *superMenu = Supermenu(); 440 441 if (superMenu != NULL) 442 return superMenu->FindItemByShortcut(key); 443 444 return NULL; 445 } 446 447 448 void 449 Menu::SortItems(bool (*less)(const MenuItem*, const MenuItem*)) 450 { 451 fItems.Sort(less); 452 } 453 454 455 void 456 Menu::Run() 457 { 458 platform_run_menu(this); 459 } 460 461 462 void 463 Menu::Draw(MenuItem* item) 464 { 465 if (!IsHidden()) 466 platform_update_menu_item(this, item); 467 } 468 469 470 // #pragma mark - 471 472 473 static const char* 474 size_to_string(off_t size, char* buffer, size_t bufferSize) 475 { 476 static const char* const kPrefixes[] = { "K", "M", "G", "T", "P", NULL }; 477 int32 nextIndex = 0; 478 int32 remainder = 0; 479 while (size >= 1024 && kPrefixes[nextIndex] != NULL) { 480 remainder = size % 1024; 481 size /= 1024; 482 nextIndex++; 483 484 if (size < 1024) { 485 // Compute the decimal remainder and make sure we have at most 486 // 3 decimal places (or 4 for 1000 <= size <= 1023). 487 int32 factor; 488 if (size >= 100) 489 factor = 100; 490 else if (size >= 10) 491 factor = 10; 492 else 493 factor = 1; 494 495 remainder = (remainder * 1000 + 5 * factor) / 1024; 496 497 if (remainder >= 1000) { 498 size++; 499 remainder = 0; 500 } else 501 remainder /= 10 * factor; 502 } else 503 size += (remainder + 512) / 1024; 504 } 505 506 if (remainder == 0) { 507 snprintf(buffer, bufferSize, "%" B_PRIdOFF, size); 508 } else { 509 snprintf(buffer, bufferSize, "%" B_PRIdOFF ".%" B_PRId32, size, 510 remainder); 511 } 512 513 size_t length = strlen(buffer); 514 snprintf(buffer + length, bufferSize - length, " %sB", 515 nextIndex == 0 ? "" : kPrefixes[nextIndex - 1]); 516 517 return buffer; 518 } 519 520 521 // #pragma mark - blacklist menu 522 523 524 class BlacklistMenuItem : public MenuItem { 525 public: 526 BlacklistMenuItem(char* label, Node* node, Menu* subMenu) 527 : 528 MenuItem(label, subMenu), 529 fNode(node), 530 fSubMenu(subMenu) 531 { 532 fNode->Acquire(); 533 SetType(MENU_ITEM_MARKABLE); 534 } 535 536 ~BlacklistMenuItem() 537 { 538 fNode->Release(); 539 540 // make sure the submenu is destroyed 541 SetSubmenu(fSubMenu); 542 } 543 544 bool IsDirectoryItem() const 545 { 546 return fNode->Type() == S_IFDIR; 547 } 548 549 bool GetPath(BlacklistedPath& _path) const 550 { 551 Menu* menu = Supermenu(); 552 if (menu != NULL && menu != sBlacklistRootMenu 553 && menu->Superitem() != NULL) { 554 return static_cast<BlacklistMenuItem*>(menu->Superitem()) 555 ->GetPath(_path) 556 && _path.Append(Label()); 557 } 558 559 return _path.SetTo(Label()); 560 } 561 562 void UpdateBlacklisted() 563 { 564 BlacklistedPath path; 565 if (GetPath(path)) 566 _SetMarked(sPathBlacklist->Contains(path.Path()), false); 567 } 568 569 virtual void SetMarked(bool marked) 570 { 571 _SetMarked(marked, true); 572 } 573 574 static bool Less(const MenuItem* a, const MenuItem* b) 575 { 576 const BlacklistMenuItem* item1 577 = static_cast<const BlacklistMenuItem*>(a); 578 const BlacklistMenuItem* item2 579 = static_cast<const BlacklistMenuItem*>(b); 580 581 // directories come first 582 if (item1->IsDirectoryItem() != item2->IsDirectoryItem()) 583 return item1->IsDirectoryItem(); 584 585 // compare the labels 586 return strcasecmp(item1->Label(), item2->Label()) < 0; 587 } 588 589 private: 590 void _SetMarked(bool marked, bool updateBlacklist) 591 { 592 if (marked == IsMarked()) 593 return; 594 595 // For directories toggle the availability of the submenu. 596 if (IsDirectoryItem()) 597 SetSubmenu(marked ? NULL : fSubMenu); 598 599 if (updateBlacklist) { 600 BlacklistedPath path; 601 if (GetPath(path)) { 602 if (marked) 603 sPathBlacklist->Add(path.Path()); 604 else 605 sPathBlacklist->Remove(path.Path()); 606 } 607 } 608 609 MenuItem::SetMarked(marked); 610 } 611 612 private: 613 Node* fNode; 614 Menu* fSubMenu; 615 }; 616 617 618 class BlacklistMenu : public Menu { 619 public: 620 BlacklistMenu() 621 : 622 Menu(STANDARD_MENU, kDefaultMenuTitle), 623 fDirectory(NULL) 624 { 625 } 626 627 ~BlacklistMenu() 628 { 629 SetDirectory(NULL); 630 } 631 632 virtual void Entered() 633 { 634 _DeleteItems(); 635 636 if (fDirectory != NULL) { 637 void* cookie; 638 if (fDirectory->Open(&cookie, O_RDONLY) == B_OK) { 639 Node* node; 640 while (fDirectory->GetNextNode(cookie, &node) == B_OK) { 641 BlacklistMenuItem* item = _CreateItem(node); 642 node->Release(); 643 if (item == NULL) 644 break; 645 646 AddItem(item); 647 648 item->UpdateBlacklisted(); 649 } 650 fDirectory->Close(cookie); 651 } 652 653 SortItems(&BlacklistMenuItem::Less); 654 } 655 656 if (CountItems() > 0) 657 AddSeparatorItem(); 658 AddItem(new(nothrow) MenuItem("Return to parent directory")); 659 } 660 661 virtual void Exited() 662 { 663 _DeleteItems(); 664 } 665 666 protected: 667 void SetDirectory(Directory* directory) 668 { 669 if (fDirectory != NULL) 670 fDirectory->Release(); 671 672 fDirectory = directory; 673 674 if (fDirectory != NULL) 675 fDirectory->Acquire(); 676 } 677 678 private: 679 static BlacklistMenuItem* _CreateItem(Node* node) 680 { 681 // Get the node name and duplicate it, so we can use it as a label. 682 char name[B_FILE_NAME_LENGTH]; 683 if (node->GetName(name, sizeof(name)) != B_OK) 684 return NULL; 685 686 // append '/' to directory labels 687 bool isDirectory = node->Type() == S_IFDIR; 688 if (isDirectory) 689 strlcat(name, "/", sizeof(name)); 690 691 // If this is a directory, create the submenu. 692 BlacklistMenu* subMenu = NULL; 693 if (isDirectory) { 694 subMenu = new(std::nothrow) BlacklistMenu; 695 if (subMenu != NULL) 696 subMenu->SetDirectory(static_cast<Directory*>(node)); 697 698 } 699 ObjectDeleter<BlacklistMenu> subMenuDeleter(subMenu); 700 701 // create the menu item 702 BlacklistMenuItem* item = new(std::nothrow) BlacklistMenuItem(name, 703 node, subMenu); 704 if (item == NULL) 705 return NULL; 706 707 subMenuDeleter.Detach(); 708 return item; 709 } 710 711 void _DeleteItems() 712 { 713 int32 count = CountItems(); 714 for (int32 i = 0; i < count; i++) 715 delete RemoveItemAt(0); 716 } 717 718 private: 719 Directory* fDirectory; 720 721 protected: 722 static const char* const kDefaultMenuTitle; 723 }; 724 725 726 const char* const BlacklistMenu::kDefaultMenuTitle 727 = "Mark the entries to blacklist"; 728 729 730 class BlacklistRootMenu : public BlacklistMenu { 731 public: 732 BlacklistRootMenu() 733 : 734 BlacklistMenu() 735 { 736 } 737 738 virtual void Entered() 739 { 740 // Get the system directory, but only if this is a packaged Haiku. 741 // Otherwise blacklisting isn't supported. 742 if (sBootVolume != NULL && sBootVolume->IsValid() 743 && sBootVolume->IsPackaged()) { 744 SetDirectory(sBootVolume->SystemDirectory()); 745 SetTitle(kDefaultMenuTitle); 746 } else { 747 SetDirectory(NULL); 748 SetTitle(sBootVolume != NULL && sBootVolume->IsValid() 749 ? "The selected boot volume doesn't support blacklisting!" 750 : "No boot volume selected!"); 751 } 752 753 BlacklistMenu::Entered(); 754 755 // rename last item 756 if (MenuItem* item = ItemAt(CountItems() - 1)) 757 item->SetLabel("Return to safe mode menu"); 758 } 759 760 virtual void Exited() 761 { 762 BlacklistMenu::Exited(); 763 SetDirectory(NULL); 764 } 765 }; 766 767 768 // #pragma mark - boot volume menu 769 770 771 class BootVolumeMenuItem : public MenuItem { 772 public: 773 BootVolumeMenuItem(const char* volumeName) 774 : 775 MenuItem(volumeName), 776 fStateChoiceText(NULL) 777 { 778 } 779 780 ~BootVolumeMenuItem() 781 { 782 UpdateStateName(NULL); 783 } 784 785 void UpdateStateName(PackageVolumeState* volumeState) 786 { 787 free(fStateChoiceText); 788 fStateChoiceText = NULL; 789 790 if (volumeState != NULL && volumeState->Name() != NULL) { 791 char nameBuffer[128]; 792 snprintf(nameBuffer, sizeof(nameBuffer), "%s (%s)", Label(), 793 volumeState->DisplayName()); 794 fStateChoiceText = strdup(nameBuffer); 795 } 796 797 Supermenu()->SetChoiceText( 798 fStateChoiceText != NULL ? fStateChoiceText : Label()); 799 } 800 801 private: 802 char* fStateChoiceText; 803 }; 804 805 806 class PackageVolumeStateMenuItem : public MenuItem { 807 public: 808 PackageVolumeStateMenuItem(const char* label, PackageVolumeInfo* volumeInfo, 809 PackageVolumeState* volumeState) 810 : 811 MenuItem(label), 812 fVolumeInfo(volumeInfo), 813 fVolumeState(volumeState) 814 { 815 fVolumeInfo->AcquireReference(); 816 } 817 818 ~PackageVolumeStateMenuItem() 819 { 820 fVolumeInfo->ReleaseReference(); 821 } 822 823 PackageVolumeInfo* VolumeInfo() const 824 { 825 return fVolumeInfo; 826 } 827 828 PackageVolumeState* VolumeState() const 829 { 830 return fVolumeState; 831 } 832 833 private: 834 PackageVolumeInfo* fVolumeInfo; 835 PackageVolumeState* fVolumeState; 836 }; 837 838 839 // #pragma mark - 840 841 842 class StringBuffer { 843 public: 844 StringBuffer() 845 : 846 fBuffer(NULL), 847 fLength(0), 848 fCapacity(0) 849 { 850 } 851 852 ~StringBuffer() 853 { 854 free(fBuffer); 855 } 856 857 const char* String() const 858 { 859 return fBuffer != NULL ? fBuffer : ""; 860 } 861 862 size_t Length() const 863 { 864 return fLength; 865 } 866 867 bool Append(const char* toAppend) 868 { 869 return Append(toAppend, strlen(toAppend)); 870 } 871 872 bool Append(const char* toAppend, size_t length) 873 { 874 size_t oldLength = fLength; 875 if (!_Resize(fLength + length)) 876 return false; 877 878 memcpy(fBuffer + oldLength, toAppend, length); 879 return true; 880 } 881 882 private: 883 bool _Resize(size_t newLength) 884 { 885 if (newLength >= fCapacity) { 886 size_t newCapacity = std::max(fCapacity, size_t(32)); 887 while (newLength >= newCapacity) 888 newCapacity *= 2; 889 890 char* buffer = (char*)realloc(fBuffer, newCapacity); 891 if (buffer == NULL) 892 return false; 893 894 fBuffer = buffer; 895 fCapacity = newCapacity; 896 } 897 898 fBuffer[newLength] = '\0'; 899 fLength = newLength; 900 return true; 901 } 902 903 private: 904 char* fBuffer; 905 size_t fLength; 906 size_t fCapacity; 907 }; 908 909 910 // #pragma mark - 911 912 913 static StringBuffer sSafeModeOptionsBuffer; 914 915 916 static MenuItem* 917 get_continue_booting_menu_item() 918 { 919 // It's the last item in the main menu. 920 if (sMainMenu == NULL || sMainMenu->CountItems() == 0) 921 return NULL; 922 return sMainMenu->ItemAt(sMainMenu->CountItems() - 1); 923 } 924 925 926 static void 927 update_continue_booting_menu_item(status_t status) 928 { 929 MenuItem* bootItem = get_continue_booting_menu_item(); 930 if (bootItem == NULL) { 931 // huh? 932 return; 933 } 934 935 if (status == B_OK) { 936 bootItem->SetLabel("Continue booting"); 937 bootItem->SetEnabled(true); 938 bootItem->Select(true); 939 } else { 940 char label[128]; 941 snprintf(label, sizeof(label), "Cannot continue booting (%s)", 942 strerror(status)); 943 bootItem->SetLabel(label); 944 bootItem->SetEnabled(false); 945 } 946 } 947 948 949 static bool 950 user_menu_boot_volume(Menu* menu, MenuItem* item) 951 { 952 if (sBootVolume->IsValid() && sBootVolume->RootDirectory() == item->Data()) 953 return true; 954 955 sPathBlacklist->MakeEmpty(); 956 957 status_t status = sBootVolume->SetTo((Directory*)item->Data()); 958 update_continue_booting_menu_item(status); 959 960 gBootVolume.SetBool(BOOT_VOLUME_USER_SELECTED, true); 961 return true; 962 } 963 964 965 static bool 966 user_menu_boot_volume_state(Menu* menu, MenuItem* _item) 967 { 968 PackageVolumeStateMenuItem* item = static_cast<PackageVolumeStateMenuItem*>( 969 _item); 970 if (sBootVolume->IsValid() && sBootVolume->GetPackageVolumeState() != NULL 971 && sBootVolume->GetPackageVolumeState() == item->VolumeState()) { 972 return true; 973 } 974 975 BootVolumeMenuItem* volumeItem = static_cast<BootVolumeMenuItem*>( 976 item->Supermenu()->Superitem()); 977 volumeItem->SetMarked(true); 978 volumeItem->Select(true); 979 volumeItem->UpdateStateName(item->VolumeState()); 980 981 sPathBlacklist->MakeEmpty(); 982 983 status_t status = sBootVolume->SetTo((Directory*)item->Data(), 984 item->VolumeInfo(), item->VolumeState()); 985 update_continue_booting_menu_item(status); 986 987 gBootVolume.SetBool(BOOT_VOLUME_USER_SELECTED, true); 988 return true; 989 } 990 991 992 static bool 993 debug_menu_display_current_log(Menu* menu, MenuItem* item) 994 { 995 // get the buffer 996 size_t bufferSize; 997 const char* buffer = platform_debug_get_log_buffer(&bufferSize); 998 if (buffer == NULL || bufferSize == 0) 999 return true; 1000 1001 struct TextSource : PagerTextSource { 1002 TextSource(const char* buffer, size_t size) 1003 : 1004 fBuffer(buffer), 1005 fSize(strnlen(buffer, size)) 1006 { 1007 } 1008 1009 virtual size_t BytesAvailable() const 1010 { 1011 return fSize; 1012 } 1013 1014 virtual size_t Read(size_t offset, void* buffer, size_t size) const 1015 { 1016 if (offset >= fSize) 1017 return 0; 1018 1019 if (size > fSize - offset) 1020 size = fSize - offset; 1021 1022 memcpy(buffer, fBuffer + offset, size); 1023 return size; 1024 } 1025 1026 private: 1027 const char* fBuffer; 1028 size_t fSize; 1029 }; 1030 1031 pager(TextSource(buffer, bufferSize)); 1032 1033 return true; 1034 } 1035 1036 1037 static bool 1038 debug_menu_display_previous_syslog(Menu* menu, MenuItem* item) 1039 { 1040 ring_buffer* buffer = (ring_buffer*)gKernelArgs.debug_output.Pointer(); 1041 if (buffer == NULL) 1042 return true; 1043 1044 struct TextSource : PagerTextSource { 1045 TextSource(ring_buffer* buffer) 1046 : 1047 fBuffer(buffer) 1048 { 1049 } 1050 1051 virtual size_t BytesAvailable() const 1052 { 1053 return ring_buffer_readable(fBuffer); 1054 } 1055 1056 virtual size_t Read(size_t offset, void* buffer, size_t size) const 1057 { 1058 return ring_buffer_peek(fBuffer, offset, buffer, size); 1059 } 1060 1061 private: 1062 ring_buffer* fBuffer; 1063 }; 1064 1065 pager(TextSource(buffer)); 1066 1067 return true; 1068 } 1069 1070 1071 static status_t 1072 save_previous_syslog_to_volume(Directory* directory) 1073 { 1074 // find an unused name 1075 char name[16]; 1076 bool found = false; 1077 for (int i = 0; i < 99; i++) { 1078 snprintf(name, sizeof(name), "SYSLOG%02d.TXT", i); 1079 Node* node = directory->Lookup(name, false); 1080 if (node == NULL) { 1081 found = true; 1082 break; 1083 } 1084 1085 node->Release(); 1086 } 1087 1088 if (!found) { 1089 printf("Failed to find an unused name for the syslog file!\n"); 1090 return B_ERROR; 1091 } 1092 1093 printf("Writing syslog to file \"%s\" ...\n", name); 1094 1095 int fd = open_from(directory, name, O_RDWR | O_CREAT | O_EXCL, 0644); 1096 if (fd < 0) { 1097 printf("Failed to create syslog file!\n"); 1098 return fd; 1099 } 1100 1101 ring_buffer* syslogBuffer 1102 = (ring_buffer*)gKernelArgs.debug_output.Pointer(); 1103 iovec vecs[2]; 1104 int32 vecCount = ring_buffer_get_vecs(syslogBuffer, vecs); 1105 if (vecCount > 0) { 1106 size_t toWrite = ring_buffer_readable(syslogBuffer); 1107 1108 ssize_t written = writev(fd, vecs, vecCount); 1109 if (written < 0 || (size_t)written != toWrite) { 1110 printf("Failed to write to the syslog file \"%s\"!\n", name); 1111 close(fd); 1112 return errno; 1113 } 1114 } 1115 1116 close(fd); 1117 1118 printf("Successfully wrote syslog file.\n"); 1119 1120 return B_OK; 1121 } 1122 1123 1124 static bool 1125 debug_menu_add_advanced_option(Menu* menu, MenuItem* item) 1126 { 1127 char buffer[256]; 1128 1129 size_t size = platform_get_user_input_text(menu, item, buffer, 1130 sizeof(buffer) - 1); 1131 1132 if (size > 0) { 1133 buffer[size] = '\n'; 1134 if (!sSafeModeOptionsBuffer.Append(buffer)) { 1135 dprintf("debug_menu_add_advanced_option(): failed to append option " 1136 "to buffer\n"); 1137 } 1138 } 1139 1140 return true; 1141 } 1142 1143 1144 static bool 1145 debug_menu_toggle_debug_syslog(Menu* menu, MenuItem* item) 1146 { 1147 gKernelArgs.keep_debug_output_buffer = item->IsMarked(); 1148 return true; 1149 } 1150 1151 1152 static bool 1153 debug_menu_toggle_previous_debug_syslog(Menu* menu, MenuItem* item) 1154 { 1155 gKernelArgs.previous_debug_size = item->IsMarked(); 1156 return true; 1157 } 1158 1159 1160 static bool 1161 debug_menu_save_previous_syslog(Menu* menu, MenuItem* item) 1162 { 1163 Directory* volume = (Directory*)item->Data(); 1164 1165 console_clear_screen(); 1166 1167 save_previous_syslog_to_volume(volume); 1168 1169 printf("\nPress any key to continue\n"); 1170 console_wait_for_key(); 1171 1172 return true; 1173 } 1174 1175 1176 static void 1177 add_boot_volume_item(Menu* menu, Directory* volume, const char* name) 1178 { 1179 BReference<PackageVolumeInfo> volumeInfo; 1180 PackageVolumeState* selectedState = NULL; 1181 if (volume == sBootVolume->RootDirectory()) { 1182 volumeInfo.SetTo(sBootVolume->GetPackageVolumeInfo()); 1183 selectedState = sBootVolume->GetPackageVolumeState(); 1184 } else { 1185 volumeInfo.SetTo(new(std::nothrow) PackageVolumeInfo); 1186 if (volumeInfo->SetTo(volume, "system/packages") == B_OK) 1187 selectedState = volumeInfo->States().Head(); 1188 else 1189 volumeInfo.Unset(); 1190 } 1191 1192 BootVolumeMenuItem* item = new(nothrow) BootVolumeMenuItem(name); 1193 menu->AddItem(item); 1194 1195 Menu* subMenu = NULL; 1196 1197 if (volumeInfo != NULL && volumeInfo->LoadOldStates() == B_OK) { 1198 subMenu = new(std::nothrow) Menu(CHOICE_MENU, "Select Haiku version"); 1199 1200 for (PackageVolumeStateList::ConstIterator it 1201 = volumeInfo->States().GetIterator(); 1202 PackageVolumeState* state = it.Next();) { 1203 PackageVolumeStateMenuItem* stateItem 1204 = new(nothrow) PackageVolumeStateMenuItem(state->DisplayName(), 1205 volumeInfo, state); 1206 subMenu->AddItem(stateItem); 1207 stateItem->SetTarget(user_menu_boot_volume_state); 1208 stateItem->SetData(volume); 1209 1210 if (state == selectedState) { 1211 stateItem->SetMarked(true); 1212 stateItem->Select(true); 1213 item->UpdateStateName(stateItem->VolumeState()); 1214 } 1215 } 1216 } 1217 1218 if (subMenu != NULL && subMenu->CountItems() > 1) { 1219 item->SetHelpText( 1220 "Enter to choose a different state to boot"); 1221 item->SetSubmenu(subMenu); 1222 } else { 1223 delete subMenu; 1224 item->SetTarget(user_menu_boot_volume); 1225 item->SetData(volume); 1226 } 1227 1228 if (volume == sBootVolume->RootDirectory()) { 1229 item->SetMarked(true); 1230 item->Select(true); 1231 } 1232 } 1233 1234 1235 static Menu* 1236 add_boot_volume_menu() 1237 { 1238 Menu* menu = new(std::nothrow) Menu(CHOICE_MENU, "Select Boot Volume"); 1239 MenuItem* item; 1240 void* cookie; 1241 int32 count = 0; 1242 1243 if (gRoot->Open(&cookie, O_RDONLY) == B_OK) { 1244 Directory* volume; 1245 while (gRoot->GetNextNode(cookie, (Node**)&volume) == B_OK) { 1246 // only list bootable volumes 1247 if (volume != sBootVolume->RootDirectory() && !is_bootable(volume)) 1248 continue; 1249 1250 char name[B_FILE_NAME_LENGTH]; 1251 if (volume->GetName(name, sizeof(name)) == B_OK) { 1252 add_boot_volume_item(menu, volume, name); 1253 1254 count++; 1255 } 1256 } 1257 gRoot->Close(cookie); 1258 } 1259 1260 if (count == 0) { 1261 // no boot volume found yet 1262 menu->AddItem(item = new(nothrow) MenuItem("<No boot volume found>")); 1263 item->SetType(MENU_ITEM_NO_CHOICE); 1264 item->SetEnabled(false); 1265 } 1266 1267 menu->AddSeparatorItem(); 1268 1269 menu->AddItem(item = new(nothrow) MenuItem("Rescan volumes")); 1270 item->SetHelpText("Please insert a Haiku CD-ROM or attach a USB disk - " 1271 "depending on your system, you can then boot from there."); 1272 item->SetType(MENU_ITEM_NO_CHOICE); 1273 if (count == 0) 1274 item->Select(true); 1275 1276 menu->AddItem(item = new(nothrow) MenuItem("Return to main menu")); 1277 item->SetType(MENU_ITEM_NO_CHOICE); 1278 1279 if (gBootVolume.GetBool(BOOT_VOLUME_BOOTED_FROM_IMAGE, false)) 1280 menu->SetChoiceText("CD-ROM or hard drive"); 1281 1282 return menu; 1283 } 1284 1285 1286 static Menu* 1287 add_safe_mode_menu() 1288 { 1289 Menu* safeMenu = new(nothrow) Menu(SAFE_MODE_MENU, "Safe Mode Options"); 1290 MenuItem* item; 1291 1292 safeMenu->AddItem(item = new(nothrow) MenuItem("Safe mode")); 1293 item->SetData(B_SAFEMODE_SAFE_MODE); 1294 item->SetType(MENU_ITEM_MARKABLE); 1295 item->SetHelpText("Puts the system into safe mode. This can be enabled " 1296 "independently from the other options."); 1297 1298 safeMenu->AddItem(item = new(nothrow) MenuItem("Disable user add-ons")); 1299 item->SetData(B_SAFEMODE_DISABLE_USER_ADD_ONS); 1300 item->SetType(MENU_ITEM_MARKABLE); 1301 item->SetHelpText("Prevents all user installed add-ons from being loaded. " 1302 "Only the add-ons in the system directory will be used."); 1303 1304 safeMenu->AddItem(item = new(nothrow) MenuItem("Disable IDE DMA")); 1305 item->SetData(B_SAFEMODE_DISABLE_IDE_DMA); 1306 item->SetType(MENU_ITEM_MARKABLE); 1307 item->SetHelpText("Disables IDE DMA, increasing IDE compatibility " 1308 "at the expense of performance."); 1309 1310 #if B_HAIKU_PHYSICAL_BITS > 32 1311 // check whether we have memory beyond 4 GB 1312 bool hasMemoryBeyond4GB = false; 1313 for (uint32 i = 0; i < gKernelArgs.num_physical_memory_ranges; i++) { 1314 addr_range& range = gKernelArgs.physical_memory_range[i]; 1315 if (range.start >= (uint64)1 << 32) { 1316 hasMemoryBeyond4GB = true; 1317 break; 1318 } 1319 } 1320 1321 bool needs64BitPaging = true; 1322 // TODO: Determine whether 64 bit paging (i.e. PAE for x86) is needed 1323 // for other reasons (NX support). 1324 1325 // ... add the menu item, if so 1326 if (hasMemoryBeyond4GB || needs64BitPaging) { 1327 safeMenu->AddItem( 1328 item = new(nothrow) MenuItem("Ignore memory beyond 4 GiB")); 1329 item->SetData(B_SAFEMODE_4_GB_MEMORY_LIMIT); 1330 item->SetType(MENU_ITEM_MARKABLE); 1331 item->SetHelpText("Ignores all memory beyond the 4 GiB address limit, " 1332 "overriding the setting in the kernel settings file."); 1333 } 1334 #endif 1335 1336 platform_add_menus(safeMenu); 1337 1338 safeMenu->AddSeparatorItem(); 1339 sBlacklistRootMenu = new(std::nothrow) BlacklistRootMenu; 1340 safeMenu->AddItem(item = new(std::nothrow) MenuItem("Blacklist entries", 1341 sBlacklistRootMenu)); 1342 item->SetHelpText("Allows to select system files that shall be ignored. " 1343 "Useful e.g. to disable drivers temporarily."); 1344 1345 safeMenu->AddSeparatorItem(); 1346 safeMenu->AddItem(item = new(nothrow) MenuItem("Return to main menu")); 1347 1348 return safeMenu; 1349 } 1350 1351 1352 static Menu* 1353 add_save_debug_syslog_menu() 1354 { 1355 Menu* menu = new(nothrow) Menu(STANDARD_MENU, "Save syslog to volume ..."); 1356 MenuItem* item; 1357 1358 const char* const kHelpText = "Currently only FAT32 volumes are supported. " 1359 "Newly plugged in removable devices are only recognized after " 1360 "rebooting."; 1361 1362 int32 itemsAdded = 0; 1363 1364 void* cookie; 1365 if (gRoot->Open(&cookie, O_RDONLY) == B_OK) { 1366 Node* node; 1367 while (gRoot->GetNextNode(cookie, &node) == B_OK) { 1368 Directory* volume = static_cast<Directory*>(node); 1369 Partition* partition; 1370 if (gRoot->GetPartitionFor(volume, &partition) != B_OK) 1371 continue; 1372 1373 // we support only FAT32 volumes ATM 1374 if (partition->content_type == NULL 1375 || strcmp(partition->content_type, kPartitionTypeFAT32) != 0) { 1376 continue; 1377 } 1378 1379 char name[B_FILE_NAME_LENGTH]; 1380 if (volume->GetName(name, sizeof(name)) != B_OK) 1381 strlcpy(name, "unnamed", sizeof(name)); 1382 1383 // append offset, size, and type to the name 1384 size_t len = strlen(name); 1385 char offsetBuffer[32]; 1386 char sizeBuffer[32]; 1387 snprintf(name + len, sizeof(name) - len, 1388 " (%s, offset %s, size %s)", partition->content_type, 1389 size_to_string(partition->offset, offsetBuffer, 1390 sizeof(offsetBuffer)), 1391 size_to_string(partition->size, sizeBuffer, 1392 sizeof(sizeBuffer))); 1393 1394 item = new(nothrow) MenuItem(name); 1395 item->SetData(volume); 1396 item->SetTarget(&debug_menu_save_previous_syslog); 1397 item->SetType(MENU_ITEM_NO_CHOICE); 1398 item->SetHelpText(kHelpText); 1399 menu->AddItem(item); 1400 itemsAdded++; 1401 } 1402 1403 gRoot->Close(cookie); 1404 } 1405 1406 if (itemsAdded == 0) { 1407 menu->AddItem(item 1408 = new(nothrow) MenuItem("No supported volumes found")); 1409 item->SetType(MENU_ITEM_NO_CHOICE); 1410 item->SetHelpText(kHelpText); 1411 item->SetEnabled(false); 1412 } 1413 1414 menu->AddSeparatorItem(); 1415 menu->AddItem(item = new(nothrow) MenuItem("Return to debug menu")); 1416 item->SetHelpText(kHelpText); 1417 1418 return menu; 1419 } 1420 1421 1422 static Menu* 1423 add_debug_menu() 1424 { 1425 Menu* menu = new(std::nothrow) Menu(STANDARD_MENU, "Debug Options"); 1426 MenuItem* item; 1427 1428 #if DEBUG_SPINLOCK_LATENCIES 1429 item = new(std::nothrow) MenuItem("Disable latency checks"); 1430 if (item != NULL) { 1431 item->SetType(MENU_ITEM_MARKABLE); 1432 item->SetData(B_SAFEMODE_DISABLE_LATENCY_CHECK); 1433 item->SetHelpText("Disables latency check panics."); 1434 menu->AddItem(item); 1435 } 1436 #endif 1437 1438 menu->AddItem(item 1439 = new(nothrow) MenuItem("Enable serial debug output")); 1440 item->SetData("serial_debug_output"); 1441 item->SetType(MENU_ITEM_MARKABLE); 1442 item->SetHelpText("Turns on forwarding the syslog output to the serial " 1443 "interface (default: 115200, 8N1)."); 1444 1445 menu->AddItem(item 1446 = new(nothrow) MenuItem("Enable on screen debug output")); 1447 item->SetData("debug_screen"); 1448 item->SetType(MENU_ITEM_MARKABLE); 1449 item->SetHelpText("Displays debug output on screen while the system " 1450 "is booting, instead of the normal boot logo."); 1451 1452 menu->AddItem(item 1453 = new(nothrow) MenuItem("Disable on screen paging")); 1454 item->SetData("disable_onscreen_paging"); 1455 item->SetType(MENU_ITEM_MARKABLE); 1456 item->SetHelpText("Disables paging when on screen debug output is " 1457 "enabled."); 1458 1459 menu->AddItem(item = new(nothrow) MenuItem("Enable debug syslog")); 1460 item->SetType(MENU_ITEM_MARKABLE); 1461 item->SetMarked(gKernelArgs.keep_debug_output_buffer); 1462 item->SetTarget(&debug_menu_toggle_debug_syslog); 1463 item->SetHelpText("Enables a special in-memory syslog buffer for this " 1464 "session that the boot loader will be able to access after rebooting."); 1465 1466 ring_buffer* syslogBuffer 1467 = (ring_buffer*)gKernelArgs.debug_output.Pointer(); 1468 bool hasPreviousSyslog 1469 = syslogBuffer != NULL && ring_buffer_readable(syslogBuffer) > 0; 1470 if (hasPreviousSyslog) { 1471 menu->AddItem(item = new(nothrow) MenuItem( 1472 "Save syslog from previous session during boot")); 1473 item->SetType(MENU_ITEM_MARKABLE); 1474 item->SetMarked(gKernelArgs.previous_debug_size); 1475 item->SetTarget(&debug_menu_toggle_previous_debug_syslog); 1476 item->SetHelpText("Saves the syslog from the previous Haiku session to " 1477 "/var/log/previous_syslog when booting."); 1478 } 1479 1480 bool currentLogItemVisible = platform_debug_get_log_buffer(NULL) != NULL; 1481 if (currentLogItemVisible) { 1482 menu->AddSeparatorItem(); 1483 menu->AddItem(item 1484 = new(nothrow) MenuItem("Display current boot loader log")); 1485 item->SetTarget(&debug_menu_display_current_log); 1486 item->SetType(MENU_ITEM_NO_CHOICE); 1487 item->SetHelpText( 1488 "Displays the debug info the boot loader has logged."); 1489 } 1490 1491 if (hasPreviousSyslog) { 1492 if (!currentLogItemVisible) 1493 menu->AddSeparatorItem(); 1494 1495 menu->AddItem(item 1496 = new(nothrow) MenuItem("Display syslog from previous session")); 1497 item->SetTarget(&debug_menu_display_previous_syslog); 1498 item->SetType(MENU_ITEM_NO_CHOICE); 1499 item->SetHelpText( 1500 "Displays the syslog from the previous Haiku session."); 1501 1502 menu->AddItem(item = new(nothrow) MenuItem( 1503 "Save syslog from previous session", add_save_debug_syslog_menu())); 1504 item->SetHelpText("Saves the syslog from the previous Haiku session to " 1505 "disk. Currently only FAT32 volumes are supported."); 1506 } 1507 1508 menu->AddSeparatorItem(); 1509 menu->AddItem(item = new(nothrow) MenuItem( 1510 "Add advanced debug option")); 1511 item->SetType(MENU_ITEM_NO_CHOICE); 1512 item->SetTarget(&debug_menu_add_advanced_option); 1513 item->SetHelpText( 1514 "Allows advanced debugging options to be entered directly."); 1515 1516 menu->AddSeparatorItem(); 1517 menu->AddItem(item = new(nothrow) MenuItem("Return to main menu")); 1518 1519 return menu; 1520 } 1521 1522 1523 static void 1524 apply_safe_mode_options(Menu* menu) 1525 { 1526 MenuItemIterator iterator = menu->ItemIterator(); 1527 while (MenuItem* item = iterator.Next()) { 1528 if (item->Type() == MENU_ITEM_SEPARATOR || !item->IsMarked() 1529 || item->Data() == NULL) { 1530 continue; 1531 } 1532 1533 char buffer[256]; 1534 if (snprintf(buffer, sizeof(buffer), "%s true\n", 1535 (const char*)item->Data()) >= (int)sizeof(buffer) 1536 || !sSafeModeOptionsBuffer.Append(buffer)) { 1537 dprintf("apply_safe_mode_options(): failed to append option to " 1538 "buffer\n"); 1539 } 1540 } 1541 } 1542 1543 1544 static void 1545 apply_safe_mode_path_blacklist() 1546 { 1547 if (sPathBlacklist->IsEmpty()) 1548 return; 1549 1550 bool success = sSafeModeOptionsBuffer.Append("EntryBlacklist {\n"); 1551 1552 for (PathBlacklist::Iterator it = sPathBlacklist->GetIterator(); 1553 BlacklistedPath* path = it.Next();) { 1554 success &= sSafeModeOptionsBuffer.Append(path->Path()); 1555 success &= sSafeModeOptionsBuffer.Append("\n", 1); 1556 } 1557 1558 success &= sSafeModeOptionsBuffer.Append("}\n"); 1559 1560 if (!success) { 1561 dprintf("apply_safe_mode_options(): failed to append path " 1562 "blacklist to buffer\n"); 1563 } 1564 } 1565 1566 1567 static bool 1568 user_menu_reboot(Menu* menu, MenuItem* item) 1569 { 1570 platform_exit(); 1571 return true; 1572 } 1573 1574 1575 status_t 1576 user_menu(BootVolume& _bootVolume, PathBlacklist& _pathBlacklist) 1577 { 1578 1579 Menu* menu = new(std::nothrow) Menu(MAIN_MENU); 1580 1581 sMainMenu = menu; 1582 sBootVolume = &_bootVolume; 1583 sPathBlacklist = &_pathBlacklist; 1584 1585 Menu* safeModeMenu = NULL; 1586 Menu* debugMenu = NULL; 1587 MenuItem* item; 1588 1589 TRACE(("user_menu: enter\n")); 1590 1591 // Add boot volume 1592 menu->AddItem(item = new(std::nothrow) MenuItem("Select boot volume", 1593 add_boot_volume_menu())); 1594 1595 // Add safe mode 1596 menu->AddItem(item = new(std::nothrow) MenuItem("Select safe mode options", 1597 safeModeMenu = add_safe_mode_menu())); 1598 1599 // add debug menu 1600 menu->AddItem(item = new(std::nothrow) MenuItem("Select debug options", 1601 debugMenu = add_debug_menu())); 1602 1603 // Add platform dependent menus 1604 platform_add_menus(menu); 1605 1606 menu->AddSeparatorItem(); 1607 1608 menu->AddItem(item = new(std::nothrow) MenuItem("Reboot")); 1609 item->SetTarget(user_menu_reboot); 1610 item->SetShortcut('r'); 1611 1612 menu->AddItem(item = new(std::nothrow) MenuItem("Continue booting")); 1613 if (!_bootVolume.IsValid()) { 1614 item->SetLabel("Cannot continue booting (Boot volume is not valid)"); 1615 item->SetEnabled(false); 1616 menu->ItemAt(0)->Select(true); 1617 } else 1618 item->SetShortcut('b'); 1619 1620 menu->Run(); 1621 1622 apply_safe_mode_options(safeModeMenu); 1623 apply_safe_mode_options(debugMenu); 1624 apply_safe_mode_path_blacklist(); 1625 add_safe_mode_settings(sSafeModeOptionsBuffer.String()); 1626 delete menu; 1627 1628 1629 TRACE(("user_menu: leave\n")); 1630 1631 sMainMenu = NULL; 1632 sBlacklistRootMenu = NULL; 1633 sBootVolume = NULL; 1634 sPathBlacklist = NULL; 1635 return B_OK; 1636 } 1637