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()
48static Menu* sMainMenu = NULL;
49static Menu* sBlocklistRootMenu = NULL;
50static BootVolume* sBootVolume = NULL;
51static PathBlocklist* sPathBlocklist;
52
53
54MenuItem::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
72MenuItem::~MenuItem()
73{
74	delete fSubMenu;
75	free(const_cast<char *>(fLabel));
76}
77
78
79void
80MenuItem::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
92void
93MenuItem::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
117void
118MenuItem::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
137void
138MenuItem::SetType(menu_item_type type)
139{
140	fType = type;
141}
142
143
144void
145MenuItem::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
157void
158MenuItem::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*/
170void
171MenuItem::SetHelpText(const char* text)
172{
173	fHelpText = text;
174}
175
176
177void
178MenuItem::SetShortcut(char key)
179{
180	fShortcut = key;
181}
182
183
184void
185MenuItem::SetLabel(const char* label)
186{
187	if (char* newLabel = strdup(label)) {
188		free(const_cast<char*>(fLabel));
189		fLabel = newLabel;
190	}
191}
192
193
194void
195MenuItem::SetSubmenu(Menu* subMenu)
196{
197	fSubMenu = subMenu;
198
199	if (fSubMenu != NULL)
200		fSubMenu->fSuperItem = this;
201}
202
203
204void
205MenuItem::SetMenu(Menu* menu)
206{
207	fMenu = menu;
208}
209
210
211//	#pragma mark -
212
213
214Menu::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
227Menu::~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
239void
240Menu::Entered()
241{
242}
243
244
245void
246Menu::Exited()
247{
248}
249
250
251MenuItem*
252Menu::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
269int32
270Menu::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
286int32
287Menu::CountItems() const
288{
289	return fCount;
290}
291
292
293MenuItem*
294Menu::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
306MenuItem*
307Menu::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
319MenuItem*
320Menu::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
339void
340Menu::AddItem(MenuItem* item)
341{
342	item->fMenu = this;
343	fItems.Add(item);
344	fCount++;
345}
346
347
348status_t
349Menu::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
362MenuItem*
363Menu::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
380void
381Menu::RemoveItem(MenuItem* item)
382{
383	item->fMenu = NULL;
384	fItems.Remove(item);
385	fCount--;
386}
387
388
389void
390Menu::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
404shortcut_hook
405Menu::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
427MenuItem*
428Menu::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
448void
449Menu::SortItems(bool (*less)(const MenuItem*, const MenuItem*))
450{
451	fItems.Sort(less);
452}
453
454
455void
456Menu::Run()
457{
458	platform_run_menu(this);
459}
460
461
462void
463Menu::Draw(MenuItem* item)
464{
465	if (!IsHidden())
466		platform_update_menu_item(this, item);
467}
468
469
470//	#pragma mark -
471
472
473static const char*
474size_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
524class BlocklistMenuItem : public MenuItem {
525public:
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
589private:
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
612private:
613	Node*	fNode;
614	Menu*	fSubMenu;
615};
616
617
618class BlocklistMenu : public Menu {
619public:
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
666protected:
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
678private:
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
718private:
719	Directory*	fDirectory;
720
721protected:
722	static const char* const kDefaultMenuTitle;
723};
724
725
726const char* const BlocklistMenu::kDefaultMenuTitle
727	= "Mark the components to disable";
728
729
730class BlocklistRootMenu : public BlocklistMenu {
731public:
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
772class BootVolumeMenuItem : public MenuItem {
773public:
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
802private:
803	char*	fStateChoiceText;
804};
805
806
807class PackageVolumeStateMenuItem : public MenuItem {
808public:
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
834private:
835	PackageVolumeInfo*	fVolumeInfo;
836	PackageVolumeState*	fVolumeState;
837};
838
839
840// #pragma mark -
841
842
843class StringBuffer {
844public:
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
883private:
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
904private:
905	char*	fBuffer;
906	size_t	fLength;
907	size_t	fCapacity;
908};
909
910
911// #pragma mark -
912
913
914static StringBuffer sSafeModeOptionsBuffer;
915
916
917static MenuItem*
918get_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
927static void
928update_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
950static bool
951user_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
966static bool
967user_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
993static bool
994debug_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
1038static bool
1039debug_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
1072static status_t
1073save_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
1125static bool
1126debug_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
1145static bool
1146debug_menu_toggle_debug_syslog(Menu* menu, MenuItem* item)
1147{
1148	gKernelArgs.keep_debug_output_buffer = item->IsMarked();
1149	return true;
1150}
1151
1152
1153static bool
1154debug_menu_toggle_previous_debug_syslog(Menu* menu, MenuItem* item)
1155{
1156	gKernelArgs.previous_debug_size = item->IsMarked();
1157	return true;
1158}
1159
1160
1161static bool
1162debug_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
1177static void
1178add_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
1238static Menu*
1239add_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
1289static Menu*
1290add_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
1355static Menu*
1356add_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
1425static Menu*
1426add_debug_menu()
1427{
1428	Menu* menu = new(std::nothrow) Menu(STANDARD_MENU, "Debug Options");
1429	MenuItem* item;
1430
1431	menu->AddItem(item
1432		= new(nothrow) MenuItem("Enable serial debug output"));
1433	item->SetData("serial_debug_output");
1434	item->SetType(MENU_ITEM_MARKABLE);
1435	item->SetHelpText("Turns on forwarding the syslog output to the serial "
1436		"interface (default: 115200, 8N1).");
1437
1438	menu->AddItem(item
1439		= new(nothrow) MenuItem("Enable on screen debug output"));
1440	item->SetData("debug_screen");
1441	item->SetType(MENU_ITEM_MARKABLE);
1442	item->SetHelpText("Displays debug output on screen while the system "
1443		"is booting, instead of the normal boot logo.");
1444
1445	menu->AddItem(item
1446		= new(nothrow) MenuItem("Disable on screen paging"));
1447	item->SetData("disable_onscreen_paging");
1448	item->SetType(MENU_ITEM_MARKABLE);
1449	item->SetHelpText("Disables paging when on screen debug output is "
1450		"enabled.");
1451
1452	menu->AddItem(item = new(nothrow) MenuItem("Enable debug syslog"));
1453	item->SetType(MENU_ITEM_MARKABLE);
1454	item->SetMarked(gKernelArgs.keep_debug_output_buffer);
1455	item->SetTarget(&debug_menu_toggle_debug_syslog);
1456    item->SetHelpText("Enables a special in-memory syslog buffer for this "
1457    	"session that the boot loader will be able to access after rebooting.");
1458
1459	ring_buffer* syslogBuffer
1460		= (ring_buffer*)gKernelArgs.debug_output.Pointer();
1461	bool hasPreviousSyslog
1462		= syslogBuffer != NULL && ring_buffer_readable(syslogBuffer) > 0;
1463	if (hasPreviousSyslog) {
1464		menu->AddItem(item = new(nothrow) MenuItem(
1465			"Save syslog from previous session during boot"));
1466		item->SetType(MENU_ITEM_MARKABLE);
1467		item->SetMarked(gKernelArgs.previous_debug_size);
1468		item->SetTarget(&debug_menu_toggle_previous_debug_syslog);
1469		item->SetHelpText("Saves the syslog from the previous Haiku session to "
1470			"/var/log/previous_syslog when booting.");
1471	}
1472
1473	bool currentLogItemVisible = platform_debug_get_log_buffer(NULL) != NULL;
1474	if (currentLogItemVisible) {
1475		menu->AddSeparatorItem();
1476		menu->AddItem(item
1477			= new(nothrow) MenuItem("Display current boot loader log"));
1478		item->SetTarget(&debug_menu_display_current_log);
1479		item->SetType(MENU_ITEM_NO_CHOICE);
1480		item->SetHelpText(
1481			"Displays the debug info the boot loader has logged.");
1482	}
1483
1484	if (hasPreviousSyslog) {
1485		if (!currentLogItemVisible)
1486			menu->AddSeparatorItem();
1487
1488		menu->AddItem(item
1489			= new(nothrow) MenuItem("Display syslog from previous session"));
1490		item->SetTarget(&debug_menu_display_previous_syslog);
1491		item->SetType(MENU_ITEM_NO_CHOICE);
1492		item->SetHelpText(
1493			"Displays the syslog from the previous Haiku session.");
1494
1495		menu->AddItem(item = new(nothrow) MenuItem(
1496			"Save syslog from previous session", add_save_debug_syslog_menu()));
1497		item->SetHelpText("Saves the syslog from the previous Haiku session to "
1498			"disk. Currently only FAT32 volumes are supported.");
1499	}
1500
1501	menu->AddSeparatorItem();
1502	menu->AddItem(item = new(nothrow) MenuItem(
1503		"Add advanced debug option"));
1504	item->SetType(MENU_ITEM_NO_CHOICE);
1505	item->SetTarget(&debug_menu_add_advanced_option);
1506	item->SetHelpText(
1507		"Allows advanced debugging options to be entered directly.");
1508
1509	menu->AddSeparatorItem();
1510	menu->AddItem(item = new(nothrow) MenuItem("Return to main menu"));
1511
1512	return menu;
1513}
1514
1515
1516static void
1517apply_safe_mode_options(Menu* menu)
1518{
1519	MenuItemIterator iterator = menu->ItemIterator();
1520	while (MenuItem* item = iterator.Next()) {
1521		if (item->Type() == MENU_ITEM_SEPARATOR || !item->IsMarked()
1522			|| item->Data() == NULL) {
1523			continue;
1524		}
1525
1526		char buffer[256];
1527		if (snprintf(buffer, sizeof(buffer), "%s true\n",
1528				(const char*)item->Data()) >= (int)sizeof(buffer)
1529			|| !sSafeModeOptionsBuffer.Append(buffer)) {
1530			dprintf("apply_safe_mode_options(): failed to append option to "
1531				"buffer\n");
1532		}
1533	}
1534}
1535
1536
1537static void
1538apply_safe_mode_path_blocklist()
1539{
1540	if (sPathBlocklist->IsEmpty())
1541		return;
1542
1543	bool success = sSafeModeOptionsBuffer.Append("BlockedEntries {\n");
1544
1545	for (PathBlocklist::Iterator it = sPathBlocklist->GetIterator();
1546		BlockedPath* path = it.Next();) {
1547		success &= sSafeModeOptionsBuffer.Append(path->Path());
1548		success &= sSafeModeOptionsBuffer.Append("\n", 1);
1549	}
1550
1551	success &= sSafeModeOptionsBuffer.Append("}\n");
1552
1553	// Append the option a second time using the legacy name, so it also works
1554	// for older kernel.
1555	// TODO remove this after R1 beta3 is released and no one is using older
1556	// kernels anymore.
1557	success &= sSafeModeOptionsBuffer.Append("EntryBlacklist {\n");
1558
1559	for (PathBlocklist::Iterator it = sPathBlocklist->GetIterator();
1560		BlockedPath* path = it.Next();) {
1561		success &= sSafeModeOptionsBuffer.Append(path->Path());
1562		success &= sSafeModeOptionsBuffer.Append("\n", 1);
1563	}
1564
1565	success &= sSafeModeOptionsBuffer.Append("}\n");
1566
1567	if (!success) {
1568		dprintf("apply_safe_mode_options(): failed to append path "
1569			"blocklist to buffer\n");
1570	}
1571}
1572
1573
1574static bool
1575user_menu_reboot(Menu* menu, MenuItem* item)
1576{
1577	platform_exit();
1578	return true;
1579}
1580
1581
1582status_t
1583user_menu(BootVolume& _bootVolume, PathBlocklist& _pathBlocklist)
1584{
1585
1586	Menu* menu = new(std::nothrow) Menu(MAIN_MENU);
1587
1588	sMainMenu = menu;
1589	sBootVolume = &_bootVolume;
1590	sPathBlocklist = &_pathBlocklist;
1591
1592	Menu* safeModeMenu = NULL;
1593	Menu* debugMenu = NULL;
1594	MenuItem* item;
1595
1596	TRACE(("user_menu: enter\n"));
1597
1598	// Add boot volume
1599	menu->AddItem(item = new(std::nothrow) MenuItem("Select boot volume/state",
1600		add_boot_volume_menu()));
1601
1602	// Add safe mode
1603	menu->AddItem(item = new(std::nothrow) MenuItem("Select safe mode options",
1604		safeModeMenu = add_safe_mode_menu()));
1605
1606	// add debug menu
1607	menu->AddItem(item = new(std::nothrow) MenuItem("Select debug options",
1608		debugMenu = add_debug_menu()));
1609
1610	// Add platform dependent menus
1611	platform_add_menus(menu);
1612
1613	menu->AddSeparatorItem();
1614
1615	menu->AddItem(item = new(std::nothrow) MenuItem("Reboot"));
1616	item->SetTarget(user_menu_reboot);
1617	item->SetShortcut('r');
1618
1619	menu->AddItem(item = new(std::nothrow) MenuItem("Continue booting"));
1620	if (!_bootVolume.IsValid()) {
1621		item->SetLabel("Cannot continue booting (Boot volume is not valid)");
1622		item->SetEnabled(false);
1623		menu->ItemAt(0)->Select(true);
1624	} else
1625		item->SetShortcut('b');
1626
1627	menu->Run();
1628
1629	apply_safe_mode_options(safeModeMenu);
1630	apply_safe_mode_options(debugMenu);
1631	apply_safe_mode_path_blocklist();
1632	add_safe_mode_settings(sSafeModeOptionsBuffer.String());
1633	delete menu;
1634
1635
1636	TRACE(("user_menu: leave\n"));
1637
1638	sMainMenu = NULL;
1639	sBlocklistRootMenu = NULL;
1640	sBootVolume = NULL;
1641	sPathBlocklist = NULL;
1642	return B_OK;
1643}
1644