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