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 function OnOff(str, b)
43 return str .. color.escapef(color.GREEN) .. "On" ..
44 color.escapef(color.WHITE)
46 return str .. color.escapef(color.RED) .. "off" ..
47 color.escapef(color.WHITE)
51 local function bootenvSet(env)
52 loader.setenv("vfs.root.mountfrom", env)
53 loader.setenv("currdev", env .. ":")
59 -- Menu handlers take the current menu and selected entry as parameters,
60 -- and should return a boolean indicating whether execution should
61 -- continue or not. The return value may be omitted if this entry should
62 -- have no bearing on whether we continue or not, indicating that we
63 -- should just continue after execution.
64 [core.MENU_ENTRY] = function(_, entry)
68 [core.MENU_CAROUSEL_ENTRY] = function(_, entry)
69 -- carousel (rotating) functionality
70 local carid = entry.carousel_id
71 local caridx = config.getCarouselIndex(carid)
72 local choices = entry.items
73 if type(choices) == "function" then
77 caridx = (caridx % #choices) + 1
78 config.setCarouselIndex(carid, caridx)
79 entry.func(caridx, choices[caridx], choices)
82 [core.MENU_SUBMENU] = function(_, entry)
84 return menu.run(entry.submenu)
86 [core.MENU_RETURN] = function(_, entry)
87 -- allow entry to have a function/side effect
88 if entry.func ~= nil then
94 -- loader menu tree is rooted at menu.welcome
96 menu.boot_environments = {
98 -- return to welcome menu
100 entry_type = core.MENU_RETURN,
101 name = "Back to main menu" ..
102 color.highlight(" [Backspace]"),
105 entry_type = core.MENU_CAROUSEL_ENTRY,
106 carousel_id = "be_active",
107 items = core.bootenvList,
108 name = function(idx, choice, all_choices)
109 if #all_choices == 0 then
113 local is_default = (idx == 1)
114 local bootenv_name = ""
117 name_color = color.escapef(color.GREEN)
119 name_color = color.escapef(color.BLUE)
121 bootenv_name = bootenv_name .. name_color ..
122 choice .. color.default()
123 return color.highlight("A").."ctive: " ..
124 bootenv_name .. " (" .. idx .. " of " ..
127 func = function(_, choice, _)
133 entry_type = core.MENU_ENTRY,
135 return color.highlight("b") .. "ootfs: " ..
136 core.bootenvDefault()
139 -- Reset active boot environment to the default
140 config.setCarouselIndex("be_active", 1)
141 bootenvSet(core.bootenvDefault())
148 menu.boot_options = {
150 -- return to welcome menu
152 entry_type = core.MENU_RETURN,
153 name = "Back to main menu" ..
154 color.highlight(" [Backspace]"),
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 -- Swap the first two menu items on single user boot
219 if core.isSingleUserBoot() then
220 -- We'll cache the swapped menu, for performance
221 if menu.welcome.swapped_menu ~= nil then
222 return menu.welcome.swapped_menu
224 -- Shallow copy the table
225 menu_entries = core.deepCopyTable(menu_entries)
227 -- Swap the first two menu entries
228 menu_entries[1], menu_entries[2] =
229 menu_entries[2], menu_entries[1]
231 -- Then set their names to their alternate names
232 menu_entries[1].name, menu_entries[2].name =
233 menu_entries[1].alternate_name,
234 menu_entries[2].alternate_name
235 menu.welcome.swapped_menu = menu_entries
242 entry_type = core.MENU_ENTRY,
243 name = color.highlight("B") .. "oot Multi user " ..
244 color.highlight("[Enter]"),
245 -- Not a standard menu entry function!
246 alternate_name = color.highlight("B") ..
249 core.setSingleUser(false)
256 entry_type = core.MENU_ENTRY,
257 name = "Boot " .. color.highlight("S") .. "ingle user",
258 -- Not a standard menu entry function!
259 alternate_name = "Boot " .. color.highlight("S") ..
260 "ingle user " .. color.highlight("[Enter]"),
262 core.setSingleUser(true)
267 -- escape to interpreter
269 entry_type = core.MENU_RETURN,
270 name = color.highlight("Esc") .. "ape to loader prompt",
272 loader.setenv("autoboot_delay", "NO")
274 alias = {core.KEYSTR_ESCAPE}
278 entry_type = core.MENU_ENTRY,
279 name = color.highlight("R") .. "eboot",
281 loader.perform("reboot")
286 entry_type = core.MENU_SEPARATOR,
289 entry_type = core.MENU_SEPARATOR,
294 entry_type = core.MENU_CAROUSEL_ENTRY,
295 carousel_id = "kernel",
296 items = core.kernelList,
297 name = function(idx, choice, all_choices)
298 if #all_choices == 0 then
302 local is_default = (idx == 1)
303 local kernel_name = ""
306 name_color = color.escapef(color.GREEN)
307 kernel_name = "default/"
309 name_color = color.escapef(color.BLUE)
311 kernel_name = kernel_name .. name_color ..
312 choice .. color.default()
313 return color.highlight("K") .. "ernel: " ..
314 kernel_name .. " (" .. idx .. " of " ..
317 func = function(_, choice, _)
318 config.selectKernel(choice)
324 entry_type = core.MENU_SUBMENU,
325 name = "Boot " .. color.highlight("O") .. "ptions",
326 submenu = menu.boot_options,
331 entry_type = core.MENU_SUBMENU,
333 return core.isZFSBoot() and
334 #core.bootenvList() > 1
336 name = "Boot " .. color.highlight("E") .. "nvironments",
337 submenu = menu.boot_environments,
343 menu.default = menu.welcome
359 local alias_table = drawer.drawscreen(m)
361 -- Might return nil, that's ok
363 if m == menu.default then
364 autoboot_key = menu.autoboot()
368 local key = autoboot_key or io.getchar()
371 -- Special key behaviors
372 if (key == core.KEY_BACKSPACE or key == core.KEY_DELETE) and
373 m ~= menu.default then
375 elseif key == core.KEY_ENTER then
380 key = string.char(key)
381 -- check to see if key is an alias
382 local sel_entry = nil
383 for k, v in pairs(alias_table) do
389 -- if we have an alias do the assigned action:
390 if sel_entry ~= nil then
392 local handler = menu.handlers[sel_entry.entry_type]
393 if handler ~= nil then
394 -- The handler's return value indicates whether
395 -- we need to exit this menu. An omitted return
396 -- value means "continue" by default.
397 cont = handler(m, sel_entry)
402 -- if we got an alias key the screen is out of date:
405 alias_table = drawer.drawscreen(m)
409 if m == menu.default then
411 print("Exiting menu!")
419 if core.isSerialBoot() then
422 local c = string.lower(loader.getenv("console") or "")
423 if c:match("^efi[ ;]") ~= nil or c:match("[ ;]efi[ ;]") ~= nil then
427 c = string.lower(loader.getenv("beastie_disable") or "")
428 print("beastie_disable", c)
432 function menu.autoboot()
433 local ab = loader.getenv("autoboot_delay")
434 if ab ~= nil and ab:lower() == "no" then
436 elseif tonumber(ab) == -1 then
439 ab = tonumber(ab) or 10
441 local x = loader.getenv("loader_menu_timeout_x") or 5
442 local y = loader.getenv("loader_menu_timeout_y") or 22
444 local endtime = loader.time() + ab
448 time = endtime - loader.time()
449 screen.setcursor(x, y)
450 print("Autoboot in " .. time ..
451 " seconds, hit [Enter] to boot" ..
452 " or any other key to stop ")
455 local ch = io.getchar()
456 if ch == core.KEY_ENTER then
459 -- erase autoboot msg
460 screen.setcursor(0, y)
461 print(string.rep(" ", 80))