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
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")
41 local screen_invalid = true
43 local function OnOff(str, b)
45 return str .. color.escapef(color.GREEN) .. "On" ..
46 color.escapef(color.WHITE)
48 return str .. color.escapef(color.RED) .. "off" ..
49 color.escapef(color.WHITE)
53 local function bootenvSet(env)
54 loader.setenv("vfs.root.mountfrom", env)
55 loader.setenv("currdev", env .. ":")
61 -- Menu handlers take the current menu and selected entry as parameters,
62 -- and should return a boolean indicating whether execution should
63 -- continue or not. The return value may be omitted if this entry should
64 -- have no bearing on whether we continue or not, indicating that we
65 -- should just continue after execution.
66 [core.MENU_ENTRY] = function(_, entry)
70 [core.MENU_CAROUSEL_ENTRY] = function(_, entry)
71 -- carousel (rotating) functionality
72 local carid = entry.carousel_id
73 local caridx = config.getCarouselIndex(carid)
74 local choices = entry.items
75 if type(choices) == "function" then
79 caridx = (caridx % #choices) + 1
80 config.setCarouselIndex(carid, caridx)
81 entry.func(caridx, choices[caridx], choices)
84 [core.MENU_SUBMENU] = function(_, entry)
86 menu.process(entry.submenu)
88 [core.MENU_RETURN] = function(_, entry)
89 -- allow entry to have a function/side effect
90 if entry.func ~= nil then
96 -- loader menu tree is rooted at menu.welcome
98 menu.boot_environments = {
100 -- return to welcome menu
102 entry_type = core.MENU_RETURN,
103 name = "Back to main menu" ..
104 color.highlight(" [Backspace]"),
107 entry_type = core.MENU_CAROUSEL_ENTRY,
108 carousel_id = "be_active",
109 items = core.bootenvList,
110 name = function(idx, choice, all_choices)
111 if #all_choices == 0 then
115 local is_default = (idx == 1)
116 local bootenv_name = ""
119 name_color = color.escapef(color.GREEN)
121 name_color = color.escapef(color.BLUE)
123 bootenv_name = bootenv_name .. name_color ..
124 choice .. color.default()
125 return color.highlight("A").."ctive: " ..
126 bootenv_name .. " (" .. idx .. " of " ..
129 func = function(_, choice, _)
135 entry_type = core.MENU_ENTRY,
137 return color.highlight("b") .. "ootfs: " ..
138 core.bootenvDefault()
141 -- Reset active boot environment to the default
142 config.setCarouselIndex("be_active", 1)
143 bootenvSet(core.bootenvDefault())
150 menu.boot_options = {
152 -- return to welcome menu
154 entry_type = core.MENU_RETURN,
155 name = "Back to main menu" ..
156 color.highlight(" [Backspace]"),
160 entry_type = core.MENU_ENTRY,
161 name = "Load System " .. color.highlight("D") ..
163 func = core.setDefaults,
167 entry_type = core.MENU_SEPARATOR,
170 entry_type = core.MENU_SEPARATOR,
171 name = "Boot Options:",
175 entry_type = core.MENU_ENTRY,
176 visible = core.isSystem386,
178 return OnOff(color.highlight("A") ..
186 entry_type = core.MENU_ENTRY,
188 return OnOff("Safe " .. color.highlight("M") ..
191 func = core.setSafeMode,
196 entry_type = core.MENU_ENTRY,
198 return OnOff(color.highlight("S") ..
199 "ingle user:", core.su)
201 func = core.setSingleUser,
206 entry_type = core.MENU_ENTRY,
208 return OnOff(color.highlight("V") ..
209 "erbose :", core.verbose)
211 func = core.setVerbose,
219 local menu_entries = menu.welcome.all_entries
220 -- Swap the first two menu items on single user boot
221 if core.isSingleUserBoot() then
222 -- We'll cache the swapped menu, for performance
223 if menu.welcome.swapped_menu ~= nil then
224 return menu.welcome.swapped_menu
226 -- Shallow copy the table
227 menu_entries = core.deepCopyTable(menu_entries)
229 -- Swap the first two menu entries
230 menu_entries[1], menu_entries[2] =
231 menu_entries[2], menu_entries[1]
233 -- Then set their names to their alternate names
234 menu_entries[1].name, menu_entries[2].name =
235 menu_entries[1].alternate_name,
236 menu_entries[2].alternate_name
237 menu.welcome.swapped_menu = menu_entries
244 entry_type = core.MENU_ENTRY,
245 name = color.highlight("B") .. "oot Multi user " ..
246 color.highlight("[Enter]"),
247 -- Not a standard menu entry function!
248 alternate_name = color.highlight("B") ..
251 core.setSingleUser(false)
258 entry_type = core.MENU_ENTRY,
259 name = "Boot " .. color.highlight("S") .. "ingle user",
260 -- Not a standard menu entry function!
261 alternate_name = "Boot " .. color.highlight("S") ..
262 "ingle user " .. color.highlight("[Enter]"),
264 core.setSingleUser(true)
269 -- escape to interpreter
271 entry_type = core.MENU_RETURN,
272 name = color.highlight("Esc") .. "ape to loader prompt",
274 loader.setenv("autoboot_delay", "NO")
276 alias = {core.KEYSTR_ESCAPE},
280 entry_type = core.MENU_ENTRY,
281 name = color.highlight("R") .. "eboot",
283 loader.perform("reboot")
288 entry_type = core.MENU_SEPARATOR,
291 entry_type = core.MENU_SEPARATOR,
296 entry_type = core.MENU_CAROUSEL_ENTRY,
297 carousel_id = "kernel",
298 items = core.kernelList,
299 name = function(idx, choice, all_choices)
300 if #all_choices == 0 then
304 local is_default = (idx == 1)
305 local kernel_name = ""
308 name_color = color.escapef(color.GREEN)
309 kernel_name = "default/"
311 name_color = color.escapef(color.BLUE)
313 kernel_name = kernel_name .. name_color ..
314 choice .. color.default()
315 return color.highlight("K") .. "ernel: " ..
316 kernel_name .. " (" .. idx .. " of " ..
319 func = function(_, choice, _)
320 config.selectKernel(choice)
326 entry_type = core.MENU_SUBMENU,
327 name = "Boot " .. color.highlight("O") .. "ptions",
328 submenu = menu.boot_options,
333 entry_type = core.MENU_SUBMENU,
335 return core.isZFSBoot() and
336 #core.bootenvList() > 1
338 name = "Boot " .. color.highlight("E") .. "nvironments",
339 submenu = menu.boot_environments,
345 menu.default = menu.welcome
346 -- current_alias_table will be used to keep our alias table consistent across
347 -- screen redraws, instead of relying on whatever triggered the redraw to update
348 -- the local alias_table in menu.process.
349 menu.current_alias_table = {}
351 function menu.redraw(m)
355 menu.current_alias_table = drawer.drawscreen(m)
356 screen_invalid = false
359 -- 'keypress' allows the caller to indicate that a key has been pressed that we
360 -- should process as our initial input.
361 function menu.process(m, keypress)
364 -- Trigger a redraw if we've been invalidated. Otherwise, we assume
365 -- that this menu has already been drawn.
366 if screen_invalid then
371 local key = keypress or io.getchar()
374 -- Special key behaviors
375 if (key == core.KEY_BACKSPACE or key == core.KEY_DELETE) and
376 m ~= menu.default then
378 elseif key == core.KEY_ENTER then
383 key = string.char(key)
384 -- check to see if key is an alias
385 local sel_entry = nil
386 for k, v in pairs(menu.current_alias_table) do
393 -- if we have an alias do the assigned action:
394 if sel_entry ~= nil then
396 local handler = menu.handlers[sel_entry.entry_type]
397 if handler ~= nil then
398 -- The handler's return value indicates if we
399 -- need to exit this menu. An omitted or true
400 -- return value means to continue.
401 if handler(m, sel_entry) == false then
405 -- If we got an alias key the screen is out of date...
410 -- Invalidate the screen upon exit so that it gets redrawn upon
411 -- processing a new menu, assuming it won't be redrawn after leaving
413 screen_invalid = false
422 menu.redraw(menu.default)
423 local autoboot_key = menu.autoboot()
425 menu.process(menu.default, autoboot_key)
428 print("Exiting menu!")
432 if core.isSerialBoot() then
435 local c = string.lower(loader.getenv("console") or "")
436 if c:match("^efi[ ;]") ~= nil or c:match("[ ;]efi[ ;]") ~= nil then
440 c = string.lower(loader.getenv("beastie_disable") or "")
441 print("beastie_disable", c)
445 function menu.autoboot()
446 local ab = loader.getenv("autoboot_delay")
447 if ab ~= nil and ab:lower() == "no" then
449 elseif tonumber(ab) == -1 then
452 ab = tonumber(ab) or 10
454 local x = loader.getenv("loader_menu_timeout_x") or 5
455 local y = loader.getenv("loader_menu_timeout_y") or 22
457 local endtime = loader.time() + ab
461 time = endtime - loader.time()
462 screen.setcursor(x, y)
463 print("Autoboot in " .. time ..
464 " seconds, hit [Enter] to boot" ..
465 " or any other key to stop ")
468 local ch = io.getchar()
469 if ch == core.KEY_ENTER then
472 -- erase autoboot msg
473 screen.setcursor(0, y)
474 print(string.rep(" ", 80))