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/PathBlocklist.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* sBlocklistRootMenu = NULL; 50 static BootVolume* sBootVolume = NULL; 51 static PathBlocklist* sPathBlocklist; 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 - blocklist menu 522 523 524 class BlocklistMenuItem : public MenuItem { 525 public: 526 BlocklistMenuItem(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 ~BlocklistMenuItem() 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(BlockedPath& _path) const 550 { 551 Menu* menu = Supermenu(); 552 if (menu != NULL && menu != sBlocklistRootMenu 553 && menu->Superitem() != NULL) { 554 return static_cast<BlocklistMenuItem*>(menu->Superitem()) 555 ->GetPath(_path) 556 && _path.Append(Label()); 557 } 558 559 return _path.SetTo(Label()); 560 } 561 562 void UpdateBlocked() 563 { 564 BlockedPath path; 565 if (GetPath(path)) 566 _SetMarked(sPathBlocklist->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 BlocklistMenuItem* item1 577 = static_cast<const BlocklistMenuItem*>(a); 578 const BlocklistMenuItem* item2 579 = static_cast<const BlocklistMenuItem*>(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 updateBlocklist) 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 (updateBlocklist) { 600 BlockedPath path; 601 if (GetPath(path)) { 602 if (marked) 603 sPathBlocklist->Add(path.Path()); 604 else 605 sPathBlocklist->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 BlocklistMenu : public Menu { 619 public: 620 BlocklistMenu() 621 : 622 Menu(STANDARD_MENU, kDefaultMenuTitle), 623 fDirectory(NULL) 624 { 625 } 626 627 ~BlocklistMenu() 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 BlocklistMenuItem* item = _CreateItem(node); 642 node->Release(); 643 if (item == NULL) 644 break; 645 646 AddItem(item); 647 648 item->UpdateBlocked(); 649 } 650 fDirectory->Close(cookie); 651 } 652 653 SortItems(&BlocklistMenuItem::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 BlocklistMenuItem* _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 BlocklistMenu* subMenu = NULL; 693 if (isDirectory) { 694 subMenu = new(std::nothrow) BlocklistMenu; 695 if (subMenu != NULL) 696 subMenu->SetDirectory(static_cast<Directory*>(node)); 697 698 } 699 ObjectDeleter<BlocklistMenu> subMenuDeleter(subMenu); 700 701 // create the menu item 702 BlocklistMenuItem* item = new(std::nothrow) BlocklistMenuItem(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 BlocklistMenu::kDefaultMenuTitle 727 = "Mark the components to disable"; 728 729 730 class BlocklistRootMenu : public BlocklistMenu { 731 public: 732 BlocklistRootMenu() 733 : 734 BlocklistMenu() 735 { 736 } 737 738 virtual void Entered() 739 { 740 // Get the system directory, but only if this is a packaged Haiku. 741 // Otherwise blocklisting 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 disabling " 750 "components!" 751 : "No boot volume selected!"); 752 } 753 754 BlocklistMenu::Entered(); 755 756 // rename last item 757 if (MenuItem* item = ItemAt(CountItems() - 1)) 758 item->SetLabel("Return to safe mode menu"); 759 } 760 761 virtual void Exited() 762 { 763 BlocklistMenu::Exited(); 764 SetDirectory(NULL); 765 } 766 }; 767 768 769 // #pragma mark - boot volume menu 770 771 772 class BootVolumeMenuItem : public MenuItem { 773 public: 774 BootVolumeMenuItem(const char* volumeName) 775 : 776 MenuItem(volumeName), 777 fStateChoiceText(NULL) 778 { 779 } 780 781 ~BootVolumeMenuItem() 782 { 783 UpdateStateName(NULL); 784 } 785 786 void UpdateStateName(PackageVolumeState* volumeState) 787 { 788 free(fStateChoiceText); 789 fStateChoiceText = NULL; 790 791 if (volumeState != NULL && volumeState->Name() != NULL) { 792 char nameBuffer[128]; 793 snprintf(nameBuffer, sizeof(nameBuffer), "%s (%s)", Label(), 794 volumeState->DisplayName()); 795 fStateChoiceText = strdup(nameBuffer); 796 } 797 798 Supermenu()->SetChoiceText( 799 fStateChoiceText != NULL ? fStateChoiceText : Label()); 800 } 801 802 private: 803 char* fStateChoiceText; 804 }; 805 806 807 class PackageVolumeStateMenuItem : public MenuItem { 808 public: 809 PackageVolumeStateMenuItem(const char* label, PackageVolumeInfo* volumeInfo, 810 PackageVolumeState* volumeState) 811 : 812 MenuItem(label), 813 fVolumeInfo(volumeInfo), 814 fVolumeState(volumeState) 815 { 816 fVolumeInfo->AcquireReference(); 817 } 818 819 ~PackageVolumeStateMenuItem() 820 { 821 fVolumeInfo->ReleaseReference(); 822 } 823 824 PackageVolumeInfo* VolumeInfo() const 825 { 826 return fVolumeInfo; 827 } 828 829 PackageVolumeState* VolumeState() const 830 { 831 return fVolumeState; 832 } 833 834 private: 835 PackageVolumeInfo* fVolumeInfo; 836 PackageVolumeState* fVolumeState; 837 }; 838 839 840 // #pragma mark - 841 842 843 class StringBuffer { 844 public: 845 StringBuffer() 846 : 847 fBuffer(NULL), 848 fLength(0), 849 fCapacity(0) 850 { 851 } 852 853 ~StringBuffer() 854 { 855 free(fBuffer); 856 } 857 858 const char* String() const 859 { 860 return fBuffer != NULL ? fBuffer : ""; 861 } 862 863 size_t Length() const 864 { 865 return fLength; 866 } 867 868 bool Append(const char* toAppend) 869 { 870 return Append(toAppend, strlen(toAppend)); 871 } 872 873 bool Append(const char* toAppend, size_t length) 874 { 875 size_t oldLength = fLength; 876 if (!_Resize(fLength + length)) 877 return false; 878 879 memcpy(fBuffer + oldLength, toAppend, length); 880 return true; 881 } 882 883 private: 884 bool _Resize(size_t newLength) 885 { 886 if (newLength >= fCapacity) { 887 size_t newCapacity = std::max(fCapacity, size_t(32)); 888 while (newLength >= newCapacity) 889 newCapacity *= 2; 890 891 char* buffer = (char*)realloc(fBuffer, newCapacity); 892 if (buffer == NULL) 893 return false; 894 895 fBuffer = buffer; 896 fCapacity = newCapacity; 897 } 898 899 fBuffer[newLength] = '\0'; 900 fLength = newLength; 901 return true; 902 } 903 904 private: 905 char* fBuffer; 906 size_t fLength; 907 size_t fCapacity; 908 }; 909 910 911 // #pragma mark - 912 913 914 static StringBuffer sSafeModeOptionsBuffer; 915 916 917 static MenuItem* 918 get_continue_booting_menu_item() 919 { 920 // It's the last item in the main menu. 921 if (sMainMenu == NULL || sMainMenu->CountItems() == 0) 922 return NULL; 923 return sMainMenu->ItemAt(sMainMenu->CountItems() - 1); 924 } 925 926 927 static void 928 update_continue_booting_menu_item(status_t status) 929 { 930 MenuItem* bootItem = get_continue_booting_menu_item(); 931 if (bootItem == NULL) { 932 // huh? 933 return; 934 } 935 936 if (status == B_OK) { 937 bootItem->SetLabel("Continue booting"); 938 bootItem->SetEnabled(true); 939 bootItem->Select(true); 940 } else { 941 char label[128]; 942 snprintf(label, sizeof(label), "Cannot continue booting (%s)", 943 strerror(status)); 944 bootItem->SetLabel(label); 945 bootItem->SetEnabled(false); 946 } 947 } 948 949 950 static bool 951 user_menu_boot_volume(Menu* menu, MenuItem* item) 952 { 953 if (sBootVolume->IsValid() && sBootVolume->RootDirectory() == item->Data()) 954 return true; 955 956 sPathBlocklist->MakeEmpty(); 957 958 status_t status = sBootVolume->SetTo((Directory*)item->Data()); 959 update_continue_booting_menu_item(status); 960 961 gBootVolume.SetBool(BOOT_VOLUME_USER_SELECTED, true); 962 return true; 963 } 964 965 966 static bool 967 user_menu_boot_volume_state(Menu* menu, MenuItem* _item) 968 { 969 PackageVolumeStateMenuItem* item = static_cast<PackageVolumeStateMenuItem*>( 970 _item); 971 if (sBootVolume->IsValid() && sBootVolume->GetPackageVolumeState() != NULL 972 && sBootVolume->GetPackageVolumeState() == item->VolumeState()) { 973 return true; 974 } 975 976 BootVolumeMenuItem* volumeItem = static_cast<BootVolumeMenuItem*>( 977 item->Supermenu()->Superitem()); 978 volumeItem->SetMarked(true); 979 volumeItem->Select(true); 980 volumeItem->UpdateStateName(item->VolumeState()); 981 982 sPathBlocklist->MakeEmpty(); 983 984 status_t status = sBootVolume->SetTo((Directory*)item->Data(), 985 item->VolumeInfo(), item->VolumeState()); 986 update_continue_booting_menu_item(status); 987 988 gBootVolume.SetBool(BOOT_VOLUME_USER_SELECTED, true); 989 return true; 990 } 991 992 993 static bool 994 debug_menu_display_current_log(Menu* menu, MenuItem* item) 995 { 996 // get the buffer 997 size_t bufferSize; 998 const char* buffer = platform_debug_get_log_buffer(&bufferSize); 999 if (buffer == NULL || bufferSize == 0) 1000 return true; 1001 1002 struct TextSource : PagerTextSource { 1003 TextSource(const char* buffer, size_t size) 1004 : 1005 fBuffer(buffer), 1006 fSize(strnlen(buffer, size)) 1007 { 1008 } 1009 1010 virtual size_t BytesAvailable() const 1011 { 1012 return fSize; 1013 } 1014 1015 virtual size_t Read(size_t offset, void* buffer, size_t size) const 1016 { 1017 if (offset >= fSize) 1018 return 0; 1019 1020 if (size > fSize - offset) 1021 size = fSize - offset; 1022 1023 memcpy(buffer, fBuffer + offset, size); 1024 return size; 1025 } 1026 1027 private: 1028 const char* fBuffer; 1029 size_t fSize; 1030 }; 1031 1032 pager(TextSource(buffer, bufferSize)); 1033 1034 return true; 1035 } 1036 1037 1038 static bool 1039 debug_menu_display_previous_syslog(Menu* menu, MenuItem* item) 1040 { 1041 ring_buffer* buffer = (ring_buffer*)gKernelArgs.debug_output.Pointer(); 1042 if (buffer == NULL) 1043 return true; 1044 1045 struct TextSource : PagerTextSource { 1046 TextSource(ring_buffer* buffer) 1047 : 1048 fBuffer(buffer) 1049 { 1050 } 1051 1052 virtual size_t BytesAvailable() const 1053 { 1054 return ring_buffer_readable(fBuffer); 1055 } 1056 1057 virtual size_t Read(size_t offset, void* buffer, size_t size) const 1058 { 1059 return ring_buffer_peek(fBuffer, offset, buffer, size); 1060 } 1061 1062 private: 1063 ring_buffer* fBuffer; 1064 }; 1065 1066 pager(TextSource(buffer)); 1067 1068 return true; 1069 } 1070 1071 1072 static status_t 1073 save_previous_syslog_to_volume(Directory* directory) 1074 { 1075 // find an unused name 1076 char name[16]; 1077 bool found = false; 1078 for (int i = 0; i < 99; i++) { 1079 snprintf(name, sizeof(name), "SYSLOG%02d.TXT", i); 1080 Node* node = directory->Lookup(name, false); 1081 if (node == NULL) { 1082 found = true; 1083 break; 1084 } 1085 1086 node->Release(); 1087 } 1088 1089 if (!found) { 1090 printf("Failed to find an unused name for the syslog file!\n"); 1091 return B_ERROR; 1092 } 1093 1094 printf("Writing syslog to file \"%s\" ...\n", name); 1095 1096 int fd = open_from(directory, name, O_RDWR | O_CREAT | O_EXCL, 0644); 1097 if (fd < 0) { 1098 printf("Failed to create syslog file!\n"); 1099 return fd; 1100 } 1101 1102 ring_buffer* syslogBuffer 1103 = (ring_buffer*)gKernelArgs.debug_output.Pointer(); 1104 iovec vecs[2]; 1105 int32 vecCount = ring_buffer_get_vecs(syslogBuffer, vecs); 1106 if (vecCount > 0) { 1107 size_t toWrite = ring_buffer_readable(syslogBuffer); 1108 1109 ssize_t written = writev(fd, vecs, vecCount); 1110 if (written < 0 || (size_t)written != toWrite) { 1111 printf("Failed to write to the syslog file \"%s\"!\n", name); 1112 close(fd); 1113 return errno; 1114 } 1115 } 1116 1117 close(fd); 1118 1119 printf("Successfully wrote syslog file.\n"); 1120 1121 return B_OK; 1122 } 1123 1124 1125 static bool 1126 debug_menu_add_advanced_option(Menu* menu, MenuItem* item) 1127 { 1128 char buffer[256]; 1129 1130 size_t size = platform_get_user_input_text(menu, item, buffer, 1131 sizeof(buffer) - 1); 1132 1133 if (size > 0) { 1134 buffer[size] = '\n'; 1135 if (!sSafeModeOptionsBuffer.Append(buffer)) { 1136 dprintf("debug_menu_add_advanced_option(): failed to append option " 1137 "to buffer\n"); 1138 } 1139 } 1140 1141 return true; 1142 } 1143 1144 1145 static bool 1146 debug_menu_toggle_debug_syslog(Menu* menu, MenuItem* item) 1147 { 1148 gKernelArgs.keep_debug_output_buffer = item->IsMarked(); 1149 return true; 1150 } 1151 1152 1153 static bool 1154 debug_menu_toggle_previous_debug_syslog(Menu* menu, MenuItem* item) 1155 { 1156 gKernelArgs.previous_debug_size = item->IsMarked(); 1157 return true; 1158 } 1159 1160 1161 static bool 1162 debug_menu_save_previous_syslog(Menu* menu, MenuItem* item) 1163 { 1164 Directory* volume = (Directory*)item->Data(); 1165 1166 console_clear_screen(); 1167 1168 save_previous_syslog_to_volume(volume); 1169 1170 printf("\nPress any key to continue\n"); 1171 console_wait_for_key(); 1172 1173 return true; 1174 } 1175 1176 1177 static void 1178 add_boot_volume_item(Menu* menu, Directory* volume, const char* name) 1179 { 1180 BReference<PackageVolumeInfo> volumeInfo; 1181 PackageVolumeState* selectedState = NULL; 1182 if (volume == sBootVolume->RootDirectory()) { 1183 volumeInfo.SetTo(sBootVolume->GetPackageVolumeInfo()); 1184 selectedState = sBootVolume->GetPackageVolumeState(); 1185 } else { 1186 volumeInfo.SetTo(new(std::nothrow) PackageVolumeInfo); 1187 if (volumeInfo->SetTo(volume, "system/packages") == B_OK) 1188 selectedState = volumeInfo->States().Head(); 1189 else 1190 volumeInfo.Unset(); 1191 } 1192 1193 BootVolumeMenuItem* item = new(nothrow) BootVolumeMenuItem(name); 1194 menu->AddItem(item); 1195 1196 Menu* subMenu = NULL; 1197 1198 if (volumeInfo != NULL && volumeInfo->LoadOldStates() == B_OK) { 1199 subMenu = new(std::nothrow) Menu(CHOICE_MENU, "Select package activation state"); 1200 1201 for (PackageVolumeStateList::ConstIterator it 1202 = volumeInfo->States().GetIterator(); 1203 PackageVolumeState* state = it.Next();) { 1204 PackageVolumeStateMenuItem* stateItem 1205 = new(nothrow) PackageVolumeStateMenuItem(state->DisplayName(), 1206 volumeInfo, state); 1207 subMenu->AddItem(stateItem); 1208 stateItem->SetTarget(user_menu_boot_volume_state); 1209 stateItem->SetData(volume); 1210 1211 if (state == selectedState) { 1212 stateItem->SetMarked(true); 1213 stateItem->Select(true); 1214 if (volume == sBootVolume->RootDirectory()) { 1215 item->UpdateStateName(stateItem->VolumeState()); 1216 } 1217 } 1218 } 1219 } 1220 1221 if (subMenu != NULL && subMenu->CountItems() > 1) { 1222 item->SetHelpText( 1223 "Enter to choose a different state to boot"); 1224 item->SetSubmenu(subMenu); 1225 } else { 1226 delete subMenu; 1227 item->SetTarget(user_menu_boot_volume); 1228 item->SetData(volume); 1229 } 1230 1231 if (volume == sBootVolume->RootDirectory()) { 1232 item->SetMarked(true); 1233 item->Select(true); 1234 } 1235 } 1236 1237 1238 static Menu* 1239 add_boot_volume_menu() 1240 { 1241 Menu* menu = new(std::nothrow) Menu(CHOICE_MENU, "Select Boot Volume/State"); 1242 MenuItem* item; 1243 void* cookie; 1244 int32 count = 0; 1245 1246 if (gRoot->Open(&cookie, O_RDONLY) == B_OK) { 1247 Directory* volume; 1248 while (gRoot->GetNextNode(cookie, (Node**)&volume) == B_OK) { 1249 // only list bootable volumes 1250 if (volume != sBootVolume->RootDirectory() && !is_bootable(volume)) 1251 continue; 1252 1253 char name[B_FILE_NAME_LENGTH]; 1254 if (volume->GetName(name, sizeof(name)) == B_OK) { 1255 add_boot_volume_item(menu, volume, name); 1256 1257 count++; 1258 } 1259 } 1260 gRoot->Close(cookie); 1261 } 1262 1263 if (count == 0) { 1264 // no boot volume found yet 1265 menu->AddItem(item = new(nothrow) MenuItem("<No boot volume found>")); 1266 item->SetType(MENU_ITEM_NO_CHOICE); 1267 item->SetEnabled(false); 1268 } 1269 1270 menu->AddSeparatorItem(); 1271 1272 menu->AddItem(item = new(nothrow) MenuItem("Rescan volumes")); 1273 item->SetHelpText("Please insert a Haiku CD-ROM or attach a USB disk - " 1274 "depending on your system, you can then boot from there."); 1275 item->SetType(MENU_ITEM_NO_CHOICE); 1276 if (count == 0) 1277 item->Select(true); 1278 1279 menu->AddItem(item = new(nothrow) MenuItem("Return to main menu")); 1280 item->SetType(MENU_ITEM_NO_CHOICE); 1281 1282 if (gBootVolume.GetBool(BOOT_VOLUME_BOOTED_FROM_IMAGE, false)) 1283 menu->SetChoiceText("CD-ROM or hard drive"); 1284 1285 return menu; 1286 } 1287 1288 1289 static Menu* 1290 add_safe_mode_menu() 1291 { 1292 Menu* safeMenu = new(nothrow) Menu(SAFE_MODE_MENU, "Safe Mode Options"); 1293 MenuItem* item; 1294 1295 safeMenu->AddItem(item = new(nothrow) MenuItem("Safe mode")); 1296 item->SetData(B_SAFEMODE_SAFE_MODE); 1297 item->SetType(MENU_ITEM_MARKABLE); 1298 item->SetHelpText("Puts the system into safe mode. This can be enabled " 1299 "independently from the other options."); 1300 1301 safeMenu->AddItem(item = new(nothrow) MenuItem("Disable user add-ons")); 1302 item->SetData(B_SAFEMODE_DISABLE_USER_ADD_ONS); 1303 item->SetType(MENU_ITEM_MARKABLE); 1304 item->SetHelpText("Prevents all user installed add-ons from being loaded. " 1305 "Only the add-ons in the system directory will be used."); 1306 1307 safeMenu->AddItem(item = new(nothrow) MenuItem("Disable IDE DMA")); 1308 item->SetData(B_SAFEMODE_DISABLE_IDE_DMA); 1309 item->SetType(MENU_ITEM_MARKABLE); 1310 item->SetHelpText("Disables IDE DMA, increasing IDE compatibility " 1311 "at the expense of performance."); 1312 1313 #if B_HAIKU_PHYSICAL_BITS > 32 1314 // check whether we have memory beyond 4 GB 1315 bool hasMemoryBeyond4GB = false; 1316 for (uint32 i = 0; i < gKernelArgs.num_physical_memory_ranges; i++) { 1317 addr_range& range = gKernelArgs.physical_memory_range[i]; 1318 if (range.start >= (uint64)1 << 32) { 1319 hasMemoryBeyond4GB = true; 1320 break; 1321 } 1322 } 1323 1324 bool needs64BitPaging = true; 1325 // TODO: Determine whether 64 bit paging (i.e. PAE for x86) is needed 1326 // for other reasons (NX support). 1327 1328 // ... add the menu item, if so 1329 if (hasMemoryBeyond4GB || needs64BitPaging) { 1330 safeMenu->AddItem( 1331 item = new(nothrow) MenuItem("Ignore memory beyond 4 GiB")); 1332 item->SetData(B_SAFEMODE_4_GB_MEMORY_LIMIT); 1333 item->SetType(MENU_ITEM_MARKABLE); 1334 item->SetHelpText("Ignores all memory beyond the 4 GiB address limit, " 1335 "overriding the setting in the kernel settings file."); 1336 } 1337 #endif 1338 1339 platform_add_menus(safeMenu); 1340 1341 safeMenu->AddSeparatorItem(); 1342 sBlocklistRootMenu = new(std::nothrow) BlocklistRootMenu; 1343 safeMenu->AddItem(item = new(std::nothrow) MenuItem( 1344 "Disable system components", sBlocklistRootMenu)); 1345 item->SetHelpText("Allows to select system files that shall be ignored. " 1346 "Useful e.g. to disable drivers temporarily."); 1347 1348 safeMenu->AddSeparatorItem(); 1349 safeMenu->AddItem(item = new(nothrow) MenuItem("Return to main menu")); 1350 1351 return safeMenu; 1352 } 1353 1354 1355 static Menu* 1356 add_save_debug_syslog_menu() 1357 { 1358 Menu* menu = new(nothrow) Menu(STANDARD_MENU, "Save syslog to volume ..."); 1359 MenuItem* item; 1360 1361 const char* const kHelpText = "Currently only FAT32 volumes are supported. " 1362 "Newly plugged in removable devices are only recognized after " 1363 "rebooting."; 1364 1365 int32 itemsAdded = 0; 1366 1367 void* cookie; 1368 if (gRoot->Open(&cookie, O_RDONLY) == B_OK) { 1369 Node* node; 1370 while (gRoot->GetNextNode(cookie, &node) == B_OK) { 1371 Directory* volume = static_cast<Directory*>(node); 1372 Partition* partition; 1373 if (gRoot->GetPartitionFor(volume, &partition) != B_OK) 1374 continue; 1375 1376 // we support only FAT32 volumes ATM 1377 if (partition->content_type == NULL 1378 || strcmp(partition->content_type, kPartitionTypeFAT32) != 0) { 1379 continue; 1380 } 1381 1382 char name[B_FILE_NAME_LENGTH]; 1383 if (volume->GetName(name, sizeof(name)) != B_OK) 1384 strlcpy(name, "unnamed", sizeof(name)); 1385 1386 // append offset, size, and type to the name 1387 size_t len = strlen(name); 1388 char offsetBuffer[32]; 1389 char sizeBuffer[32]; 1390 snprintf(name + len, sizeof(name) - len, 1391 " (%s, offset %s, size %s)", partition->content_type, 1392 size_to_string(partition->offset, offsetBuffer, 1393 sizeof(offsetBuffer)), 1394 size_to_string(partition->size, sizeBuffer, 1395 sizeof(sizeBuffer))); 1396 1397 item = new(nothrow) MenuItem(name); 1398 item->SetData(volume); 1399 item->SetTarget(&debug_menu_save_previous_syslog); 1400 item->SetType(MENU_ITEM_NO_CHOICE); 1401 item->SetHelpText(kHelpText); 1402 menu->AddItem(item); 1403 itemsAdded++; 1404 } 1405 1406 gRoot->Close(cookie); 1407 } 1408 1409 if (itemsAdded == 0) { 1410 menu->AddItem(item 1411 = new(nothrow) MenuItem("No supported volumes found")); 1412 item->SetType(MENU_ITEM_NO_CHOICE); 1413 item->SetHelpText(kHelpText); 1414 item->SetEnabled(false); 1415 } 1416 1417 menu->AddSeparatorItem(); 1418 menu->AddItem(item = new(nothrow) MenuItem("Return to debug menu")); 1419 item->SetHelpText(kHelpText); 1420 1421 return menu; 1422 } 1423 1424 1425 static Menu* 1426 add_debug_menu() 1427 { 1428 Menu* menu = new(std::nothrow) Menu(STANDARD_MENU, "Debug Options"); 1429 MenuItem* item; 1430 1431 #if DEBUG_SPINLOCK_LATENCIES 1432 item = new(std::nothrow) MenuItem("Disable latency checks"); 1433 if (item != NULL) { 1434 item->SetType(MENU_ITEM_MARKABLE); 1435 item->SetData(B_SAFEMODE_DISABLE_LATENCY_CHECK); 1436 item->SetHelpText("Disables latency check panics."); 1437 menu->AddItem(item); 1438 } 1439 #endif 1440 1441 menu->AddItem(item 1442 = new(nothrow) MenuItem("Enable serial debug output")); 1443 item->SetData("serial_debug_output"); 1444 item->SetType(MENU_ITEM_MARKABLE); 1445 item->SetHelpText("Turns on forwarding the syslog output to the serial " 1446 "interface (default: 115200, 8N1)."); 1447 1448 menu->AddItem(item 1449 = new(nothrow) MenuItem("Enable on screen debug output")); 1450 item->SetData("debug_screen"); 1451 item->SetType(MENU_ITEM_MARKABLE); 1452 item->SetHelpText("Displays debug output on screen while the system " 1453 "is booting, instead of the normal boot logo."); 1454 1455 menu->AddItem(item 1456 = new(nothrow) MenuItem("Disable on screen paging")); 1457 item->SetData("disable_onscreen_paging"); 1458 item->SetType(MENU_ITEM_MARKABLE); 1459 item->SetHelpText("Disables paging when on screen debug output is " 1460 "enabled."); 1461 1462 menu->AddItem(item = new(nothrow) MenuItem("Enable debug syslog")); 1463 item->SetType(MENU_ITEM_MARKABLE); 1464 item->SetMarked(gKernelArgs.keep_debug_output_buffer); 1465 item->SetTarget(&debug_menu_toggle_debug_syslog); 1466 item->SetHelpText("Enables a special in-memory syslog buffer for this " 1467 "session that the boot loader will be able to access after rebooting."); 1468 1469 ring_buffer* syslogBuffer 1470 = (ring_buffer*)gKernelArgs.debug_output.Pointer(); 1471 bool hasPreviousSyslog 1472 = syslogBuffer != NULL && ring_buffer_readable(syslogBuffer) > 0; 1473 if (hasPreviousSyslog) { 1474 menu->AddItem(item = new(nothrow) MenuItem( 1475 "Save syslog from previous session during boot")); 1476 item->SetType(MENU_ITEM_MARKABLE); 1477 item->SetMarked(gKernelArgs.previous_debug_size); 1478 item->SetTarget(&debug_menu_toggle_previous_debug_syslog); 1479 item->SetHelpText("Saves the syslog from the previous Haiku session to " 1480 "/var/log/previous_syslog when booting."); 1481 } 1482 1483 bool currentLogItemVisible = platform_debug_get_log_buffer(NULL) != NULL; 1484 if (currentLogItemVisible) { 1485 menu->AddSeparatorItem(); 1486 menu->AddItem(item 1487 = new(nothrow) MenuItem("Display current boot loader log")); 1488 item->SetTarget(&debug_menu_display_current_log); 1489 item->SetType(MENU_ITEM_NO_CHOICE); 1490 item->SetHelpText( 1491 "Displays the debug info the boot loader has logged."); 1492 } 1493 1494 if (hasPreviousSyslog) { 1495 if (!currentLogItemVisible) 1496 menu->AddSeparatorItem(); 1497 1498 menu->AddItem(item 1499 = new(nothrow) MenuItem("Display syslog from previous session")); 1500 item->SetTarget(&debug_menu_display_previous_syslog); 1501 item->SetType(MENU_ITEM_NO_CHOICE); 1502 item->SetHelpText( 1503 "Displays the syslog from the previous Haiku session."); 1504 1505 menu->AddItem(item = new(nothrow) MenuItem( 1506 "Save syslog from previous session", add_save_debug_syslog_menu())); 1507 item->SetHelpText("Saves the syslog from the previous Haiku session to " 1508 "disk. Currently only FAT32 volumes are supported."); 1509 } 1510 1511 menu->AddSeparatorItem(); 1512 menu->AddItem(item = new(nothrow) MenuItem( 1513 "Add advanced debug option")); 1514 item->SetType(MENU_ITEM_NO_CHOICE); 1515 item->SetTarget(&debug_menu_add_advanced_option); 1516 item->SetHelpText( 1517 "Allows advanced debugging options to be entered directly."); 1518 1519 menu->AddSeparatorItem(); 1520 menu->AddItem(item = new(nothrow) MenuItem("Return to main menu")); 1521 1522 return menu; 1523 } 1524 1525 1526 static void 1527 apply_safe_mode_options(Menu* menu) 1528 { 1529 MenuItemIterator iterator = menu->ItemIterator(); 1530 while (MenuItem* item = iterator.Next()) { 1531 if (item->Type() == MENU_ITEM_SEPARATOR || !item->IsMarked() 1532 || item->Data() == NULL) { 1533 continue; 1534 } 1535 1536 char buffer[256]; 1537 if (snprintf(buffer, sizeof(buffer), "%s true\n", 1538 (const char*)item->Data()) >= (int)sizeof(buffer) 1539 || !sSafeModeOptionsBuffer.Append(buffer)) { 1540 dprintf("apply_safe_mode_options(): failed to append option to " 1541 "buffer\n"); 1542 } 1543 } 1544 } 1545 1546 1547 static void 1548 apply_safe_mode_path_blocklist() 1549 { 1550 if (sPathBlocklist->IsEmpty()) 1551 return; 1552 1553 bool success = sSafeModeOptionsBuffer.Append("BlockedEntries {\n"); 1554 1555 for (PathBlocklist::Iterator it = sPathBlocklist->GetIterator(); 1556 BlockedPath* path = it.Next();) { 1557 success &= sSafeModeOptionsBuffer.Append(path->Path()); 1558 success &= sSafeModeOptionsBuffer.Append("\n", 1); 1559 } 1560 1561 success &= sSafeModeOptionsBuffer.Append("}\n"); 1562 1563 // Append the option a second time using the legacy name, so it also works 1564 // for older kernel. 1565 // TODO remove this after R1 beta3 is released and no one is using older 1566 // kernels anymore. 1567 success &= sSafeModeOptionsBuffer.Append("EntryBlacklist {\n"); 1568 1569 for (PathBlocklist::Iterator it = sPathBlocklist->GetIterator(); 1570 BlockedPath* path = it.Next();) { 1571 success &= sSafeModeOptionsBuffer.Append(path->Path()); 1572 success &= sSafeModeOptionsBuffer.Append("\n", 1); 1573 } 1574 1575 success &= sSafeModeOptionsBuffer.Append("}\n"); 1576 1577 if (!success) { 1578 dprintf("apply_safe_mode_options(): failed to append path " 1579 "blocklist to buffer\n"); 1580 } 1581 } 1582 1583 1584 static bool 1585 user_menu_reboot(Menu* menu, MenuItem* item) 1586 { 1587 platform_exit(); 1588 return true; 1589 } 1590 1591 1592 status_t 1593 user_menu(BootVolume& _bootVolume, PathBlocklist& _pathBlocklist) 1594 { 1595 1596 Menu* menu = new(std::nothrow) Menu(MAIN_MENU); 1597 1598 sMainMenu = menu; 1599 sBootVolume = &_bootVolume; 1600 sPathBlocklist = &_pathBlocklist; 1601 1602 Menu* safeModeMenu = NULL; 1603 Menu* debugMenu = NULL; 1604 MenuItem* item; 1605 1606 TRACE(("user_menu: enter\n")); 1607 1608 // Add boot volume 1609 menu->AddItem(item = new(std::nothrow) MenuItem("Select boot volume/state", 1610 add_boot_volume_menu())); 1611 1612 // Add safe mode 1613 menu->AddItem(item = new(std::nothrow) MenuItem("Select safe mode options", 1614 safeModeMenu = add_safe_mode_menu())); 1615 1616 // add debug menu 1617 menu->AddItem(item = new(std::nothrow) MenuItem("Select debug options", 1618 debugMenu = add_debug_menu())); 1619 1620 // Add platform dependent menus 1621 platform_add_menus(menu); 1622 1623 menu->AddSeparatorItem(); 1624 1625 menu->AddItem(item = new(std::nothrow) MenuItem("Reboot")); 1626 item->SetTarget(user_menu_reboot); 1627 item->SetShortcut('r'); 1628 1629 menu->AddItem(item = new(std::nothrow) MenuItem("Continue booting")); 1630 if (!_bootVolume.IsValid()) { 1631 item->SetLabel("Cannot continue booting (Boot volume is not valid)"); 1632 item->SetEnabled(false); 1633 menu->ItemAt(0)->Select(true); 1634 } else 1635 item->SetShortcut('b'); 1636 1637 menu->Run(); 1638 1639 apply_safe_mode_options(safeModeMenu); 1640 apply_safe_mode_options(debugMenu); 1641 apply_safe_mode_path_blocklist(); 1642 add_safe_mode_settings(sSafeModeOptionsBuffer.String()); 1643 delete menu; 1644 1645 1646 TRACE(("user_menu: leave\n")); 1647 1648 sMainMenu = NULL; 1649 sBlocklistRootMenu = NULL; 1650 sBootVolume = NULL; 1651 sPathBlocklist = NULL; 1652 return B_OK; 1653 } 1654