2 -- SPDX-License-Identifier: BSD-2-Clause
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 .. ":")
61 if loader.getenv("kernelname") ~= nil then
62 loader.perform("unload")
66 local function multiUserPrompt()
67 return loader.getenv("loader_menu_multi_user_prompt") or "Multi user"
72 -- Menu handlers take the current menu and selected entry as parameters,
73 -- and should return a boolean indicating whether execution should
74 -- continue or not. The return value may be omitted if this entry should
75 -- have no bearing on whether we continue or not, indicating that we
76 -- should just continue after execution.
77 [core.MENU_ENTRY] = function(_, entry)
81 [core.MENU_CAROUSEL_ENTRY] = function(_, entry)
82 -- carousel (rotating) functionality
83 local carid = entry.carousel_id
84 local caridx = config.getCarouselIndex(carid)
85 local choices = entry.items
86 if type(choices) == "function" then
90 caridx = (caridx % #choices) + 1
91 config.setCarouselIndex(carid, caridx)
92 entry.func(caridx, choices[caridx], choices)
95 [core.MENU_SUBMENU] = function(_, entry)
96 menu.process(entry.submenu)
98 [core.MENU_RETURN] = function(_, entry)
99 -- allow entry to have a function/side effect
100 if entry.func ~= nil then
106 -- loader menu tree is rooted at menu.welcome
108 menu.boot_environments = {
110 -- return to welcome menu
113 entry_type = core.MENU_CAROUSEL_ENTRY,
114 carousel_id = "be_active",
115 items = core.bootenvList,
116 name = function(idx, choice, all_choices)
117 if #all_choices == 0 then
121 local is_default = (idx == 1)
122 local bootenv_name = ""
125 name_color = color.escapefg(color.GREEN)
127 name_color = color.escapefg(color.BLUE)
129 bootenv_name = bootenv_name .. name_color ..
130 choice .. color.resetfg()
131 return color.highlight("A").."ctive: " ..
132 bootenv_name .. " (" .. idx .. " of " ..
135 func = function(_, choice, _)
141 entry_type = core.MENU_ENTRY,
143 return core.isRewinded() == false
146 return color.highlight("b") .. "ootfs: " ..
147 core.bootenvDefault()
150 -- Reset active boot environment to the default
151 config.setCarouselIndex("be_active", 1)
152 bootenvSet(core.bootenvDefault())
159 menu.boot_options = {
161 -- return to welcome menu
165 entry_type = core.MENU_ENTRY,
166 name = "Load System " .. color.highlight("D") ..
168 func = core.setDefaults,
172 entry_type = core.MENU_SEPARATOR,
175 entry_type = core.MENU_SEPARATOR,
176 name = "Boot Options:",
180 entry_type = core.MENU_ENTRY,
181 visible = core.isSystem386,
183 return OnOff(color.highlight("A") ..
191 entry_type = core.MENU_ENTRY,
193 return OnOff("Safe " .. color.highlight("M") ..
196 func = core.setSafeMode,
201 entry_type = core.MENU_ENTRY,
203 return OnOff(color.highlight("S") ..
204 "ingle user:", core.su)
206 func = core.setSingleUser,
211 entry_type = core.MENU_ENTRY,
213 return OnOff(color.highlight("V") ..
214 "erbose :", core.verbose)
216 func = core.setVerbose,
224 local menu_entries = menu.welcome.all_entries
225 local multi_user = menu_entries.multi_user
226 local single_user = menu_entries.single_user
227 local boot_entry_1, boot_entry_2
228 if core.isSingleUserBoot() then
229 -- Swap the first two menu items on single user boot.
230 -- We'll cache the alternate entries for performance.
231 local alts = menu_entries.alts
233 single_user = core.deepCopyTable(single_user)
234 multi_user = core.deepCopyTable(multi_user)
235 single_user.name = single_user.alternate_name
236 multi_user.name = multi_user.alternate_name
237 menu_entries.alts = {
238 single_user = single_user,
239 multi_user = multi_user,
242 single_user = alts.single_user
243 multi_user = alts.multi_user
245 boot_entry_1, boot_entry_2 = single_user, multi_user
247 boot_entry_1, boot_entry_2 = multi_user, single_user
254 menu_entries.console,
256 entry_type = core.MENU_SEPARATOR,
259 entry_type = core.MENU_SEPARATOR,
262 menu_entries.kernel_options,
263 menu_entries.boot_options,
264 menu_entries.zpool_checkpoints,
265 menu_entries.boot_envs,
266 menu_entries.chainload,
272 entry_type = core.MENU_ENTRY,
274 return color.highlight("B") .. "oot " ..
275 multiUserPrompt() .. " " ..
276 color.highlight("[Enter]")
278 -- Not a standard menu entry function!
279 alternate_name = function()
280 return color.highlight("B") .. "oot " ..
284 core.setSingleUser(false)
290 entry_type = core.MENU_ENTRY,
291 name = "Boot " .. color.highlight("S") .. "ingle user",
292 -- Not a standard menu entry function!
293 alternate_name = "Boot " .. color.highlight("S") ..
294 "ingle user " .. color.highlight("[Enter]"),
296 core.setSingleUser(true)
302 entry_type = core.MENU_ENTRY,
304 return color.highlight("C") .. "ons: " .. core.getConsoleName()
307 core.nextConsoleChoice()
312 entry_type = core.MENU_RETURN,
313 name = color.highlight("Esc") .. "ape to loader prompt",
315 loader.setenv("autoboot_delay", "NO")
317 alias = {core.KEYSTR_ESCAPE},
320 entry_type = core.MENU_ENTRY,
321 name = color.highlight("R") .. "eboot",
323 loader.perform("reboot")
328 entry_type = core.MENU_CAROUSEL_ENTRY,
329 carousel_id = "kernel",
330 items = core.kernelList,
331 name = function(idx, choice, all_choices)
332 if #all_choices == 0 then
336 local is_default = (idx == 1)
337 local kernel_name = ""
340 name_color = color.escapefg(color.GREEN)
341 kernel_name = "default/"
343 name_color = color.escapefg(color.BLUE)
345 kernel_name = kernel_name .. name_color ..
346 choice .. color.resetfg()
347 return color.highlight("K") .. "ernel: " ..
348 kernel_name .. " (" .. idx .. " of " ..
351 func = function(_, choice, _)
352 if loader.getenv("kernelname") ~= nil then
353 loader.perform("unload")
355 config.selectKernel(choice)
360 entry_type = core.MENU_SUBMENU,
361 name = "Boot " .. color.highlight("O") .. "ptions",
362 submenu = menu.boot_options,
365 zpool_checkpoints = {
366 entry_type = core.MENU_ENTRY,
369 if core.isRewinded() then
372 return "Rewind ZFS " .. color.highlight("C") ..
373 "heckpoint: " .. rewind
376 core.changeRewindCheckpoint()
377 if core.isRewinded() then
379 core.bootenvDefaultRewinded())
381 bootenvSet(core.bootenvDefault())
383 config.setCarouselIndex("be_active", 1)
386 return core.isZFSBoot() and
387 core.isCheckpointed()
392 entry_type = core.MENU_SUBMENU,
394 return core.isZFSBoot() and
395 #core.bootenvList() > 1
397 name = "Boot " .. color.highlight("E") .. "nvironments",
398 submenu = menu.boot_environments,
402 entry_type = core.MENU_ENTRY,
404 return 'Chain' .. color.highlight("L") ..
405 "oad " .. loader.getenv('chain_disk')
408 loader.perform("chain " ..
409 loader.getenv('chain_disk'))
412 return loader.getenv('chain_disk') ~= nil
417 entry_type = core.MENU_ENTRY,
425 menu.default = menu.welcome
426 -- current_alias_table will be used to keep our alias table consistent across
427 -- screen redraws, instead of relying on whatever triggered the redraw to update
428 -- the local alias_table in menu.process.
429 menu.current_alias_table = {}
431 function menu.draw(menudef)
432 -- Clear the screen, reset the cursor, then draw
434 menu.current_alias_table = drawer.drawscreen(menudef)
439 -- 'keypress' allows the caller to indicate that a key has been pressed that we
440 -- should process as our initial input.
441 function menu.process(menudef, keypress)
442 assert(menudef ~= nil)
444 if drawn_menu ~= menudef then
449 local key = keypress or io.getchar()
452 -- Special key behaviors
453 if (key == core.KEY_BACKSPACE or key == core.KEY_DELETE) and
454 menudef ~= menu.default then
456 elseif key == core.KEY_ENTER then
458 -- Should not return. If it does, escape menu handling
459 -- and drop to loader prompt.
463 key = string.char(key)
464 -- check to see if key is an alias
465 local sel_entry = nil
466 for k, v in pairs(menu.current_alias_table) do
473 -- if we have an alias do the assigned action:
474 if sel_entry ~= nil then
475 local handler = menu.handlers[sel_entry.entry_type]
476 assert(handler ~= nil)
477 -- The handler's return value indicates if we
478 -- need to exit this menu. An omitted or true
479 -- return value means to continue.
480 if handler(menudef, sel_entry) == false then
483 -- If we got an alias key the screen is out of date...
492 local delay = loader.getenv("autoboot_delay")
494 if delay ~= nil and delay:lower() == "no" then
497 delay = tonumber(delay) or 10
505 menu.draw(menu.default)
508 autoboot_key = menu.autoboot(delay)
510 -- autoboot_key should return the key pressed. It will only
511 -- return nil if we hit the timeout and executed the timeout
512 -- command. Bail out.
513 if autoboot_key == nil then
518 menu.process(menu.default, autoboot_key)
522 print("Exiting menu!")
525 function menu.autoboot(delay)
526 local x = loader.getenv("loader_menu_timeout_x") or 4
527 local y = loader.getenv("loader_menu_timeout_y") or 23
528 local endtime = loader.time() + delay
532 time = endtime - loader.time()
533 if last == nil or last ~= time then
535 screen.setcursor(x, y)
536 print("Autoboot in " .. time ..
537 " seconds. [Space] to pause ")
541 local ch = io.getchar()
542 if ch == core.KEY_ENTER then
545 -- erase autoboot msg
546 screen.setcursor(0, y)
547 print(string.rep(" ", 80))
556 local cmd = loader.getenv("menu_timeout_command") or "boot"
557 cli_execute_unparsed(cmd)