2 -- SPDX-License-Identifier: BSD-2-Clause-FreeBSD
4 -- Copyright (c) 2015 Pedro Souza <pedrosouza@freebsd.org>
5 -- Copyright (C) 2018 Kyle Evans <kevans@FreeBSD.org>
6 -- All rights reserved.
8 -- Redistribution and use in source and binary forms, with or without
9 -- modification, are permitted provided that the following conditions
11 -- 1. Redistributions of source code must retain the above copyright
12 -- notice, this list of conditions and the following disclaimer.
13 -- 2. Redistributions in binary form must reproduce the above copyright
14 -- notice, this list of conditions and the following disclaimer in the
15 -- documentation and/or other materials provided with the distribution.
17 -- THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 -- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 -- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 -- ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 -- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 -- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 -- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 -- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 -- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 -- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 local cli = require("cli")
33 local core = require("core")
34 local color = require("color")
35 local config = require("config")
36 local screen = require("screen")
37 local drawer = require("drawer")
42 local return_menu_entry = {
43 entry_type = core.MENU_RETURN,
44 name = "Back to main menu" .. color.highlight(" [Backspace]"),
47 local function OnOff(str, value)
49 return str .. color.escapefg(color.GREEN) .. "On" ..
52 return str .. color.escapefg(color.RED) .. "off" ..
57 local function bootenvSet(env)
58 loader.setenv("vfs.root.mountfrom", env)
59 loader.setenv("currdev", env .. ":")
65 -- Menu handlers take the current menu and selected entry as parameters,
66 -- and should return a boolean indicating whether execution should
67 -- continue or not. The return value may be omitted if this entry should
68 -- have no bearing on whether we continue or not, indicating that we
69 -- should just continue after execution.
70 [core.MENU_ENTRY] = function(_, entry)
74 [core.MENU_CAROUSEL_ENTRY] = function(_, entry)
75 -- carousel (rotating) functionality
76 local carid = entry.carousel_id
77 local caridx = config.getCarouselIndex(carid)
78 local choices = entry.items
79 if type(choices) == "function" then
83 caridx = (caridx % #choices) + 1
84 config.setCarouselIndex(carid, caridx)
85 entry.func(caridx, choices[caridx], choices)
88 [core.MENU_SUBMENU] = function(_, entry)
89 menu.process(entry.submenu)
91 [core.MENU_RETURN] = function(_, entry)
92 -- allow entry to have a function/side effect
93 if entry.func ~= nil then
99 -- loader menu tree is rooted at menu.welcome
101 menu.boot_environments = {
103 -- return to welcome menu
106 entry_type = core.MENU_CAROUSEL_ENTRY,
107 carousel_id = "be_active",
108 items = core.bootenvList,
109 name = function(idx, choice, all_choices)
110 if #all_choices == 0 then
114 local is_default = (idx == 1)
115 local bootenv_name = ""
118 name_color = color.escapefg(color.GREEN)
120 name_color = color.escapefg(color.BLUE)
122 bootenv_name = bootenv_name .. name_color ..
123 choice .. color.resetfg()
124 return color.highlight("A").."ctive: " ..
125 bootenv_name .. " (" .. idx .. " of " ..
128 func = function(_, choice, _)
134 entry_type = core.MENU_ENTRY,
136 return color.highlight("b") .. "ootfs: " ..
137 core.bootenvDefault()
140 -- Reset active boot environment to the default
141 config.setCarouselIndex("be_active", 1)
142 bootenvSet(core.bootenvDefault())
149 menu.boot_options = {
151 -- return to welcome menu
155 entry_type = core.MENU_ENTRY,
156 name = "Load System " .. color.highlight("D") ..
158 func = core.setDefaults,
162 entry_type = core.MENU_SEPARATOR,
165 entry_type = core.MENU_SEPARATOR,
166 name = "Boot Options:",
170 entry_type = core.MENU_ENTRY,
171 visible = core.isSystem386,
173 return OnOff(color.highlight("A") ..
181 entry_type = core.MENU_ENTRY,
183 return OnOff("Safe " .. color.highlight("M") ..
186 func = core.setSafeMode,
191 entry_type = core.MENU_ENTRY,
193 return OnOff(color.highlight("S") ..
194 "ingle user:", core.su)
196 func = core.setSingleUser,
201 entry_type = core.MENU_ENTRY,
203 return OnOff(color.highlight("V") ..
204 "erbose :", core.verbose)
206 func = core.setVerbose,
214 local menu_entries = menu.welcome.all_entries
215 local multi_user = menu_entries.multi_user
216 local single_user = menu_entries.single_user
217 local boot_entry_1, boot_entry_2
218 if core.isSingleUserBoot() then
219 -- Swap the first two menu items on single user boot.
220 -- We'll cache the alternate entries for performance.
221 local alts = menu_entries.alts
223 single_user = core.deepCopyTable(single_user)
224 multi_user = core.deepCopyTable(multi_user)
225 single_user.name = single_user.alternate_name
226 multi_user.name = multi_user.alternate_name
227 menu_entries.alts = {
228 single_user = single_user,
229 multi_user = multi_user,
232 single_user = alts.single_user
233 multi_user = alts.multi_user
235 boot_entry_1, boot_entry_2 = single_user, multi_user
237 boot_entry_1, boot_entry_2 = multi_user, single_user
244 menu_entries.console,
246 entry_type = core.MENU_SEPARATOR,
249 entry_type = core.MENU_SEPARATOR,
252 menu_entries.kernel_options,
253 menu_entries.boot_options,
254 menu_entries.boot_envs,
255 menu_entries.chainload,
260 entry_type = core.MENU_ENTRY,
261 name = color.highlight("B") .. "oot Multi user " ..
262 color.highlight("[Enter]"),
263 -- Not a standard menu entry function!
264 alternate_name = color.highlight("B") ..
267 core.setSingleUser(false)
273 entry_type = core.MENU_ENTRY,
274 name = "Boot " .. color.highlight("S") .. "ingle user",
275 -- Not a standard menu entry function!
276 alternate_name = "Boot " .. color.highlight("S") ..
277 "ingle user " .. color.highlight("[Enter]"),
279 core.setSingleUser(true)
285 entry_type = core.MENU_ENTRY,
287 return color.highlight("C") .. "ons: " .. core.getConsoleName()
290 core.nextConsoleChoice()
295 entry_type = core.MENU_RETURN,
296 name = color.highlight("Esc") .. "ape to loader prompt",
298 loader.setenv("autoboot_delay", "NO")
300 alias = {core.KEYSTR_ESCAPE},
303 entry_type = core.MENU_ENTRY,
304 name = color.highlight("R") .. "eboot",
306 loader.perform("reboot")
311 entry_type = core.MENU_CAROUSEL_ENTRY,
312 carousel_id = "kernel",
313 items = core.kernelList,
314 name = function(idx, choice, all_choices)
315 if #all_choices == 0 then
319 local is_default = (idx == 1)
320 local kernel_name = ""
323 name_color = color.escapefg(color.GREEN)
324 kernel_name = "default/"
326 name_color = color.escapefg(color.BLUE)
328 kernel_name = kernel_name .. name_color ..
329 choice .. color.resetfg()
330 return color.highlight("K") .. "ernel: " ..
331 kernel_name .. " (" .. idx .. " of " ..
334 func = function(_, choice, _)
335 if loader.getenv("kernelname") ~= nil then
336 loader.perform("unload")
338 config.selectKernel(choice)
343 entry_type = core.MENU_SUBMENU,
344 name = "Boot " .. color.highlight("O") .. "ptions",
345 submenu = menu.boot_options,
349 entry_type = core.MENU_SUBMENU,
351 return core.isZFSBoot() and
352 #core.bootenvList() > 1
354 name = "Boot " .. color.highlight("E") .. "nvironments",
355 submenu = menu.boot_environments,
359 entry_type = core.MENU_ENTRY,
361 return 'Chain' .. color.highlight("L") ..
362 "oad " .. loader.getenv('chain_disk')
365 loader.perform("chain " ..
366 loader.getenv('chain_disk'))
369 return loader.getenv('chain_disk') ~= nil
376 menu.default = menu.welcome
377 -- current_alias_table will be used to keep our alias table consistent across
378 -- screen redraws, instead of relying on whatever triggered the redraw to update
379 -- the local alias_table in menu.process.
380 menu.current_alias_table = {}
382 function menu.draw(menudef)
383 -- Clear the screen, reset the cursor, then draw
385 menu.current_alias_table = drawer.drawscreen(menudef)
390 -- 'keypress' allows the caller to indicate that a key has been pressed that we
391 -- should process as our initial input.
392 function menu.process(menudef, keypress)
393 assert(menudef ~= nil)
395 if drawn_menu ~= menudef then
400 local key = keypress or io.getchar()
403 -- Special key behaviors
404 if (key == core.KEY_BACKSPACE or key == core.KEY_DELETE) and
405 menudef ~= menu.default then
407 elseif key == core.KEY_ENTER then
409 -- Should not return. If it does, escape menu handling
410 -- and drop to loader prompt.
414 key = string.char(key)
415 -- check to see if key is an alias
416 local sel_entry = nil
417 for k, v in pairs(menu.current_alias_table) do
424 -- if we have an alias do the assigned action:
425 if sel_entry ~= nil then
426 local handler = menu.handlers[sel_entry.entry_type]
427 assert(handler ~= nil)
428 -- The handler's return value indicates if we
429 -- need to exit this menu. An omitted or true
430 -- return value means to continue.
431 if handler(menudef, sel_entry) == false then
434 -- If we got an alias key the screen is out of date...
443 local delay = loader.getenv("autoboot_delay")
445 if delay ~= nil and delay:lower() == "no" then
448 delay = tonumber(delay) or 10
456 menu.draw(menu.default)
459 autoboot_key = menu.autoboot(delay)
461 -- autoboot_key should return the key pressed. It will only
462 -- return nil if we hit the timeout and executed the timeout
463 -- command. Bail out.
464 if autoboot_key == nil then
469 menu.process(menu.default, autoboot_key)
473 print("Exiting menu!")
476 function menu.autoboot(delay)
477 local x = loader.getenv("loader_menu_timeout_x") or 4
478 local y = loader.getenv("loader_menu_timeout_y") or 23
479 local endtime = loader.time() + delay
483 time = endtime - loader.time()
484 if last == nil or last ~= time then
486 screen.setcursor(x, y)
487 print("Autoboot in " .. time ..
488 " seconds. [Space] to pause")
492 local ch = io.getchar()
493 if ch == core.KEY_ENTER then
496 -- erase autoboot msg
497 screen.setcursor(0, y)
498 print(string.rep(" ", 80))
507 local cmd = loader.getenv("menu_timeout_command") or "boot"
508 cli_execute_unparsed(cmd)