]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - stand/lua/menu.lua
lualoader: Clean up naming conventions a little bit
[FreeBSD/FreeBSD.git] / stand / lua / menu.lua
1 --
2 -- SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 --
4 -- Copyright (c) 2015 Pedro Souza <pedrosouza@freebsd.org>
5 -- Copyright (C) 2018 Kyle Evans <kevans@FreeBSD.org>
6 -- All rights reserved.
7 --
8 -- Redistribution and use in source and binary forms, with or without
9 -- modification, are permitted provided that the following conditions
10 -- are met:
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.
16 --
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
27 -- SUCH DAMAGE.
28 --
29 -- $FreeBSD$
30 --
31
32
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")
38
39 local menu = {}
40
41 local function OnOff(str, b)
42         if b then
43                 return str .. color.escapef(color.GREEN) .. "On" ..
44                     color.escapef(color.WHITE)
45         else
46                 return str .. color.escapef(color.RED) .. "off" ..
47                     color.escapef(color.WHITE)
48         end
49 end
50
51 local function bootenvSet(env)
52         loader.setenv("vfs.root.mountfrom", env)
53         loader.setenv("currdev", env .. ":")
54         config.reload()
55 end
56
57 -- Module exports
58 menu.handlers = {
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)
65                 -- run function
66                 entry.func()
67         end,
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
74                         choices = choices()
75                 end
76                 if #choices > 0 then
77                         caridx = (caridx % #choices) + 1
78                         config.setCarouselIndex(carid, caridx)
79                         entry.func(caridx, choices[caridx], choices)
80                 end
81         end,
82         [core.MENU_SUBMENU] = function(_, entry)
83                 -- recurse
84                 return menu.run(entry.submenu)
85         end,
86         [core.MENU_RETURN] = function(_, entry)
87                 -- allow entry to have a function/side effect
88                 if entry.func ~= nil then
89                         entry.func()
90                 end
91                 return false
92         end,
93 }
94 -- loader menu tree is rooted at menu.welcome
95
96 menu.boot_environments = {
97         entries = {
98                 -- return to welcome menu
99                 {
100                         entry_type = core.MENU_RETURN,
101                         name = "Back to main menu" ..
102                             color.highlight(" [Backspace]"),
103                 },
104                 {
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
110                                         return "Active: "
111                                 end
112
113                                 local is_default = (idx == 1)
114                                 local bootenv_name = ""
115                                 local name_color
116                                 if is_default then
117                                         name_color = color.escapef(color.GREEN)
118                                 else
119                                         name_color = color.escapef(color.BLUE)
120                                 end
121                                 bootenv_name = bootenv_name .. name_color ..
122                                     choice .. color.default()
123                                 return color.highlight("A").."ctive: " ..
124                                     bootenv_name .. " (" .. idx .. " of " ..
125                                     #all_choices .. ")"
126                         end,
127                         func = function(_, choice, _)
128                                 bootenvSet(choice)
129                         end,
130                         alias = {"a", "A"},
131                 },
132                 {
133                         entry_type = core.MENU_ENTRY,
134                         name = function()
135                                 return color.highlight("b") .. "ootfs: " ..
136                                     core.bootenvDefault()
137                         end,
138                         func = function()
139                                 -- Reset active boot environment to the default
140                                 config.setCarouselIndex("be_active", 1)
141                                 bootenvSet(core.bootenvDefault())
142                         end,
143                         alias = {"b", "B"},
144                 },
145         },
146 }
147
148 menu.boot_options = {
149         entries = {
150                 -- return to welcome menu
151                 {
152                         entry_type = core.MENU_RETURN,
153                         name = "Back to main menu" ..
154                             color.highlight(" [Backspace]"),
155                 },
156                 -- load defaults
157                 {
158                         entry_type = core.MENU_ENTRY,
159                         name = "Load System " .. color.highlight("D") ..
160                             "efaults",
161                         func = core.setDefaults,
162                         alias = {"d", "D"}
163                 },
164                 {
165                         entry_type = core.MENU_SEPARATOR,
166                 },
167                 {
168                         entry_type = core.MENU_SEPARATOR,
169                         name = "Boot Options:",
170                 },
171                 -- acpi
172                 {
173                         entry_type = core.MENU_ENTRY,
174                         visible = core.isSystem386,
175                         name = function()
176                                 return OnOff(color.highlight("A") ..
177                                     "CPI       :", core.acpi)
178                         end,
179                         func = core.setACPI,
180                         alias = {"a", "A"}
181                 },
182                 -- safe mode
183                 {
184                         entry_type = core.MENU_ENTRY,
185                         name = function()
186                                 return OnOff("Safe " .. color.highlight("M") ..
187                                     "ode  :", core.sm)
188                         end,
189                         func = core.setSafeMode,
190                         alias = {"m", "M"}
191                 },
192                 -- single user
193                 {
194                         entry_type = core.MENU_ENTRY,
195                         name = function()
196                                 return OnOff(color.highlight("S") ..
197                                     "ingle user:", core.su)
198                         end,
199                         func = core.setSingleUser,
200                         alias = {"s", "S"}
201                 },
202                 -- verbose boot
203                 {
204                         entry_type = core.MENU_ENTRY,
205                         name = function()
206                                 return OnOff(color.highlight("V") ..
207                                     "erbose    :", core.verbose)
208                         end,
209                         func = core.setVerbose,
210                         alias = {"v", "V"}
211                 },
212         },
213 }
214
215 menu.welcome = {
216         entries = function()
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
223                         end
224                         -- Shallow copy the table
225                         menu_entries = core.deepCopyTable(menu_entries)
226
227                         -- Swap the first two menu entries
228                         menu_entries[1], menu_entries[2] =
229                             menu_entries[2], menu_entries[1]
230
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
236                 end
237                 return menu_entries
238         end,
239         all_entries = {
240                 -- boot multi user
241                 {
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") ..
247                             "oot Multi user",
248                         func = function()
249                                 core.setSingleUser(false)
250                                 core.boot()
251                         end,
252                         alias = {"b", "B"}
253                 },
254                 -- boot single user
255                 {
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]"),
261                         func = function()
262                                 core.setSingleUser(true)
263                                 core.boot()
264                         end,
265                         alias = {"s", "S"}
266                 },
267                 -- escape to interpreter
268                 {
269                         entry_type = core.MENU_RETURN,
270                         name = color.highlight("Esc") .. "ape to loader prompt",
271                         func = function()
272                                 loader.setenv("autoboot_delay", "NO")
273                         end,
274                         alias = {core.KEYSTR_ESCAPE}
275                 },
276                 -- reboot
277                 {
278                         entry_type = core.MENU_ENTRY,
279                         name = color.highlight("R") .. "eboot",
280                         func = function()
281                                 loader.perform("reboot")
282                         end,
283                         alias = {"r", "R"}
284                 },
285                 {
286                         entry_type = core.MENU_SEPARATOR,
287                 },
288                 {
289                         entry_type = core.MENU_SEPARATOR,
290                         name = "Options:",
291                 },
292                 -- kernel options
293                 {
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
299                                         return "Kernel: "
300                                 end
301
302                                 local is_default = (idx == 1)
303                                 local kernel_name = ""
304                                 local name_color
305                                 if is_default then
306                                         name_color = color.escapef(color.GREEN)
307                                         kernel_name = "default/"
308                                 else
309                                         name_color = color.escapef(color.BLUE)
310                                 end
311                                 kernel_name = kernel_name .. name_color ..
312                                     choice .. color.default()
313                                 return color.highlight("K") .. "ernel: " ..
314                                     kernel_name .. " (" .. idx .. " of " ..
315                                     #all_choices .. ")"
316                         end,
317                         func = function(_, choice, _)
318                                 config.selectKernel(choice)
319                         end,
320                         alias = {"k", "K"}
321                 },
322                 -- boot options
323                 {
324                         entry_type = core.MENU_SUBMENU,
325                         name = "Boot " .. color.highlight("O") .. "ptions",
326                         submenu = menu.boot_options,
327                         alias = {"o", "O"}
328                 },
329                 -- boot environments
330                 {
331                         entry_type = core.MENU_SUBMENU,
332                         visible = function()
333                                 return core.isZFSBoot() and
334                                     #core.bootenvList() > 1
335                         end,
336                         name = "Boot " .. color.highlight("E") .. "nvironments",
337                         submenu = menu.boot_environments,
338                         alias = {"e", "E"},
339                 },
340         },
341 }
342
343 menu.default = menu.welcome
344
345 function menu.run(m)
346
347         if menu.skip() then
348                 core.autoboot()
349                 return false
350         end
351
352         if m == nil then
353                 m = menu.default
354         end
355
356         -- redraw screen
357         screen.clear()
358         screen.defcursor()
359         local alias_table = drawer.drawscreen(m)
360
361         -- Might return nil, that's ok
362         local autoboot_key;
363         if m == menu.default then
364                 autoboot_key = menu.autoboot()
365         end
366         local cont = true
367         while cont do
368                 local key = autoboot_key or io.getchar()
369                 autoboot_key = nil
370
371                 -- Special key behaviors
372                 if (key == core.KEY_BACKSPACE or key == core.KEY_DELETE) and
373                     m ~= menu.default then
374                         break
375                 elseif key == core.KEY_ENTER then
376                         core.boot()
377                         -- Should not return
378                 end
379
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
384                         if key == k then
385                                 sel_entry = v
386                         end
387                 end
388
389                 -- if we have an alias do the assigned action:
390                 if sel_entry ~= nil then
391                         -- Get menu handler
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)
398                                 if cont == nil then
399                                         cont = true
400                                 end
401                         end
402                         -- if we got an alias key the screen is out of date:
403                         screen.clear()
404                         screen.defcursor()
405                         alias_table = drawer.drawscreen(m)
406                 end
407         end
408
409         if m == menu.default then
410                 screen.defcursor()
411                 print("Exiting menu!")
412                 return false
413         end
414
415         return true
416 end
417
418 function menu.skip()
419         if core.isSerialBoot() then
420                 return true
421         end
422         local c = string.lower(loader.getenv("console") or "")
423         if c:match("^efi[ ;]") ~= nil or c:match("[ ;]efi[ ;]") ~= nil then
424                 return true
425         end
426
427         c = string.lower(loader.getenv("beastie_disable") or "")
428         print("beastie_disable", c)
429         return c == "yes"
430 end
431
432 function menu.autoboot()
433         local ab = loader.getenv("autoboot_delay")
434         if ab ~= nil and ab:lower() == "no" then
435                 return nil
436         elseif tonumber(ab) == -1 then
437                 core.boot()
438         end
439         ab = tonumber(ab) or 10
440
441         local x = loader.getenv("loader_menu_timeout_x") or 5
442         local y = loader.getenv("loader_menu_timeout_y") or 22
443
444         local endtime = loader.time() + ab
445         local time
446
447         repeat
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     ")
453                 screen.defcursor()
454                 if io.ischar() then
455                         local ch = io.getchar()
456                         if ch == core.KEY_ENTER then
457                                 break
458                         else
459                                 -- erase autoboot msg
460                                 screen.setcursor(0, y)
461                                 print(string.rep(" ", 80))
462                                 screen.defcursor()
463                                 return ch
464                         end
465                 end
466
467                 loader.delay(50000)
468         until time <= 0
469         core.boot()
470
471 end
472
473 return menu