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 core.isRewinded() == false
139 return color.highlight("b") .. "ootfs: " ..
140 core.bootenvDefault()
143 -- Reset active boot environment to the default
144 config.setCarouselIndex("be_active", 1)
145 bootenvSet(core.bootenvDefault())
152 menu.boot_options = {
154 -- return to welcome menu
158 entry_type = core.MENU_ENTRY,
159 name = "Load System " .. color.highlight("D") ..
161 func = core.setDefaults,
165 entry_type = core.MENU_SEPARATOR,
168 entry_type = core.MENU_SEPARATOR,
169 name = "Boot Options:",
173 entry_type = core.MENU_ENTRY,
174 visible = core.isSystem386,
176 return OnOff(color.highlight("A") ..
184 entry_type = core.MENU_ENTRY,
186 return OnOff("Safe " .. color.highlight("M") ..
189 func = core.setSafeMode,
194 entry_type = core.MENU_ENTRY,
196 return OnOff(color.highlight("S") ..
197 "ingle user:", core.su)
199 func = core.setSingleUser,
204 entry_type = core.MENU_ENTRY,
206 return OnOff(color.highlight("V") ..
207 "erbose :", core.verbose)
209 func = core.setVerbose,
217 local menu_entries = menu.welcome.all_entries
218 local multi_user = menu_entries.multi_user
219 local single_user = menu_entries.single_user
220 local boot_entry_1, boot_entry_2
221 if core.isSingleUserBoot() then
222 -- Swap the first two menu items on single user boot.
223 -- We'll cache the alternate entries for performance.
224 local alts = menu_entries.alts
226 single_user = core.deepCopyTable(single_user)
227 multi_user = core.deepCopyTable(multi_user)
228 single_user.name = single_user.alternate_name
229 multi_user.name = multi_user.alternate_name
230 menu_entries.alts = {
231 single_user = single_user,
232 multi_user = multi_user,
235 single_user = alts.single_user
236 multi_user = alts.multi_user
238 boot_entry_1, boot_entry_2 = single_user, multi_user
240 boot_entry_1, boot_entry_2 = multi_user, single_user
247 menu_entries.console,
249 entry_type = core.MENU_SEPARATOR,
252 entry_type = core.MENU_SEPARATOR,
255 menu_entries.kernel_options,
256 menu_entries.boot_options,
257 menu_entries.zpool_checkpoints,
258 menu_entries.boot_envs,
259 menu_entries.chainload,
265 entry_type = core.MENU_ENTRY,
266 name = color.highlight("B") .. "oot Multi user " ..
267 color.highlight("[Enter]"),
268 -- Not a standard menu entry function!
269 alternate_name = color.highlight("B") ..
272 core.setSingleUser(false)
278 entry_type = core.MENU_ENTRY,
279 name = "Boot " .. color.highlight("S") .. "ingle user",
280 -- Not a standard menu entry function!
281 alternate_name = "Boot " .. color.highlight("S") ..
282 "ingle user " .. color.highlight("[Enter]"),
284 core.setSingleUser(true)
290 entry_type = core.MENU_ENTRY,
292 return color.highlight("C") .. "ons: " .. core.getConsoleName()
295 core.nextConsoleChoice()
300 entry_type = core.MENU_RETURN,
301 name = color.highlight("Esc") .. "ape to loader prompt",
303 loader.setenv("autoboot_delay", "NO")
305 alias = {core.KEYSTR_ESCAPE},
308 entry_type = core.MENU_ENTRY,
309 name = color.highlight("R") .. "eboot",
311 loader.perform("reboot")
316 entry_type = core.MENU_CAROUSEL_ENTRY,
317 carousel_id = "kernel",
318 items = core.kernelList,
319 name = function(idx, choice, all_choices)
320 if #all_choices == 0 then
324 local is_default = (idx == 1)
325 local kernel_name = ""
328 name_color = color.escapefg(color.GREEN)
329 kernel_name = "default/"
331 name_color = color.escapefg(color.BLUE)
333 kernel_name = kernel_name .. name_color ..
334 choice .. color.resetfg()
335 return color.highlight("K") .. "ernel: " ..
336 kernel_name .. " (" .. idx .. " of " ..
339 func = function(_, choice, _)
340 if loader.getenv("kernelname") ~= nil then
341 loader.perform("unload")
343 config.selectKernel(choice)
348 entry_type = core.MENU_SUBMENU,
349 name = "Boot " .. color.highlight("O") .. "ptions",
350 submenu = menu.boot_options,
353 zpool_checkpoints = {
354 entry_type = core.MENU_ENTRY,
357 if core.isRewinded() then
360 return "Rewind ZFS " .. color.highlight("C") ..
361 "heckpoint: " .. rewind
364 core.changeRewindCheckpoint()
365 if core.isRewinded() then
367 core.bootenvDefaultRewinded())
369 bootenvSet(core.bootenvDefault())
371 config.setCarouselIndex("be_active", 1)
374 return core.isZFSBoot() and
375 core.isCheckpointed()
380 entry_type = core.MENU_SUBMENU,
382 return core.isZFSBoot() and
383 #core.bootenvList() > 1
385 name = "Boot " .. color.highlight("E") .. "nvironments",
386 submenu = menu.boot_environments,
390 entry_type = core.MENU_ENTRY,
392 return 'Chain' .. color.highlight("L") ..
393 "oad " .. loader.getenv('chain_disk')
396 loader.perform("chain " ..
397 loader.getenv('chain_disk'))
400 return loader.getenv('chain_disk') ~= nil
405 entry_type = core.MENU_ENTRY,
413 menu.default = menu.welcome
414 -- current_alias_table will be used to keep our alias table consistent across
415 -- screen redraws, instead of relying on whatever triggered the redraw to update
416 -- the local alias_table in menu.process.
417 menu.current_alias_table = {}
419 function menu.draw(menudef)
420 -- Clear the screen, reset the cursor, then draw
422 menu.current_alias_table = drawer.drawscreen(menudef)
427 -- 'keypress' allows the caller to indicate that a key has been pressed that we
428 -- should process as our initial input.
429 function menu.process(menudef, keypress)
430 assert(menudef ~= nil)
432 if drawn_menu ~= menudef then
437 local key = keypress or io.getchar()
440 -- Special key behaviors
441 if (key == core.KEY_BACKSPACE or key == core.KEY_DELETE) and
442 menudef ~= menu.default then
444 elseif key == core.KEY_ENTER then
446 -- Should not return. If it does, escape menu handling
447 -- and drop to loader prompt.
451 key = string.char(key)
452 -- check to see if key is an alias
453 local sel_entry = nil
454 for k, v in pairs(menu.current_alias_table) do
461 -- if we have an alias do the assigned action:
462 if sel_entry ~= nil then
463 local handler = menu.handlers[sel_entry.entry_type]
464 assert(handler ~= nil)
465 -- The handler's return value indicates if we
466 -- need to exit this menu. An omitted or true
467 -- return value means to continue.
468 if handler(menudef, sel_entry) == false then
471 -- If we got an alias key the screen is out of date...
480 local delay = loader.getenv("autoboot_delay")
482 if delay ~= nil and delay:lower() == "no" then
485 delay = tonumber(delay) or 10
493 menu.draw(menu.default)
496 autoboot_key = menu.autoboot(delay)
498 -- autoboot_key should return the key pressed. It will only
499 -- return nil if we hit the timeout and executed the timeout
500 -- command. Bail out.
501 if autoboot_key == nil then
506 menu.process(menu.default, autoboot_key)
510 print("Exiting menu!")
513 function menu.autoboot(delay)
514 local x = loader.getenv("loader_menu_timeout_x") or 4
515 local y = loader.getenv("loader_menu_timeout_y") or 23
516 local endtime = loader.time() + delay
520 time = endtime - loader.time()
521 if last == nil or last ~= time then
523 screen.setcursor(x, y)
524 print("Autoboot in " .. time ..
525 " seconds, hit [Enter] to boot" ..
526 " or any other key to stop ")
530 local ch = io.getchar()
531 if ch == core.KEY_ENTER then
534 -- erase autoboot msg
535 screen.setcursor(0, y)
536 print(string.rep(" ", 80))
545 local cmd = loader.getenv("menu_timeout_command") or "boot"
546 cli_execute_unparsed(cmd)