Index: head/stand/lua/config.lua =================================================================== --- head/stand/lua/config.lua (revision 329595) +++ head/stand/lua/config.lua (revision 329596) @@ -1,436 +1,437 @@ -- -- Copyright (c) 2015 Pedro Souza +-- Copyright (C) 2018 Kyle Evans -- All rights reserved. -- -- Redistribution and use in source and binary forms, with or without -- modification, are permitted provided that the following conditions -- are met: -- 1. Redistributions of source code must retain the above copyright -- notice, this list of conditions and the following disclaimer. -- 2. Redistributions in binary form must reproduce the above copyright -- notice, this list of conditions and the following disclaimer in the -- documentation and/or other materials provided with the distribution. -- -- THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND -- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -- ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE -- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS -- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -- SUCH DAMAGE. -- -- $FreeBSD$ -- local config = {}; -- Which variables we changed config.env_changed = {}; -- Values to restore env to (nil to unset) config.env_restore = {}; local modules = {}; function config.restoreEnv() for k, v in pairs(config.env_changed) do local restore_value = config.env_restore[k]; if (restore_value ~= nil) then loader.setenv(k, restore_value); else loader.unsetenv(k); end end config.env_changed = {}; config.env_restore = {}; end function config.setenv(k, v) -- Do we need to track this change? if (config.env_changed[k] == nil) then config.env_changed[k] = true; config.env_restore[k] = loader.getenv(k); end return loader.setenv(k, v); end function config.setKey(k, n, v) if (modules[k] == nil) then modules[k] = {}; end modules[k][n] = v; end function config.lsModules() print("== Listing modules"); for k, v in pairs(modules) do print(k, v.load); end print("== List of modules ended"); end local pattern_table = { [1] = { str = "^%s*(#.*)", process = function(k, v) end }, -- module_load="value" [2] = { str = "^%s*([%w_]+)_load%s*=%s*\"([%w%s%p]-)\"%s*(.*)", process = function(k, v) if (modules[k] == nil) then modules[k] = {}; end modules[k].load = v:upper(); end }, -- module_name="value" [3] = { str = "^%s*([%w_]+)_name%s*=%s*\"([%w%s%p]-)\"%s*(.*)", process = function(k, v) config.setKey(k, "name", v); end }, -- module_type="value" [4] = { str = "^%s*([%w_]+)_type%s*=%s*\"([%w%s%p]-)\"%s*(.*)", process = function(k, v) config.setKey(k, "type", v); end }, -- module_flags="value" [5] = { str = "^%s*([%w_]+)_flags%s*=%s*\"([%w%s%p]-)\"%s*(.*)", process = function(k, v) config.setKey(k, "flags", v); end }, -- module_before="value" [6] = { str = "^%s*([%w_]+)_before%s*=%s*\"([%w%s%p]-)\"%s*(.*)", process = function(k, v) config.setKey(k, "before", v); end }, -- module_after="value" [7] = { str = "^%s*([%w_]+)_after%s*=%s*\"([%w%s%p]-)\"%s*(.*)", process = function(k, v) config.setKey(k, "after", v); end }, -- module_error="value" [8] = { str = "^%s*([%w_]+)_error%s*=%s*\"([%w%s%p]-)\"%s*(.*)", process = function(k, v) config.setKey(k, "error", v); end }, -- exec="command" [9] = { str = "^%s*exec%s*=%s*\"([%w%s%p]-)\"%s*(.*)", process = function(k, v) if (loader.perform(k) ~= 0) then print("Failed to exec '" .. k .. "'"); end end }, -- env_var="value" [10] = { str = "^%s*([%w%p]+)%s*=%s*\"([%w%s%p]-)\"%s*(.*)", process = function(k, v) if (config.setenv(k, v) ~= 0) then print("Failed to set '" .. k .. "' with value: " .. v .. ""); end end }, -- env_var=num [11] = { str = "^%s*([%w%p]+)%s*=%s*(%d+)%s*(.*)", process = function(k, v) if (config.setenv(k, v) ~= 0) then print("Failed to set '" .. k .. "' with value: " .. v .. ""); end end } }; function config.isValidComment(c) if (c ~= nil) then local s = c:match("^%s*#.*"); if (s == nil) then s = c:match("^%s*$"); end if (s == nil) then return false; end end return true; end function config.loadmod(mod, silent) local status = true; for k, v in pairs(mod) do if (v.load == "YES") then local str = "load "; if (v.flags ~= nil) then str = str .. v.flags .. " "; end if (v.type ~= nil) then str = str .. "-t " .. v.type .. " "; end if (v.name ~= nil) then str = str .. v.name; else str = str .. k; end if (v.before ~= nil) then if (loader.perform(v.before) ~= 0) then if (not silent) then print("Failed to execute '" .. v.before .. "' before loading '" .. k .. "'"); end status = false; end end if (loader.perform(str) ~= 0) then if (not silent) then print("Failed to execute '" .. str .. "'"); end if (v.error ~= nil) then loader.perform(v.error); end status = false; end if (v.after ~= nil) then if (loader.perform(v.after) ~= 0) then if (not silent) then print("Failed to execute '" .. v.after .. "' after loading '" .. k .. "'"); end status = false; end end else -- if not silent then -- print("Skiping module '". . k .. "'"); -- end end end return status; end function config.parse(name, silent) local f = io.open(name); if (f == nil) then if (not silent) then print("Failed to open config: '" .. name .. "'"); end return false; end local text; local r; text, r = io.read(f); if (text == nil) then if (not silent) then print("Failed to read config: '" .. name .. "'"); end return false; end local n = 1; local status = true; for line in text:gmatch("([^\n]+)") do if (line:match("^%s*$") == nil) then local found = false; for i, val in ipairs(pattern_table) do local k, v, c = line:match(val.str); if (k ~= nil) then found = true; if (config.isValidComment(c)) then val.process(k, v); else print("Malformed line (" .. n .. "):\n\t'" .. line .. "'"); status = false; end break; end end if (found == false) then print("Malformed line (" .. n .. "):\n\t'" .. line .. "'"); status = false; end end n = n + 1; end return status; end -- other_kernel is optionally the name of a kernel to load, if not the default -- or autoloaded default from the module_path function config.loadkernel(other_kernel) local flags = loader.getenv("kernel_options") or ""; local kernel = other_kernel or loader.getenv("kernel"); local try_load = function (names) for name in names:gmatch("([^;]+)%s*;?") do r = loader.perform("load " .. flags .. " " .. name); if (r == 0) then return name; end end return nil; end local load_bootfile = function() local bootfile = loader.getenv("bootfile"); -- append default kernel name if (bootfile == nil) then bootfile = "kernel"; else bootfile = bootfile .. ";kernel"; end return try_load(bootfile); end -- kernel not set, try load from default module_path if (kernel == nil) then local res = load_bootfile(); if (res ~= nil) then -- Default kernel is loaded config.kernel_loaded = nil; return true; else print("No kernel set, failed to load from module_path"); return false; end else -- Use our cached module_path, so we don't end up with multiple -- automatically added kernel paths to our final module_path local module_path = config.module_path; local res = nil; if (other_kernel ~= nil) then kernel = other_kernel; end -- first try load kernel with module_path = /boot/${kernel} -- then try load with module_path=${kernel} local paths = {"/boot/" .. kernel, kernel}; for k,v in pairs(paths) do loader.setenv("module_path", v); res = load_bootfile(); -- succeeded, add path to module_path if (res ~= nil) then config.kernel_loaded = kernel; if (module_path ~= nil) then loader.setenv("module_path", v .. ";" .. module_path); end return true; end end -- failed to load with ${kernel} as a directory -- try as a file res = try_load(kernel); if (res ~= nil) then config.kernel_loaded = kernel; return true; else print("Failed to load kernel '" .. kernel .. "'"); return false; end end end function config.selectkernel(kernel) config.kernel_selected = kernel; end function config.load(file) if (not file) then file = "/boot/defaults/loader.conf"; end if (not config.parse(file)) then -- print("Failed to parse configuration: '" .. file .. "'"); end local f = loader.getenv("loader_conf_files"); if (f ~= nil) then for name in f:gmatch("([%w%p]+)%s*") do if (not config.parse(name)) then -- print("Failed to parse configuration: '" .. -- name .. "'"); end end end -- Cache the provided module_path at load time for later use config.module_path = loader.getenv("module_path"); end -- Reload configuration function config.reload(file) modules = {}; config.restoreEnv(); config.load(file); end function config.loadelf() local kernel = config.kernel_loaded or config.kernel_selected; local loaded = false; print("Loading kernel..."); loaded = config.loadkernel(kernel); if (not loaded) then loaded = config.loadkernel(); end if (not loaded) then -- Ultimately failed to load kernel print("Failed to load any kernel"); return; end print("Loading configured modules..."); if (not config.loadmod(modules)) then print("Could not load one or more modules!"); end end return config; Index: head/stand/lua/menu.lua =================================================================== --- head/stand/lua/menu.lua (revision 329595) +++ head/stand/lua/menu.lua (revision 329596) @@ -1,449 +1,450 @@ -- -- Copyright (c) 2015 Pedro Souza +-- Copyright (C) 2018 Kyle Evans -- All rights reserved. -- -- Redistribution and use in source and binary forms, with or without -- modification, are permitted provided that the following conditions -- are met: -- 1. Redistributions of source code must retain the above copyright -- notice, this list of conditions and the following disclaimer. -- 2. Redistributions in binary form must reproduce the above copyright -- notice, this list of conditions and the following disclaimer in the -- documentation and/or other materials provided with the distribution. -- -- THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND -- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -- ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE -- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS -- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -- SUCH DAMAGE. -- -- $FreeBSD$ -- local core = require("core"); local color = require("color"); local config = require("config"); local screen = require("screen"); local drawer = require("drawer"); local menu = {}; local OnOff; local skip; local run; local autoboot; local carousel_choices = {}; -- loader menu tree is rooted at menu.welcome menu.boot_options = { entries = { -- return to welcome menu { entry_type = core.MENU_RETURN, name = function() return "Back to main menu" .. color.highlight(" [Backspace]"); end }, -- load defaults { entry_type = core.MENU_ENTRY, name = function() return "Load System " .. color.highlight("D") .. "efaults"; end, func = function() core.setDefaults(); end, alias = {"d", "D"} }, { entry_type = core.MENU_SEPARATOR, name = function() return ""; end }, { entry_type = core.MENU_SEPARATOR, name = function() return "Boot Options:"; end }, -- acpi { entry_type = core.MENU_ENTRY, name = function() return OnOff(color.highlight("A") .. "CPI :", core.acpi); end, func = function() core.setACPI(); end, alias = {"a", "A"} }, -- safe mode { entry_type = core.MENU_ENTRY, name = function() return OnOff("Safe " .. color.highlight("M") .. "ode :", core.sm); end, func = function() core.setSafeMode(); end, alias = {"m", "M"} }, -- single user { entry_type = core.MENU_ENTRY, name = function() return OnOff(color.highlight("S") .. "ingle user:", core.su); end, func = function() core.setSingleUser(); end, alias = {"s", "S"} }, -- verbose boot { entry_type = core.MENU_ENTRY, name = function() return OnOff(color.highlight("V") .. "erbose :", core.verbose); end, func = function() core.setVerbose(); end, alias = {"v", "V"} }, }, }; menu.welcome = { entries = function() local menu_entries = menu.welcome.all_entries; -- Swap the first two menu items on single user boot if (core.isSingleUserBoot()) then -- Shallow copy the table menu_entries = core.shallowCopyTable(menu_entries); local multiuser = menu_entries[1]; local singleuser = menu_entries[2]; multiuser.name = multiuser.alternate_name; singleuser.name = singleuser.alternate_name; menu_entries[2] = multiuser; menu_entries[1] = singleuser; end return menu_entries; end, all_entries = { -- boot multi user { entry_type = core.MENU_ENTRY, name = function() return color.highlight("B") .. "oot Multi user " .. color.highlight("[Enter]"); end, -- Not a standard menu entry function! alternate_name = function() return color.highlight("B") .. "oot Multi user"; end, func = function() core.setSingleUser(false); core.boot(); end, alias = {"b", "B"} }, -- boot single user { entry_type = core.MENU_ENTRY, name = function() return "Boot " .. color.highlight("S") .. "ingle user"; end, -- Not a standard menu entry function! alternate_name = function() return "Boot " .. color.highlight("S") .. "ingle user " .. color.highlight("[Enter]"); end, func = function() core.setSingleUser(true); core.boot(); end, alias = {"s", "S"} }, -- escape to interpreter { entry_type = core.MENU_RETURN, name = function() return color.highlight("Esc") .. "ape to loader prompt"; end, func = function() loader.setenv("autoboot_delay", "NO"); end, alias = {core.KEYSTR_ESCAPE} }, -- reboot { entry_type = core.MENU_ENTRY, name = function() return color.highlight("R") .. "eboot"; end, func = function() loader.perform("reboot"); end, alias = {"r", "R"} }, { entry_type = core.MENU_SEPARATOR, name = function() return ""; end }, { entry_type = core.MENU_SEPARATOR, name = function() return "Options:"; end }, -- kernel options { entry_type = core.MENU_CAROUSEL_ENTRY, carousel_id = "kernel", items = core.kernelList, name = function(idx, choice, all_choices) if (#all_choices == 0) then return "Kernel: "; end local is_default = (idx == 1); local kernel_name = ""; local name_color; if (is_default) then name_color = color.escapef(color.GREEN); kernel_name = "default/"; else name_color = color.escapef(color.BLUE); end kernel_name = kernel_name .. name_color .. choice .. color.default(); return color.highlight("K") .. "ernel: " .. kernel_name .. " (" .. idx .. " of " .. #all_choices .. ")"; end, func = function(idx, choice, all_choices) config.selectkernel(choice); end, alias = {"k", "K"} }, -- boot options { entry_type = core.MENU_SUBMENU, name = function() return "Boot " .. color.highlight("O") .. "ptions"; end, submenu = function() return menu.boot_options; end, alias = {"o", "O"} }, }, }; -- The first item in every carousel is always the default item. function menu.getCarouselIndex(id) local val = carousel_choices[id]; if (val == nil) then return 1; end return val; end function menu.setCarouselIndex(id, idx) carousel_choices[id] = idx; end function menu.run(m) if (menu.skip()) then core.autoboot(); return false; end if (m == nil) then m = menu.welcome; end -- redraw screen screen.clear(); screen.defcursor(); local alias_table = drawer.drawscreen(m); menu.autoboot(); cont = true; while (cont) do local key = io.getchar(); -- Special key behaviors if ((key == core.KEY_BACKSPACE) or (key == core.KEY_DELETE)) and (m ~= menu.welcome) then break; elseif (key == core.KEY_ENTER) then core.boot(); -- Should not return end key = string.char(key) -- check to see if key is an alias local sel_entry = nil; for k, v in pairs(alias_table) do if (key == k) then sel_entry = v; end end -- if we have an alias do the assigned action: if (sel_entry ~= nil) then if (sel_entry.entry_type == core.MENU_ENTRY) then -- run function sel_entry.func(); elseif (sel_entry.entry_type == core.MENU_CAROUSEL_ENTRY) then -- carousel (rotating) functionality local carid = sel_entry.carousel_id; local caridx = menu.getCarouselIndex(carid); local choices = sel_entry.items(); if (#choices > 0) then caridx = (caridx % #choices) + 1; menu.setCarouselIndex(carid, caridx); sel_entry.func(caridx, choices[caridx], choices); end elseif (sel_entry.entry_type == core.MENU_SUBMENU) then -- recurse cont = menu.run(sel_entry.submenu()); elseif (sel_entry.entry_type == core.MENU_RETURN) then -- allow entry to have a function/side effect if (sel_entry.func ~= nil) then sel_entry.func(); end -- break recurse cont = false; end -- if we got an alias key the screen is out of date: screen.clear(); screen.defcursor(); alias_table = drawer.drawscreen(m); end end if (m == menu.welcome) then screen.defcursor(); print("Exiting menu!"); config.loadelf(); return false; end return true; end function menu.skip() if (core.isSerialBoot()) then return true; end local c = string.lower(loader.getenv("console") or ""); if ((c:match("^efi[ ;]") or c:match("[ ;]efi[ ;]")) ~= nil) then return true; end c = string.lower(loader.getenv("beastie_disable") or ""); print("beastie_disable", c); return c == "yes"; end function menu.autoboot() if (menu.already_autoboot == true) then return; end menu.already_autoboot = true; local ab = loader.getenv("autoboot_delay"); if (ab ~= nil) and (ab:lower() == "no") then return; elseif (tonumber(ab) == -1) then core.boot(); end ab = tonumber(ab) or 10; local x = loader.getenv("loader_menu_timeout_x") or 5; local y = loader.getenv("loader_menu_timeout_y") or 22; local endtime = loader.time() + ab; local time; repeat time = endtime - loader.time(); screen.setcursor(x, y); print("Autoboot in " .. time .. " seconds, hit [Enter] to boot" .. " or any other key to stop "); screen.defcursor(); if (io.ischar()) then local ch = io.getchar(); if (ch == core.KEY_ENTER) then break; else -- erase autoboot msg screen.setcursor(0, y); print(" " .. " "); screen.defcursor(); return; end end loader.delay(50000); until time <= 0; core.boot(); end function OnOff(str, b) if (b) then return str .. color.escapef(color.GREEN) .. "On" .. color.escapef(color.WHITE); else return str .. color.escapef(color.RED) .. "off" .. color.escapef(color.WHITE); end end return menu; Index: head/stand/lua/password.lua =================================================================== --- head/stand/lua/password.lua (revision 329595) +++ head/stand/lua/password.lua (revision 329596) @@ -1,100 +1,101 @@ -- -- Copyright (c) 2015 Pedro Souza +-- Copyright (C) 2018 Kyle Evans -- All rights reserved. -- -- Redistribution and use in source and binary forms, with or without -- modification, are permitted provided that the following conditions -- are met: -- 1. Redistributions of source code must retain the above copyright -- notice, this list of conditions and the following disclaimer. -- 2. Redistributions in binary form must reproduce the above copyright -- notice, this list of conditions and the following disclaimer in the -- documentation and/or other materials provided with the distribution. -- -- THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND -- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -- ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE -- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS -- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -- SUCH DAMAGE. -- -- $FreeBSD$ -- local core = require("core"); local screen = require("screen"); local password = {}; function password.read() local str = ""; local n = 0; repeat ch = io.getchar(); if (ch == core.KEY_ENTER) then break; end -- XXX TODO: Evaluate if we really want this or not, as a -- security consideration of sorts if (ch == core.KEY_BACKSPACE) or (ch == core.KEY_DELETE) then if (n > 0) then n = n - 1; -- loader.printc("\008 \008"); str = str:sub(1, n); end else -- loader.printc("*"); str = str .. string.char(ch); n = n + 1; end until (n == 16); return str; end function password.check() screen.clear(); screen.defcursor(); -- pwd is optionally supplied if we want to check it local function do_prompt(prompt, pwd) while (true) do loader.printc(prompt); local read_pwd = password.read(); if (not pwd) or (pwd == read_pwd) then -- Throw an extra newline after password prompt print(""); return read_pwd; end print("\n\nloader: incorrect password!\n"); loader.delay(3*1000*1000); end end local function compare(prompt, pwd) if (pwd == nil) then return; end do_prompt(prompt, pwd); end local boot_pwd = loader.getenv("bootlock_password"); compare("Boot password: ", boot_pwd); local geli_prompt = loader.getenv("geom_eli_passphrase_prompt"); if (geli_prompt ~= nil) and (geli_prompt:lower() == "yes") then local passphrase = do_prompt("GELI Passphrase: "); loader.setenv("kern.geom.eli.passphrase", passphrase); end local pwd = loader.getenv("password"); if (pwd ~= nil) then core.autoboot(); end compare("Password: ", pwd); end return password;