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