2 -- Copyright (c) 2015 Pedro Souza <pedrosouza@freebsd.org>
3 -- Copyright (C) 2018 Kyle Evans <kevans@FreeBSD.org>
4 -- All rights reserved.
6 -- Redistribution and use in source and binary forms, with or without
7 -- modification, are permitted provided that the following conditions
9 -- 1. Redistributions of source code must retain the above copyright
10 -- notice, this list of conditions and the following disclaimer.
11 -- 2. Redistributions in binary form must reproduce the above copyright
12 -- notice, this list of conditions and the following disclaimer in the
13 -- documentation and/or other materials provided with the distribution.
15 -- THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 -- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 -- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 -- ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 -- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 -- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 -- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 -- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 -- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 -- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 local core = require("core")
32 local color = require("color")
33 local config = require("config")
34 local screen = require("screen")
35 local drawer = require("drawer")
43 local OnOff = function(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 bootenvSet = function(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(current_menu, entry)
70 [core.MENU_CAROUSEL_ENTRY] = function(current_menu, 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(current_menu, entry)
86 return menu.run(entry.submenu)
88 [core.MENU_RETURN] = function(current_menu, 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(idx, choice, all_choices)
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.shallowCopyTable(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(idx, choice, all_choices)
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
361 local alias_table = drawer.drawscreen(m)
363 -- Might return nil, that's ok
365 if m == menu.default then
366 autoboot_key = menu.autoboot()
370 local key = autoboot_key or io.getchar()
373 -- Special key behaviors
374 if (key == core.KEY_BACKSPACE or key == core.KEY_DELETE) and
375 m ~= menu.default then
377 elseif key == core.KEY_ENTER then
382 key = string.char(key)
383 -- check to see if key is an alias
384 local sel_entry = nil
385 for k, v in pairs(alias_table) do
391 -- if we have an alias do the assigned action:
392 if sel_entry ~= nil then
394 local handler = menu.handlers[sel_entry.entry_type]
395 if handler ~= nil then
396 -- The handler's return value indicates whether
397 -- we need to exit this menu. An omitted return
398 -- value means "continue" by default.
399 cont = handler(m, sel_entry)
404 -- if we got an alias key the screen is out of date:
407 alias_table = drawer.drawscreen(m)
411 if m == menu.default then
413 print("Exiting menu!")
421 if core.isSerialBoot() then
424 local c = string.lower(loader.getenv("console") or "")
425 if c:match("^efi[ ;]") ~= nil or c:match("[ ;]efi[ ;]") ~= nil then
429 c = string.lower(loader.getenv("beastie_disable") or "")
430 print("beastie_disable", c)
434 function menu.autoboot()
435 local ab = loader.getenv("autoboot_delay")
436 if ab ~= nil and ab:lower() == "no" then
438 elseif tonumber(ab) == -1 then
441 ab = tonumber(ab) or 10
443 local x = loader.getenv("loader_menu_timeout_x") or 5
444 local y = loader.getenv("loader_menu_timeout_y") or 22
446 local endtime = loader.time() + ab
450 time = endtime - loader.time()
451 screen.setcursor(x, y)
452 print("Autoboot in " .. time ..
453 " seconds, hit [Enter] to boot" ..
454 " or any other key to stop ")
457 local ch = io.getchar()
458 if ch == core.KEY_ENTER then
461 -- erase autoboot msg
462 screen.setcursor(0, y)