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 .. ":")
63 local function multiUserPrompt()
64 return loader.getenv("loader_menu_multi_user_prompt") or "Multi user"
69 -- Menu handlers take the current menu and selected entry as parameters,
70 -- and should return a boolean indicating whether execution should
71 -- continue or not. The return value may be omitted if this entry should
72 -- have no bearing on whether we continue or not, indicating that we
73 -- should just continue after execution.
74 [core.MENU_ENTRY] = function(_, entry)
78 [core.MENU_CAROUSEL_ENTRY] = function(_, entry)
79 -- carousel (rotating) functionality
80 local carid = entry.carousel_id
81 local caridx = config.getCarouselIndex(carid)
82 local choices = entry.items
83 if type(choices) == "function" then
87 caridx = (caridx % #choices) + 1
88 config.setCarouselIndex(carid, caridx)
89 entry.func(caridx, choices[caridx], choices)
92 [core.MENU_SUBMENU] = function(_, entry)
93 menu.process(entry.submenu)
95 [core.MENU_RETURN] = function(_, entry)
96 -- allow entry to have a function/side effect
97 if entry.func ~= nil then
103 -- loader menu tree is rooted at menu.welcome
105 menu.boot_environments = {
107 -- return to welcome menu
110 entry_type = core.MENU_CAROUSEL_ENTRY,
111 carousel_id = "be_active",
112 items = core.bootenvList,
113 name = function(idx, choice, all_choices)
114 if #all_choices == 0 then
118 local is_default = (idx == 1)
119 local bootenv_name = ""
122 name_color = color.escapefg(color.GREEN)
124 name_color = color.escapefg(color.BLUE)
126 bootenv_name = bootenv_name .. name_color ..
127 choice .. color.resetfg()
128 return color.highlight("A").."ctive: " ..
129 bootenv_name .. " (" .. idx .. " of " ..
132 func = function(_, choice, _)
138 entry_type = core.MENU_ENTRY,
140 return core.isRewinded() == false
143 return color.highlight("b") .. "ootfs: " ..
144 core.bootenvDefault()
147 -- Reset active boot environment to the default
148 config.setCarouselIndex("be_active", 1)
149 bootenvSet(core.bootenvDefault())
156 menu.boot_options = {
158 -- return to welcome menu
162 entry_type = core.MENU_ENTRY,
163 name = "Load System " .. color.highlight("D") ..
165 func = core.setDefaults,
169 entry_type = core.MENU_SEPARATOR,
172 entry_type = core.MENU_SEPARATOR,
173 name = "Boot Options:",
177 entry_type = core.MENU_ENTRY,
178 visible = core.isSystem386,
180 return OnOff(color.highlight("A") ..
188 entry_type = core.MENU_ENTRY,
190 return OnOff("Safe " .. color.highlight("M") ..
193 func = core.setSafeMode,
198 entry_type = core.MENU_ENTRY,
200 return OnOff(color.highlight("S") ..
201 "ingle user:", core.su)
203 func = core.setSingleUser,
208 entry_type = core.MENU_ENTRY,
210 return OnOff(color.highlight("V") ..
211 "erbose :", core.verbose)
213 func = core.setVerbose,
221 local menu_entries = menu.welcome.all_entries
222 local multi_user = menu_entries.multi_user
223 local single_user = menu_entries.single_user
224 local boot_entry_1, boot_entry_2
225 if core.isSingleUserBoot() then
226 -- Swap the first two menu items on single user boot.
227 -- We'll cache the alternate entries for performance.
228 local alts = menu_entries.alts
230 single_user = core.deepCopyTable(single_user)
231 multi_user = core.deepCopyTable(multi_user)
232 single_user.name = single_user.alternate_name
233 multi_user.name = multi_user.alternate_name
234 menu_entries.alts = {
235 single_user = single_user,
236 multi_user = multi_user,
239 single_user = alts.single_user
240 multi_user = alts.multi_user
242 boot_entry_1, boot_entry_2 = single_user, multi_user
244 boot_entry_1, boot_entry_2 = multi_user, single_user
251 menu_entries.console,
253 entry_type = core.MENU_SEPARATOR,
256 entry_type = core.MENU_SEPARATOR,
259 menu_entries.kernel_options,
260 menu_entries.boot_options,
261 menu_entries.zpool_checkpoints,
262 menu_entries.boot_envs,
263 menu_entries.chainload,
269 entry_type = core.MENU_ENTRY,
271 return color.highlight("B") .. "oot " ..
272 multiUserPrompt() .. " " ..
273 color.highlight("[Enter]")
275 -- Not a standard menu entry function!
276 alternate_name = function()
277 return color.highlight("B") .. "oot " ..
281 core.setSingleUser(false)
287 entry_type = core.MENU_ENTRY,
288 name = "Boot " .. color.highlight("S") .. "ingle user",
289 -- Not a standard menu entry function!
290 alternate_name = "Boot " .. color.highlight("S") ..
291 "ingle user " .. color.highlight("[Enter]"),
293 core.setSingleUser(true)
299 entry_type = core.MENU_ENTRY,
301 return color.highlight("C") .. "ons: " .. core.getConsoleName()
304 core.nextConsoleChoice()
309 entry_type = core.MENU_RETURN,
310 name = color.highlight("Esc") .. "ape to loader prompt",
312 loader.setenv("autoboot_delay", "NO")
314 alias = {core.KEYSTR_ESCAPE},
317 entry_type = core.MENU_ENTRY,
318 name = color.highlight("R") .. "eboot",
320 loader.perform("reboot")
325 entry_type = core.MENU_CAROUSEL_ENTRY,
326 carousel_id = "kernel",
327 items = core.kernelList,
328 name = function(idx, choice, all_choices)
329 if #all_choices == 0 then
333 local is_default = (idx == 1)
334 local kernel_name = ""
337 name_color = color.escapefg(color.GREEN)
338 kernel_name = "default/"
340 name_color = color.escapefg(color.BLUE)
342 kernel_name = kernel_name .. name_color ..
343 choice .. color.resetfg()
344 return color.highlight("K") .. "ernel: " ..
345 kernel_name .. " (" .. idx .. " of " ..
348 func = function(_, choice, _)
349 if loader.getenv("kernelname") ~= nil then
350 loader.perform("unload")
352 config.selectKernel(choice)
357 entry_type = core.MENU_SUBMENU,
358 name = "Boot " .. color.highlight("O") .. "ptions",
359 submenu = menu.boot_options,
362 zpool_checkpoints = {
363 entry_type = core.MENU_ENTRY,
366 if core.isRewinded() then
369 return "Rewind ZFS " .. color.highlight("C") ..
370 "heckpoint: " .. rewind
373 core.changeRewindCheckpoint()
374 if core.isRewinded() then
376 core.bootenvDefaultRewinded())
378 bootenvSet(core.bootenvDefault())
380 config.setCarouselIndex("be_active", 1)
383 return core.isZFSBoot() and
384 core.isCheckpointed()
389 entry_type = core.MENU_SUBMENU,
391 return core.isZFSBoot() and
392 #core.bootenvList() > 1
394 name = "Boot " .. color.highlight("E") .. "nvironments",
395 submenu = menu.boot_environments,
399 entry_type = core.MENU_ENTRY,
401 return 'Chain' .. color.highlight("L") ..
402 "oad " .. loader.getenv('chain_disk')
405 loader.perform("chain " ..
406 loader.getenv('chain_disk'))
409 return loader.getenv('chain_disk') ~= nil
414 entry_type = core.MENU_ENTRY,
422 menu.default = menu.welcome
423 -- current_alias_table will be used to keep our alias table consistent across
424 -- screen redraws, instead of relying on whatever triggered the redraw to update
425 -- the local alias_table in menu.process.
426 menu.current_alias_table = {}
428 function menu.draw(menudef)
429 -- Clear the screen, reset the cursor, then draw
431 menu.current_alias_table = drawer.drawscreen(menudef)
436 -- 'keypress' allows the caller to indicate that a key has been pressed that we
437 -- should process as our initial input.
438 function menu.process(menudef, keypress)
439 assert(menudef ~= nil)
441 if drawn_menu ~= menudef then
446 local key = keypress or io.getchar()
449 -- Special key behaviors
450 if (key == core.KEY_BACKSPACE or key == core.KEY_DELETE) and
451 menudef ~= menu.default then
453 elseif key == core.KEY_ENTER then
455 -- Should not return. If it does, escape menu handling
456 -- and drop to loader prompt.
460 key = string.char(key)
461 -- check to see if key is an alias
462 local sel_entry = nil
463 for k, v in pairs(menu.current_alias_table) do
470 -- if we have an alias do the assigned action:
471 if sel_entry ~= nil then
472 local handler = menu.handlers[sel_entry.entry_type]
473 assert(handler ~= nil)
474 -- The handler's return value indicates if we
475 -- need to exit this menu. An omitted or true
476 -- return value means to continue.
477 if handler(menudef, sel_entry) == false then
480 -- If we got an alias key the screen is out of date...
489 local delay = loader.getenv("autoboot_delay")
491 if delay ~= nil and delay:lower() == "no" then
494 delay = tonumber(delay) or 10
502 menu.draw(menu.default)
505 autoboot_key = menu.autoboot(delay)
507 -- autoboot_key should return the key pressed. It will only
508 -- return nil if we hit the timeout and executed the timeout
509 -- command. Bail out.
510 if autoboot_key == nil then
515 menu.process(menu.default, autoboot_key)
519 print("Exiting menu!")
522 function menu.autoboot(delay)
523 local x = loader.getenv("loader_menu_timeout_x") or 4
524 local y = loader.getenv("loader_menu_timeout_y") or 23
525 local endtime = loader.time() + delay
529 time = endtime - loader.time()
530 if last == nil or last ~= time then
532 screen.setcursor(x, y)
533 print("Autoboot in " .. time ..
534 " seconds. [Space] to pause ")
538 local ch = io.getchar()
539 if ch == core.KEY_ENTER then
542 -- erase autoboot msg
543 screen.setcursor(0, y)
544 print(string.rep(" ", 80))
553 local cmd = loader.getenv("menu_timeout_command") or "boot"
554 cli_execute_unparsed(cmd)