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