xref: /freebsd/stand/lua/menu.lua (revision 3fb656f8ef21332d96de8097521aaa51ddeb649d)
1088b4f5fSWarner Losh--
24d846d26SWarner Losh-- SPDX-License-Identifier: BSD-2-Clause
372e39d71SKyle Evans--
4088b4f5fSWarner Losh-- Copyright (c) 2015 Pedro Souza <pedrosouza@freebsd.org>
5e12ff891SKyle Evans-- Copyright (c) 2018 Kyle Evans <kevans@FreeBSD.org>
6088b4f5fSWarner Losh-- All rights reserved.
7088b4f5fSWarner Losh--
8088b4f5fSWarner Losh-- Redistribution and use in source and binary forms, with or without
9088b4f5fSWarner Losh-- modification, are permitted provided that the following conditions
10088b4f5fSWarner Losh-- are met:
11088b4f5fSWarner Losh-- 1. Redistributions of source code must retain the above copyright
12088b4f5fSWarner Losh--    notice, this list of conditions and the following disclaimer.
13088b4f5fSWarner Losh-- 2. Redistributions in binary form must reproduce the above copyright
14088b4f5fSWarner Losh--    notice, this list of conditions and the following disclaimer in the
15088b4f5fSWarner Losh--    documentation and/or other materials provided with the distribution.
16088b4f5fSWarner Losh--
17088b4f5fSWarner Losh-- THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18088b4f5fSWarner Losh-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19088b4f5fSWarner Losh-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20088b4f5fSWarner Losh-- ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21088b4f5fSWarner Losh-- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22088b4f5fSWarner Losh-- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23088b4f5fSWarner Losh-- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24088b4f5fSWarner Losh-- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25088b4f5fSWarner Losh-- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26088b4f5fSWarner Losh-- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27088b4f5fSWarner Losh-- SUCH DAMAGE.
28088b4f5fSWarner Losh--
29088b4f5fSWarner Losh
3076c75816SKyle Evanslocal cli = require("cli")
31aedd6be5SKyle Evanslocal core = require("core")
32aedd6be5SKyle Evanslocal color = require("color")
33aedd6be5SKyle Evanslocal config = require("config")
34aedd6be5SKyle Evanslocal screen = require("screen")
35aedd6be5SKyle Evanslocal drawer = require("drawer")
36088b4f5fSWarner Losh
37aedd6be5SKyle Evanslocal menu = {}
38c8518398SKyle Evans
39beafe961SKyle Evanslocal drawn_menu
4043f7d9d1SKyle Evanslocal return_menu_entry = {
4143f7d9d1SKyle Evans	entry_type = core.MENU_RETURN,
4243f7d9d1SKyle Evans	name = "Back to main menu" .. color.highlight(" [Backspace]"),
4343f7d9d1SKyle Evans}
44ca16d83fSKyle Evans
4504af4229SKyle Evanslocal function OnOff(str, value)
4604af4229SKyle Evans	if value then
478ce1744fSKyle Evans		return str .. color.escapefg(color.GREEN) .. "On" ..
486dd078dfSToomas Soome		    color.resetfg()
49e15abd1fSKyle Evans	else
508ce1744fSKyle Evans		return str .. color.escapefg(color.RED) .. "off" ..
516dd078dfSToomas Soome		    color.resetfg()
52e15abd1fSKyle Evans	end
53e15abd1fSKyle Evansend
54e15abd1fSKyle Evans
559ed6f9efSKyle Evanslocal function bootenvSet(env)
567efc058fSKyle Evans	loader.setenv("vfs.root.mountfrom", env)
577efc058fSKyle Evans	loader.setenv("currdev", env .. ":")
587efc058fSKyle Evans	config.reload()
59d7584aa0SCyrus Rahman	if loader.getenv("kernelname") ~= nil then
60d7584aa0SCyrus Rahman		loader.perform("unload")
61d7584aa0SCyrus Rahman	end
627efc058fSKyle Evansend
637efc058fSKyle Evans
649b17aa27SJessica Clarkelocal function multiUserPrompt()
659b17aa27SJessica Clarke	return loader.getenv("loader_menu_multi_user_prompt") or "Multi user"
669b17aa27SJessica Clarkeend
679b17aa27SJessica Clarke
68b5746545SKyle Evans-- Module exports
698d415029SKyle Evansmenu.handlers = {
708d415029SKyle Evans	-- Menu handlers take the current menu and selected entry as parameters,
718d415029SKyle Evans	-- and should return a boolean indicating whether execution should
728d415029SKyle Evans	-- continue or not. The return value may be omitted if this entry should
738d415029SKyle Evans	-- have no bearing on whether we continue or not, indicating that we
748d415029SKyle Evans	-- should just continue after execution.
75e2df27e3SKyle Evans	[core.MENU_ENTRY] = function(_, entry)
768d415029SKyle Evans		-- run function
77aedd6be5SKyle Evans		entry.func()
788d415029SKyle Evans	end,
79e2df27e3SKyle Evans	[core.MENU_CAROUSEL_ENTRY] = function(_, entry)
808d415029SKyle Evans		-- carousel (rotating) functionality
81aedd6be5SKyle Evans		local carid = entry.carousel_id
82aedd6be5SKyle Evans		local caridx = config.getCarouselIndex(carid)
834f437f9eSKyle Evans		local choices = entry.items
844f437f9eSKyle Evans		if type(choices) == "function" then
854f437f9eSKyle Evans			choices = choices()
864f437f9eSKyle Evans		end
879f71d421SKyle Evans		if #choices > 0 then
88aedd6be5SKyle Evans			caridx = (caridx % #choices) + 1
89aedd6be5SKyle Evans			config.setCarouselIndex(carid, caridx)
90aedd6be5SKyle Evans			entry.func(caridx, choices[caridx], choices)
918d415029SKyle Evans		end
928d415029SKyle Evans	end,
93e2df27e3SKyle Evans	[core.MENU_SUBMENU] = function(_, entry)
94da9ab827SKyle Evans		menu.process(entry.submenu)
958d415029SKyle Evans	end,
96e2df27e3SKyle Evans	[core.MENU_RETURN] = function(_, entry)
978d415029SKyle Evans		-- allow entry to have a function/side effect
989f71d421SKyle Evans		if entry.func ~= nil then
99aedd6be5SKyle Evans			entry.func()
1008d415029SKyle Evans		end
101aedd6be5SKyle Evans		return false
1028d415029SKyle Evans	end,
103aedd6be5SKyle Evans}
104280e990bSKyle Evans-- loader menu tree is rooted at menu.welcome
105088b4f5fSWarner Losh
1067efc058fSKyle Evansmenu.boot_environments = {
1077efc058fSKyle Evans	entries = {
1087efc058fSKyle Evans		-- return to welcome menu
10943f7d9d1SKyle Evans		return_menu_entry,
1107efc058fSKyle Evans		{
1117efc058fSKyle Evans			entry_type = core.MENU_CAROUSEL_ENTRY,
1127efc058fSKyle Evans			carousel_id = "be_active",
1137efc058fSKyle Evans			items = core.bootenvList,
1147efc058fSKyle Evans			name = function(idx, choice, all_choices)
1157efc058fSKyle Evans				if #all_choices == 0 then
1167efc058fSKyle Evans					return "Active: "
1177efc058fSKyle Evans				end
1187efc058fSKyle Evans
1197efc058fSKyle Evans				local is_default = (idx == 1)
1207efc058fSKyle Evans				local bootenv_name = ""
1217efc058fSKyle Evans				local name_color
1227efc058fSKyle Evans				if is_default then
1238ce1744fSKyle Evans					name_color = color.escapefg(color.GREEN)
1247efc058fSKyle Evans				else
1258ce1744fSKyle Evans					name_color = color.escapefg(color.BLUE)
1267efc058fSKyle Evans				end
1277efc058fSKyle Evans				bootenv_name = bootenv_name .. name_color ..
1288ce1744fSKyle Evans				    choice .. color.resetfg()
1297efc058fSKyle Evans				return color.highlight("A").."ctive: " ..
1307efc058fSKyle Evans				    bootenv_name .. " (" .. idx .. " of " ..
1317efc058fSKyle Evans				    #all_choices .. ")"
1327efc058fSKyle Evans			end,
133e2df27e3SKyle Evans			func = function(_, choice, _)
1347efc058fSKyle Evans				bootenvSet(choice)
1357efc058fSKyle Evans			end,
1367efc058fSKyle Evans			alias = {"a", "A"},
1377efc058fSKyle Evans		},
1387efc058fSKyle Evans		{
1397efc058fSKyle Evans			entry_type = core.MENU_ENTRY,
140277f38abSMariusz Zaborski			visible = function()
141277f38abSMariusz Zaborski				return core.isRewinded() == false
142277f38abSMariusz Zaborski			end,
1437efc058fSKyle Evans			name = function()
1447efc058fSKyle Evans				return color.highlight("b") .. "ootfs: " ..
1457efc058fSKyle Evans				    core.bootenvDefault()
1467efc058fSKyle Evans			end,
1477efc058fSKyle Evans			func = function()
1487efc058fSKyle Evans				-- Reset active boot environment to the default
1497efc058fSKyle Evans				config.setCarouselIndex("be_active", 1)
1507efc058fSKyle Evans				bootenvSet(core.bootenvDefault())
1517efc058fSKyle Evans			end,
1527efc058fSKyle Evans			alias = {"b", "B"},
1537efc058fSKyle Evans		},
1547efc058fSKyle Evans	},
1557efc058fSKyle Evans}
1567efc058fSKyle Evans
157088b4f5fSWarner Loshmenu.boot_options = {
158d8757746SKyle Evans	entries = {
159088b4f5fSWarner Losh		-- return to welcome menu
16043f7d9d1SKyle Evans		return_menu_entry,
161088b4f5fSWarner Losh		-- load defaults
162088b4f5fSWarner Losh		{
163a7cf0562SKyle Evans			entry_type = core.MENU_ENTRY,
164a51f9f0cSKyle Evans			name = "Load System " .. color.highlight("D") ..
165a51f9f0cSKyle Evans			    "efaults",
166a51f9f0cSKyle Evans			func = core.setDefaults,
1673cd5547bSKyle Evans			alias = {"d", "D"},
168088b4f5fSWarner Losh		},
169088b4f5fSWarner Losh		{
170a7cf0562SKyle Evans			entry_type = core.MENU_SEPARATOR,
171088b4f5fSWarner Losh		},
172088b4f5fSWarner Losh		{
173a7cf0562SKyle Evans			entry_type = core.MENU_SEPARATOR,
174a51f9f0cSKyle Evans			name = "Boot Options:",
175088b4f5fSWarner Losh		},
176088b4f5fSWarner Losh		-- acpi
177088b4f5fSWarner Losh		{
178a7cf0562SKyle Evans			entry_type = core.MENU_ENTRY,
179e0f3dc82SR. Christian McDonald			visible = core.hasACPI,
180088b4f5fSWarner Losh			name = function()
181fd2b19b3SKyle Evans				return OnOff(color.highlight("A") ..
182aedd6be5SKyle Evans				    "CPI       :", core.acpi)
183088b4f5fSWarner Losh			end,
184a51f9f0cSKyle Evans			func = core.setACPI,
1853cd5547bSKyle Evans			alias = {"a", "A"},
186088b4f5fSWarner Losh		},
187088b4f5fSWarner Losh		-- safe mode
188088b4f5fSWarner Losh		{
189a7cf0562SKyle Evans			entry_type = core.MENU_ENTRY,
190088b4f5fSWarner Losh			name = function()
19157099121SKyle Evans				return OnOff("Safe " .. color.highlight("M") ..
192aedd6be5SKyle Evans				    "ode  :", core.sm)
193088b4f5fSWarner Losh			end,
194a51f9f0cSKyle Evans			func = core.setSafeMode,
1953cd5547bSKyle Evans			alias = {"m", "M"},
196088b4f5fSWarner Losh		},
197088b4f5fSWarner Losh		-- single user
198088b4f5fSWarner Losh		{
199a7cf0562SKyle Evans			entry_type = core.MENU_ENTRY,
200088b4f5fSWarner Losh			name = function()
201fd2b19b3SKyle Evans				return OnOff(color.highlight("S") ..
202aedd6be5SKyle Evans				    "ingle user:", core.su)
203088b4f5fSWarner Losh			end,
204a51f9f0cSKyle Evans			func = core.setSingleUser,
2053cd5547bSKyle Evans			alias = {"s", "S"},
206088b4f5fSWarner Losh		},
207088b4f5fSWarner Losh		-- verbose boot
208088b4f5fSWarner Losh		{
209a7cf0562SKyle Evans			entry_type = core.MENU_ENTRY,
210088b4f5fSWarner Losh			name = function()
211fd2b19b3SKyle Evans				return OnOff(color.highlight("V") ..
212aedd6be5SKyle Evans				    "erbose    :", core.verbose)
213088b4f5fSWarner Losh			end,
214a51f9f0cSKyle Evans			func = core.setVerbose,
2153cd5547bSKyle Evans			alias = {"v", "V"},
216088b4f5fSWarner Losh		},
217d8757746SKyle Evans	},
218aedd6be5SKyle Evans}
219088b4f5fSWarner Losh
220088b4f5fSWarner Loshmenu.welcome = {
221303253e5SKyle Evans	entries = function()
222aedd6be5SKyle Evans		local menu_entries = menu.welcome.all_entries
223d2187b39SRyan Moeller		local multi_user = menu_entries.multi_user
224d2187b39SRyan Moeller		local single_user = menu_entries.single_user
225d2187b39SRyan Moeller		local boot_entry_1, boot_entry_2
2269f71d421SKyle Evans		if core.isSingleUserBoot() then
227d2187b39SRyan Moeller			-- Swap the first two menu items on single user boot.
228d2187b39SRyan Moeller			-- We'll cache the alternate entries for performance.
229d2187b39SRyan Moeller			local alts = menu_entries.alts
230d2187b39SRyan Moeller			if alts == nil then
231d2187b39SRyan Moeller				single_user = core.deepCopyTable(single_user)
232d2187b39SRyan Moeller				multi_user = core.deepCopyTable(multi_user)
233d2187b39SRyan Moeller				single_user.name = single_user.alternate_name
234d2187b39SRyan Moeller				multi_user.name = multi_user.alternate_name
235d2187b39SRyan Moeller				menu_entries.alts = {
236d2187b39SRyan Moeller					single_user = single_user,
237d2187b39SRyan Moeller					multi_user = multi_user,
238d2187b39SRyan Moeller				}
239d2187b39SRyan Moeller			else
240d2187b39SRyan Moeller				single_user = alts.single_user
241d2187b39SRyan Moeller				multi_user = alts.multi_user
2429a0904b0SKyle Evans			end
243d2187b39SRyan Moeller			boot_entry_1, boot_entry_2 = single_user, multi_user
244d2187b39SRyan Moeller		else
245d2187b39SRyan Moeller			boot_entry_1, boot_entry_2 = multi_user, single_user
246303253e5SKyle Evans		end
247d2187b39SRyan Moeller		return {
248d2187b39SRyan Moeller			boot_entry_1,
249d2187b39SRyan Moeller			boot_entry_2,
250d2187b39SRyan Moeller			menu_entries.prompt,
251d2187b39SRyan Moeller			menu_entries.reboot,
2528f3b3610SWarner Losh			menu_entries.console,
253d2187b39SRyan Moeller			{
254d2187b39SRyan Moeller				entry_type = core.MENU_SEPARATOR,
255d2187b39SRyan Moeller			},
256d2187b39SRyan Moeller			{
257d2187b39SRyan Moeller				entry_type = core.MENU_SEPARATOR,
258d2187b39SRyan Moeller				name = "Options:",
259d2187b39SRyan Moeller			},
260d2187b39SRyan Moeller			menu_entries.kernel_options,
261d2187b39SRyan Moeller			menu_entries.boot_options,
262277f38abSMariusz Zaborski			menu_entries.zpool_checkpoints,
263d2187b39SRyan Moeller			menu_entries.boot_envs,
264d2187b39SRyan Moeller			menu_entries.chainload,
265e7ccd5b4SWarner Losh			menu_entries.vendor,
2667cb65be9SWarner Losh			{
2677cb65be9SWarner Losh				entry_type = core.MENU_SEPARATOR,
2687cb65be9SWarner Losh			},
2697cb65be9SWarner Losh			menu_entries.loader_needs_upgrade,
270d2187b39SRyan Moeller		}
271303253e5SKyle Evans	end,
272303253e5SKyle Evans	all_entries = {
273d2187b39SRyan Moeller		multi_user = {
274a7cf0562SKyle Evans			entry_type = core.MENU_ENTRY,
2759b17aa27SJessica Clarke			name = function()
2769b17aa27SJessica Clarke				return color.highlight("B") .. "oot " ..
2779b17aa27SJessica Clarke				    multiUserPrompt() .. " " ..
2789b17aa27SJessica Clarke				    color.highlight("[Enter]")
2799b17aa27SJessica Clarke			end,
2805c1b5165SKyle Evans			-- Not a standard menu entry function!
2819b17aa27SJessica Clarke			alternate_name = function()
2829b17aa27SJessica Clarke				return color.highlight("B") .. "oot " ..
2839b17aa27SJessica Clarke				    multiUserPrompt()
2849b17aa27SJessica Clarke			end,
285088b4f5fSWarner Losh			func = function()
286aedd6be5SKyle Evans				core.setSingleUser(false)
287aedd6be5SKyle Evans				core.boot()
288088b4f5fSWarner Losh			end,
2893cd5547bSKyle Evans			alias = {"b", "B"},
290088b4f5fSWarner Losh		},
291d2187b39SRyan Moeller		single_user = {
292a7cf0562SKyle Evans			entry_type = core.MENU_ENTRY,
293a51f9f0cSKyle Evans			name = "Boot " .. color.highlight("S") .. "ingle user",
2945c1b5165SKyle Evans			-- Not a standard menu entry function!
295a51f9f0cSKyle Evans			alternate_name = "Boot " .. color.highlight("S") ..
296a51f9f0cSKyle Evans			    "ingle user " .. color.highlight("[Enter]"),
297088b4f5fSWarner Losh			func = function()
298aedd6be5SKyle Evans				core.setSingleUser(true)
299aedd6be5SKyle Evans				core.boot()
300088b4f5fSWarner Losh			end,
3013cd5547bSKyle Evans			alias = {"s", "S"},
302088b4f5fSWarner Losh		},
3038f3b3610SWarner Losh		console = {
3048f3b3610SWarner Losh			entry_type = core.MENU_ENTRY,
3058f3b3610SWarner Losh			name = function()
3068f3b3610SWarner Losh				return color.highlight("C") .. "ons: " .. core.getConsoleName()
3078f3b3610SWarner Losh			end,
3088f3b3610SWarner Losh			func = function()
3098f3b3610SWarner Losh				core.nextConsoleChoice()
3108f3b3610SWarner Losh			end,
3118f3b3610SWarner Losh			alias = {"c", "C"},
3128f3b3610SWarner Losh		},
313d2187b39SRyan Moeller		prompt = {
314a7cf0562SKyle Evans			entry_type = core.MENU_RETURN,
315a51f9f0cSKyle Evans			name = color.highlight("Esc") .. "ape to loader prompt",
316ef625845SKyle Evans			func = function()
317aedd6be5SKyle Evans				loader.setenv("autoboot_delay", "NO")
318ef625845SKyle Evans			end,
3193cd5547bSKyle Evans			alias = {core.KEYSTR_ESCAPE},
320088b4f5fSWarner Losh		},
321d2187b39SRyan Moeller		reboot = {
322a7cf0562SKyle Evans			entry_type = core.MENU_ENTRY,
323a51f9f0cSKyle Evans			name = color.highlight("R") .. "eboot",
324088b4f5fSWarner Losh			func = function()
325aedd6be5SKyle Evans				loader.perform("reboot")
326088b4f5fSWarner Losh			end,
3273cd5547bSKyle Evans			alias = {"r", "R"},
328088b4f5fSWarner Losh		},
329d2187b39SRyan Moeller		kernel_options = {
330a7cf0562SKyle Evans			entry_type = core.MENU_CAROUSEL_ENTRY,
331ada26c4aSKyle Evans			carousel_id = "kernel",
332ada26c4aSKyle Evans			items = core.kernelList,
333ada26c4aSKyle Evans			name = function(idx, choice, all_choices)
3349f71d421SKyle Evans				if #all_choices == 0 then
335aedd6be5SKyle Evans					return "Kernel: "
336088b4f5fSWarner Losh				end
337b1b1f2b8SKyle Evans
338aedd6be5SKyle Evans				local is_default = (idx == 1)
339aedd6be5SKyle Evans				local kernel_name = ""
340aedd6be5SKyle Evans				local name_color
3419f71d421SKyle Evans				if is_default then
3428ce1744fSKyle Evans					name_color = color.escapefg(color.GREEN)
343aedd6be5SKyle Evans					kernel_name = "default/"
344bcf48a15SKyle Evans				else
3458ce1744fSKyle Evans					name_color = color.escapefg(color.BLUE)
346b1b1f2b8SKyle Evans				end
347fd2b19b3SKyle Evans				kernel_name = kernel_name .. name_color ..
3488ce1744fSKyle Evans				    choice .. color.resetfg()
349fd2b19b3SKyle Evans				return color.highlight("K") .. "ernel: " ..
350fd2b19b3SKyle Evans				    kernel_name .. " (" .. idx .. " of " ..
351aedd6be5SKyle Evans				    #all_choices .. ")"
352088b4f5fSWarner Losh			end,
353e2df27e3SKyle Evans			func = function(_, choice, _)
354e414851fSKyle Evans				if loader.getenv("kernelname") ~= nil then
355e414851fSKyle Evans					loader.perform("unload")
356e414851fSKyle Evans				end
357322a2dddSKyle Evans				config.selectKernel(choice)
358088b4f5fSWarner Losh			end,
3593cd5547bSKyle Evans			alias = {"k", "K"},
360088b4f5fSWarner Losh		},
361d2187b39SRyan Moeller		boot_options = {
362a7cf0562SKyle Evans			entry_type = core.MENU_SUBMENU,
363a51f9f0cSKyle Evans			name = "Boot " .. color.highlight("O") .. "ptions",
3649a28f948SKyle Evans			submenu = menu.boot_options,
3653cd5547bSKyle Evans			alias = {"o", "O"},
366d8757746SKyle Evans		},
367277f38abSMariusz Zaborski		zpool_checkpoints = {
368277f38abSMariusz Zaborski			entry_type = core.MENU_ENTRY,
369277f38abSMariusz Zaborski			name = function()
37094510c29SKyle Evans				local rewind = "No"
371277f38abSMariusz Zaborski				if core.isRewinded() then
372277f38abSMariusz Zaborski					rewind = "Yes"
373277f38abSMariusz Zaborski				end
374277f38abSMariusz Zaborski				return "Rewind ZFS " .. color.highlight("C") ..
375277f38abSMariusz Zaborski					"heckpoint: " .. rewind
376277f38abSMariusz Zaborski			end,
377277f38abSMariusz Zaborski			func = function()
378277f38abSMariusz Zaborski				core.changeRewindCheckpoint()
379277f38abSMariusz Zaborski				if core.isRewinded() then
380277f38abSMariusz Zaborski					bootenvSet(
381277f38abSMariusz Zaborski					    core.bootenvDefaultRewinded())
382277f38abSMariusz Zaborski				else
383277f38abSMariusz Zaborski					bootenvSet(core.bootenvDefault())
384277f38abSMariusz Zaborski				end
385277f38abSMariusz Zaborski				config.setCarouselIndex("be_active", 1)
386277f38abSMariusz Zaborski			end,
387277f38abSMariusz Zaborski			visible = function()
388277f38abSMariusz Zaborski				return core.isZFSBoot() and
389277f38abSMariusz Zaborski				    core.isCheckpointed()
390277f38abSMariusz Zaborski			end,
391277f38abSMariusz Zaborski			alias = {"c", "C"},
392277f38abSMariusz Zaborski		},
393d2187b39SRyan Moeller		boot_envs = {
3947efc058fSKyle Evans			entry_type = core.MENU_SUBMENU,
3957efc058fSKyle Evans			visible = function()
3967efc058fSKyle Evans				return core.isZFSBoot() and
3977efc058fSKyle Evans				    #core.bootenvList() > 1
3987efc058fSKyle Evans			end,
3997efc058fSKyle Evans			name = "Boot " .. color.highlight("E") .. "nvironments",
4007efc058fSKyle Evans			submenu = menu.boot_environments,
4017efc058fSKyle Evans			alias = {"e", "E"},
4027efc058fSKyle Evans		},
403d2187b39SRyan Moeller		chainload = {
404bdf12807SKyle Evans			entry_type = core.MENU_ENTRY,
405bdf12807SKyle Evans			name = function()
406bdf12807SKyle Evans				return 'Chain' .. color.highlight("L") ..
407bdf12807SKyle Evans				    "oad " .. loader.getenv('chain_disk')
408bdf12807SKyle Evans			end,
409bdf12807SKyle Evans			func = function()
410bdf12807SKyle Evans				loader.perform("chain " ..
411bdf12807SKyle Evans				    loader.getenv('chain_disk'))
412bdf12807SKyle Evans			end,
413bdf12807SKyle Evans			visible = function()
414bdf12807SKyle Evans				return loader.getenv('chain_disk') ~= nil
415bdf12807SKyle Evans			end,
416bdf12807SKyle Evans			alias = {"l", "L"},
417bdf12807SKyle Evans		},
4180eac99f7SWarner Losh		loader_needs_upgrade = {
4190eac99f7SWarner Losh			entry_type = core.MENU_SEPARATOR,
4200eac99f7SWarner Losh			name = function()
4217cb65be9SWarner Losh				return color.highlight("Loader needs to be updated")
4227cb65be9SWarner Losh			end,
4230eac99f7SWarner Losh			visible = function()
4240eac99f7SWarner Losh				return core.loaderTooOld()
4250eac99f7SWarner Losh			end
4260eac99f7SWarner Losh		},
427e7ccd5b4SWarner Losh		vendor = {
428e7ccd5b4SWarner Losh			entry_type = core.MENU_ENTRY,
429556e66b7SWarner Losh			visible = function()
430556e66b7SWarner Losh				return false
431556e66b7SWarner Losh			end
432e7ccd5b4SWarner Losh		},
433d8757746SKyle Evans	},
434aedd6be5SKyle Evans}
435088b4f5fSWarner Losh
43620a81676SKyle Evansmenu.default = menu.welcome
43728384160SKyle Evans-- current_alias_table will be used to keep our alias table consistent across
43828384160SKyle Evans-- screen redraws, instead of relying on whatever triggered the redraw to update
43928384160SKyle Evans-- the local alias_table in menu.process.
44028384160SKyle Evansmenu.current_alias_table = {}
44120a81676SKyle Evans
4422a11b810SKyle Evansfunction menu.draw(menudef)
443beafe961SKyle Evans	-- Clear the screen, reset the cursor, then draw
444aedd6be5SKyle Evans	screen.clear()
4452a11b810SKyle Evans	menu.current_alias_table = drawer.drawscreen(menudef)
4462a11b810SKyle Evans	drawn_menu = menudef
447decacd91SKyle Evans	screen.defcursor()
44828384160SKyle Evansend
44928384160SKyle Evans
450ca16d83fSKyle Evans-- 'keypress' allows the caller to indicate that a key has been pressed that we
451ca16d83fSKyle Evans-- should process as our initial input.
4522a11b810SKyle Evansfunction menu.process(menudef, keypress)
4532a11b810SKyle Evans	assert(menudef ~= nil)
45428384160SKyle Evans
4552a11b810SKyle Evans	if drawn_menu ~= menudef then
4562a11b810SKyle Evans		menu.draw(menudef)
4577dcffa90SKyle Evans	end
458ca16d83fSKyle Evans
459da9ab827SKyle Evans	while true do
460ca16d83fSKyle Evans		local key = keypress or io.getchar()
461ca16d83fSKyle Evans		keypress = nil
462088b4f5fSWarner Losh
463b458bf0dSKyle Evans		-- Special key behaviors
4649f71d421SKyle Evans		if (key == core.KEY_BACKSPACE or key == core.KEY_DELETE) and
4652a11b810SKyle Evans		    menudef ~= menu.default then
466aedd6be5SKyle Evans			break
4679f71d421SKyle Evans		elseif key == core.KEY_ENTER then
468aedd6be5SKyle Evans			core.boot()
469041929aaSKyle Evans			-- Should not return.  If it does, escape menu handling
470041929aaSKyle Evans			-- and drop to loader prompt.
471041929aaSKyle Evans			return false
472abc4f7e7SKyle Evans		end
473abc4f7e7SKyle Evans
474abc4f7e7SKyle Evans		key = string.char(key)
475088b4f5fSWarner Losh		-- check to see if key is an alias
476aedd6be5SKyle Evans		local sel_entry = nil
47728384160SKyle Evans		for k, v in pairs(menu.current_alias_table) do
4789f71d421SKyle Evans			if key == k then
479aedd6be5SKyle Evans				sel_entry = v
48028384160SKyle Evans				break
481088b4f5fSWarner Losh			end
482088b4f5fSWarner Losh		end
483088b4f5fSWarner Losh
484088b4f5fSWarner Losh		-- if we have an alias do the assigned action:
4859f71d421SKyle Evans		if sel_entry ~= nil then
486aedd6be5SKyle Evans			local handler = menu.handlers[sel_entry.entry_type]
4872a11b810SKyle Evans			assert(handler ~= nil)
488da9ab827SKyle Evans			-- The handler's return value indicates if we
489da9ab827SKyle Evans			-- need to exit this menu.  An omitted or true
490da9ab827SKyle Evans			-- return value means to continue.
4912a11b810SKyle Evans			if handler(menudef, sel_entry) == false then
492da9ab827SKyle Evans				return
493aefcaa7eSKyle Evans			end
49428384160SKyle Evans			-- If we got an alias key the screen is out of date...
49528384160SKyle Evans			-- redraw it.
4962a11b810SKyle Evans			menu.draw(menudef)
497088b4f5fSWarner Losh		end
498088b4f5fSWarner Losh	end
499088b4f5fSWarner Loshend
500088b4f5fSWarner Losh
501da9ab827SKyle Evansfunction menu.run()
502041929aaSKyle Evans	local autoboot_key
503c84dbc53SKyle Evans	local delay = loader.getenv("autoboot_delay")
504c84dbc53SKyle Evans
505c84dbc53SKyle Evans	if delay ~= nil and delay:lower() == "no" then
506c84dbc53SKyle Evans		delay = nil
507c84dbc53SKyle Evans	else
508c84dbc53SKyle Evans		delay = tonumber(delay) or 10
509c84dbc53SKyle Evans	end
510c84dbc53SKyle Evans
511c84dbc53SKyle Evans	if delay == -1 then
512c84dbc53SKyle Evans		core.boot()
513c84dbc53SKyle Evans		return
514c84dbc53SKyle Evans	end
515c84dbc53SKyle Evans
516beafe961SKyle Evans	menu.draw(menu.default)
517c84dbc53SKyle Evans
518041929aaSKyle Evans	if delay ~= nil then
519041929aaSKyle Evans		autoboot_key = menu.autoboot(delay)
520041929aaSKyle Evans
521041929aaSKyle Evans		-- autoboot_key should return the key pressed.  It will only
522041929aaSKyle Evans		-- return nil if we hit the timeout and executed the timeout
523041929aaSKyle Evans		-- command.  Bail out.
524041929aaSKyle Evans		if autoboot_key == nil then
525041929aaSKyle Evans			return
526041929aaSKyle Evans		end
527041929aaSKyle Evans	end
528ca16d83fSKyle Evans
529ca16d83fSKyle Evans	menu.process(menu.default, autoboot_key)
530beafe961SKyle Evans	drawn_menu = nil
531da9ab827SKyle Evans
532da9ab827SKyle Evans	screen.defcursor()
533*3fb656f8SWarner Losh	-- We explicitly want the newline print adds
534da9ab827SKyle Evans	print("Exiting menu!")
535088b4f5fSWarner Loshend
536088b4f5fSWarner Losh
537c84dbc53SKyle Evansfunction menu.autoboot(delay)
5381495c98fSKyle Evans	local x = loader.getenv("loader_menu_timeout_x") or 4
539101afbc6SWarner Losh	local y = loader.getenv("loader_menu_timeout_y") or 24
540c84dbc53SKyle Evans	local endtime = loader.time() + delay
541aedd6be5SKyle Evans	local time
5423244729fSKyle Evans	local last
543088b4f5fSWarner Losh	repeat
544aedd6be5SKyle Evans		time = endtime - loader.time()
5453244729fSKyle Evans		if last == nil or last ~= time then
5463244729fSKyle Evans			last = time
547aedd6be5SKyle Evans			screen.setcursor(x, y)
548*3fb656f8SWarner Losh			printc("Autoboot in " .. time ..
549ed107537SKyle Evans			    " seconds. [Space] to pause ")
550aedd6be5SKyle Evans			screen.defcursor()
5513244729fSKyle Evans		end
5529f71d421SKyle Evans		if io.ischar() then
553aedd6be5SKyle Evans			local ch = io.getchar()
5549f71d421SKyle Evans			if ch == core.KEY_ENTER then
555aedd6be5SKyle Evans				break
556088b4f5fSWarner Losh			else
557*3fb656f8SWarner Losh				-- Erase autoboot msg.  While real VT100s
558*3fb656f8SWarner Losh				-- wouldn't scroll when receiving a char with
559*3fb656f8SWarner Losh				-- the cursor at (24, 79), bad emulators do.
560*3fb656f8SWarner Losh				-- Avoid the issue by stopping at 79.
561aedd6be5SKyle Evans				screen.setcursor(0, y)
562*3fb656f8SWarner Losh				printc(string.rep(" ", 79))
563aedd6be5SKyle Evans				screen.defcursor()
56412b95c84SKyle Evans				return ch
565088b4f5fSWarner Losh			end
566088b4f5fSWarner Losh		end
567088b4f5fSWarner Losh
568aedd6be5SKyle Evans		loader.delay(50000)
569aedd6be5SKyle Evans	until time <= 0
570088b4f5fSWarner Losh
571a76f8a5bSKyle Evans	local cmd = loader.getenv("menu_timeout_command") or "boot"
572e9c3ceb1SKyle Evans	cli_execute_unparsed(cmd)
573041929aaSKyle Evans	return nil
574088b4f5fSWarner Loshend
575088b4f5fSWarner Losh
57676c75816SKyle Evans-- CLI commands
5778f7f3d08SKyle Evansfunction cli.menu()
57876c75816SKyle Evans	menu.run()
57976c75816SKyle Evansend
58076c75816SKyle Evans
581aedd6be5SKyle Evansreturn menu
582