diff --git a/stand/lua/cli.lua b/stand/lua/cli.lua index 188292191448..d1947ca1021d 100644 --- a/stand/lua/cli.lua +++ b/stand/lua/cli.lua @@ -1,145 +1,251 @@ -- -- SPDX-License-Identifier: BSD-2-Clause-FreeBSD -- -- Copyright (c) 2018 Kyle Evans -- -- 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 = require("config") local core = require("core") local cli = {} +if not pager then + -- shim for the pager module that just doesn't do it. + -- XXX Remove after 12.2 goes EoL. + pager = { + open = function() end, + close = function() end, + output = function(str) + printc(str) + end, + } +end + -- Internal function -- Parses arguments to boot and returns two values: kernel_name, argstr -- Defaults to nil and "" respectively. -- This will also parse arguments to autoboot, but the with_kernel argument -- will need to be explicitly overwritten to false local function parseBootArgs(argv, with_kernel) if with_kernel == nil then with_kernel = true end if #argv == 0 then if with_kernel then return nil, "" else return "" end end local kernel_name local argstr = "" for _, v in ipairs(argv) do if with_kernel and v:sub(1,1) ~= "-" then kernel_name = v else argstr = argstr .. " " .. v end end if with_kernel then return kernel_name, argstr else return argstr end end +local function setModule(module, loading) + if loading and config.enableModule(module) then + print(module .. " will be loaded") + elseif not loading and config.disableModule(module) then + print(module .. " will not be loaded") + end +end + -- Declares a global function cli_execute that attempts to dispatch the -- arguments passed as a lua function. This gives lua a chance to intercept -- builtin CLI commands like "boot" -- This function intentionally does not follow our general naming guideline for -- functions. This is global pollution, but the clearly separated 'cli' looks -- more like a module indicator to serve as a hint of where to look for the -- corresponding definition. function cli_execute(...) local argv = {...} -- Just in case... if #argv == 0 then return loader.command(...) end local cmd_name = argv[1] local cmd = cli[cmd_name] if cmd ~= nil and type(cmd) == "function" then -- Pass argv wholesale into cmd. We could omit argv[0] since the -- traditional reasons for including it don't necessarily apply, -- it may not be totally redundant if we want to have one global -- handling multiple commands return cmd(...) else return loader.command(...) end end function cli_execute_unparsed(str) return cli_execute(loader.parse(str)) end -- Module exports function cli.boot(...) local _, argv = cli.arguments(...) local kernel, argstr = parseBootArgs(argv) if kernel ~= nil then loader.perform("unload") config.selectKernel(kernel) end core.boot(argstr) end function cli.autoboot(...) local _, argv = cli.arguments(...) local argstr = parseBootArgs(argv, false) core.autoboot(argstr) end cli['boot-conf'] = function(...) local _, argv = cli.arguments(...) local kernel, argstr = parseBootArgs(argv) if kernel ~= nil then loader.perform("unload") config.selectKernel(kernel) end core.autoboot(argstr) end cli['read-conf'] = function(...) local _, argv = cli.arguments(...) config.readConf(assert(core.popFrontTable(argv))) end cli['reload-conf'] = function(...) config.reload() end +cli["enable-module"] = function(...) + local _, argv = cli.arguments(...) + if #argv == 0 then + print("usage error: enable-module module") + return + end + + setModule(argv[1], true) +end + +cli["disable-module"] = function(...) + local _, argv = cli.arguments(...) + if #argv == 0 then + print("usage error: disable-module module") + return + end + + setModule(argv[1], false) +end + +cli["toggle-module"] = function(...) + local _, argv = cli.arguments(...) + if #argv == 0 then + print("usage error: toggle-module module") + return + end + + local module = argv[1] + setModule(module, not config.isModuleEnabled(module)) +end + +cli["show-module-options"] = function() + local module_info = config.getModuleInfo() + local modules = module_info['modules'] + local blacklist = module_info['blacklist'] + local lines = {} + + for module, info in pairs(modules) do + if #lines > 0 then + lines[#lines + 1] = "" + end + + lines[#lines + 1] = "Name: " .. module + if info.name then + lines[#lines + 1] = "Path: " .. info.name + end + + if info.type then + lines[#lines + 1] = "Type: " .. info.type + end + + if info.flags then + lines[#lines + 1] = "Flags: " .. info.flags + end + + if info.before then + lines[#lines + 1] = "Before load: " .. info.before + end + + if info.after then + lines[#lines + 1] = "After load: " .. info.after + end + + if info.error then + lines[#lines + 1] = "Error: " .. info.error + end + + local status + if blacklist[module] and not info.force then + status = "Blacklisted" + elseif info.load == "YES" then + status = "Load" + else + status = "Don't load" + end + + lines[#lines + 1] = "Status: " .. status + end + + pager.open() + for i, v in ipairs(lines) do + pager.output(v .. "\n") + end + pager.close() +end + -- Used for splitting cli varargs into cmd_name and the rest of argv function cli.arguments(...) local argv = {...} local cmd_name cmd_name, argv = core.popFrontTable(argv) return cmd_name, argv end return cli diff --git a/stand/lua/cli.lua.8 b/stand/lua/cli.lua.8 index ac9ed3580448..390831d21452 100644 --- a/stand/lua/cli.lua.8 +++ b/stand/lua/cli.lua.8 @@ -1,126 +1,154 @@ .\" .\" SPDX-License-Identifier: BSD-2-Clause-FreeBSD .\" .\" Copyright (c) 2018 Kyle Evans .\" .\" 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$ .\" -.Dd September 13, 2019 +.Dd December 17, 2020 .Dt CLI.LUA 8 .Os .Sh NAME .Nm cli.lua .Nd FreeBSD Lua CLI module .Sh DESCRIPTION .Nm contains the main functionality required to add new CLI commands, which can be executed at the loader prompt. .Pp Before hooking into the functionality provided by .Nm , it must be included with a statement such as the following: .Pp .Dl local cli = require("cli") .Ss Adding new commands New loader commands may be created by adding functions to the object returned by requiring the .Nm module. .Pp For instance: .Pp .Bd -literal -offset indent -compact local cli = require("cli") cli.foo = function(...) -- Expand args to command name and the rest of argv. These arguments -- are pushed directly to the stack by loader, then handed off to -- cli_execute. cli_execute then passes them on to the invoked -- function, where they appear as varargs that must be peeled apart into -- their respective components. local _, argv = cli.arguments(...) print("This is the foo command!") for k, v in ipairs(argv) do print("arg #" .. tostring(k) .. ": '" .. v .. "'") end -- Perform a loader command directly. This will not get dispatched back -- to Lua, so it is acceptable to have a function of the exact same name -- in loader. Lua will have the first chance to handle any commands -- executed at the loader prompt. loader.perform("foo") end .Ed .Pp This function may be invoked by a user at the loader prompt by simply typing .Ic foo . Arguments may be passed to it as usual, space-delimited. .Ss Default Commands -As of present, the +The .Nm -module by default provides commands for -.Ic autoboot , -.Ic boot , -.Ic boot-conf , -and -.Ic reload-conf . +module provides the following default commands: +.Bl -bullet +.\"-width toggle-module -offset indent +.It +.Ic autoboot +.It +.Ic boot +.It +.Ic boot-conf +.It +.Ic reload-conf +.It +.Ic enable-module +.It +.Ic disable-module +.It +.Ic toggle-module +.It +.Ic show-module-options +.El .Pp For .Ic autoboot , .Ic boot , and .Ic boot-conf , the .Xr core.lua 8 module will load all ELF modules as-needed before executing the equivalent built-in loader commands. All non-kernel arguments to these commands are passed in the same order to the loader command. .Pp The .Ic reload-conf command will reload the configuration from disk. This is useful if you have manually changed currdev and would like to easily reload the configuration from the new device. +.Pp +The +.Ic enable-module , +.Ic disable-module , +and +.Ic toggle-module +commands manipulate the list of modules to be loaded along with the kernel. +Modules blacklisted are considered disabled by +.Ic toggle-module . +These commands will override any such restriction as needed. +The +.Ic show-module-options +command will dump the list of modules that loader has been made aware of and +any applicable options using paged output. .Ss Exported Functions The following functions are exported from .Nm : .Bl -tag -width cli.arguments -offset indent .It Fn cli.arguments ... Takes varargs passed on the stack from .Xr loader 8 to .Ic cli_execute , splits them out into two return values: the command name, traditionally argv[0], and the rest of argv. .El .Sh SEE ALSO .Xr loader.conf 5 , .Xr core.lua 8 , .Xr loader 8 .Sh AUTHORS The .Nm file was originally written by .An Kyle Evans Aq Mt kevans@FreeBSD.org . diff --git a/stand/lua/config.lua b/stand/lua/config.lua index 0d9e78aa02dc..5b554806fc9f 100644 --- a/stand/lua/config.lua +++ b/stand/lua/config.lua @@ -1,680 +1,726 @@ -- -- SPDX-License-Identifier: BSD-2-Clause-FreeBSD -- -- 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 hook = require("hook") local config = {} local modules = {} local carousel_choices = {} -- Which variables we changed local env_changed = {} -- Values to restore env to (nil to unset) local env_restore = {} local MSG_FAILEXEC = "Failed to exec '%s'" local MSG_FAILSETENV = "Failed to '%s' with value: %s" local MSG_FAILOPENCFG = "Failed to open config: '%s'" local MSG_FAILREADCFG = "Failed to read config: '%s'" local MSG_FAILPARSECFG = "Failed to parse config: '%s'" local MSG_FAILEXBEF = "Failed to execute '%s' before loading '%s'" local MSG_FAILEXAF = "Failed to execute '%s' after loading '%s'" local MSG_MALFORMED = "Malformed line (%d):\n\t'%s'" local MSG_DEFAULTKERNFAIL = "No kernel set, failed to load from module_path" local MSG_KERNFAIL = "Failed to load kernel '%s'" local MSG_XENKERNFAIL = "Failed to load Xen kernel '%s'" local MSG_XENKERNLOADING = "Loading Xen kernel..." local MSG_KERNLOADING = "Loading kernel..." local MSG_MODLOADING = "Loading configured modules..." local MSG_MODBLACKLIST = "Not loading blacklisted module '%s'" local MODULEEXPR = '([%w-_]+)' local QVALEXPR = "\"([%w%s%p]-)\"" local QVALREPL = QVALEXPR:gsub('%%', '%%%%') local WORDEXPR = "([%w]+)" local WORDREPL = WORDEXPR:gsub('%%', '%%%%') -- Entries that should never make it into the environment; each one should have -- a documented reason for its existence, and these should all be implementation -- details of the config module. local loader_env_restricted_table = { -- loader_conf_files should be considered write-only, and consumers -- should not rely on any particular value; it's a loader implementation -- detail. Moreover, it's not a particularly useful variable to have in -- the kenv. Save the overhead, let it get fetched other ways. loader_conf_files = true, } local function restoreEnv() -- Examine changed environment variables for k, v in pairs(env_changed) do local restore_value = env_restore[k] if restore_value == nil then -- This one doesn't need restored for some reason goto continue end local current_value = loader.getenv(k) if current_value ~= v then -- This was overwritten by some action taken on the menu -- most likely; we'll leave it be. goto continue end restore_value = restore_value.value if restore_value ~= nil then loader.setenv(k, restore_value) else loader.unsetenv(k) end ::continue:: end env_changed = {} env_restore = {} end -- XXX This getEnv/setEnv should likely be exported at some point. We can save -- the call back into loader.getenv for any variable that's been set or -- overridden by any loader.conf using this implementation with little overhead -- since we're already tracking the values. local function getEnv(key) if loader_env_restricted_table[key] ~= nil or env_changed[key] ~= nil then return env_changed[key] end return loader.getenv(key) end local function setEnv(key, value) env_changed[key] = value if loader_env_restricted_table[key] ~= nil then return 0 end -- Track the original value for this if we haven't already if env_restore[key] == nil then env_restore[key] = {value = loader.getenv(key)} end return loader.setenv(key, value) end -- name here is one of 'name', 'type', flags', 'before', 'after', or 'error.' -- These are set from lines in loader.conf(5): ${key}_${name}="${value}" where -- ${key} is a module name. local function setKey(key, name, value) if modules[key] == nil then modules[key] = {} end modules[key][name] = value end -- Escapes the named value for use as a literal in a replacement pattern. -- e.g. dhcp.host-name gets turned into dhcp%.host%-name to remove the special -- meaning. local function escapeName(name) return name:gsub("([%p])", "%%%1") end local function processEnvVar(value) for name in value:gmatch("${([^}]+)}") do local replacement = loader.getenv(name) or "" value = value:gsub("${" .. escapeName(name) .. "}", replacement) end for name in value:gmatch("$([%w%p]+)%s*") do local replacement = loader.getenv(name) or "" value = value:gsub("$" .. escapeName(name), replacement) end return value end local function checkPattern(line, pattern) local function _realCheck(_line, _pattern) return _line:match(_pattern) end if pattern:find('$VALUE') then local k, v, c k, v, c = _realCheck(line, pattern:gsub('$VALUE', QVALREPL)) if k ~= nil then return k,v, c end return _realCheck(line, pattern:gsub('$VALUE', WORDREPL)) else return _realCheck(line, pattern) end end -- str in this table is a regex pattern. It will automatically be anchored to -- the beginning of a line and any preceding whitespace will be skipped. The -- pattern should have no more than two captures patterns, which correspond to -- the two parameters (usually 'key' and 'value') that are passed to the -- process function. All trailing characters will be validated. Any $VALUE -- token included in a pattern will be tried first with a quoted value capture -- group, then a single-word value capture group. This is our kludge for Lua -- regex not supporting branching. -- -- We have two special entries in this table: the first is the first entry, -- a full-line comment. The second is for 'exec' handling. Both have a single -- capture group, but the difference is that the full-line comment pattern will -- match the entire line. This does not run afoul of the later end of line -- validation that we'll do after a match. However, the 'exec' pattern will. -- We document the exceptions with a special 'groups' index that indicates -- the number of capture groups, if not two. We'll use this later to do -- validation on the proper entry. -- local pattern_table = { { str = "(#.*)", process = function(_, _) end, groups = 1, }, -- module_load="value" { str = MODULEEXPR .. "_load%s*=%s*$VALUE", process = function(k, v) if modules[k] == nil then modules[k] = {} end modules[k].load = v:upper() end, }, -- module_name="value" { str = MODULEEXPR .. "_name%s*=%s*$VALUE", process = function(k, v) setKey(k, "name", v) end, }, -- module_type="value" { str = MODULEEXPR .. "_type%s*=%s*$VALUE", process = function(k, v) setKey(k, "type", v) end, }, -- module_flags="value" { str = MODULEEXPR .. "_flags%s*=%s*$VALUE", process = function(k, v) setKey(k, "flags", v) end, }, -- module_before="value" { str = MODULEEXPR .. "_before%s*=%s*$VALUE", process = function(k, v) setKey(k, "before", v) end, }, -- module_after="value" { str = MODULEEXPR .. "_after%s*=%s*$VALUE", process = function(k, v) setKey(k, "after", v) end, }, -- module_error="value" { str = MODULEEXPR .. "_error%s*=%s*$VALUE", process = function(k, v) setKey(k, "error", v) end, }, -- exec="command" { str = "exec%s*=%s*" .. QVALEXPR, process = function(k, _) if cli_execute_unparsed(k) ~= 0 then print(MSG_FAILEXEC:format(k)) end end, groups = 1, }, -- env_var="value" { str = "([%w%p]+)%s*=%s*$VALUE", process = function(k, v) if setEnv(k, processEnvVar(v)) ~= 0 then print(MSG_FAILSETENV:format(k, v)) end end, }, -- env_var=num { str = "([%w%p]+)%s*=%s*(-?%d+)", process = function(k, v) if setEnv(k, processEnvVar(v)) ~= 0 then print(MSG_FAILSETENV:format(k, tostring(v))) end end, }, } local function isValidComment(line) if line ~= nil then local s = line:match("^%s*#.*") if s == nil then s = line:match("^%s*$") end if s == nil then return false end end return true end local function getBlacklist() local blacklist = {} local blacklist_str = loader.getenv('module_blacklist') if blacklist_str == nil then return blacklist end for mod in blacklist_str:gmatch("[;, ]?([%w-_]+)[;, ]?") do blacklist[mod] = true end return blacklist end local function loadModule(mod, silent) local status = true local blacklist = getBlacklist() local pstatus for k, v in pairs(mod) do if v.load ~= nil and v.load:lower() == "yes" then local module_name = v.name or k - if blacklist[module_name] ~= nil then + if not v.force and blacklist[module_name] ~= nil then if not silent then print(MSG_MODBLACKLIST:format(module_name)) end goto continue end if not silent then loader.printc(module_name .. "...") end local str = "load " if v.type ~= nil then str = str .. "-t " .. v.type .. " " end str = str .. module_name if v.flags ~= nil then str = str .. " " .. v.flags end if v.before ~= nil then pstatus = cli_execute_unparsed(v.before) == 0 if not pstatus and not silent then print(MSG_FAILEXBEF:format(v.before, k)) end status = status and pstatus end if cli_execute_unparsed(str) ~= 0 then -- XXX Temporary shim: don't break the boot if -- loader hadn't been recompiled with this -- function exposed. if loader.command_error then print(loader.command_error()) end if not silent then print("failed!") end if v.error ~= nil then cli_execute_unparsed(v.error) end status = false elseif v.after ~= nil then pstatus = cli_execute_unparsed(v.after) == 0 if not pstatus and not silent then print(MSG_FAILEXAF:format(v.after, k)) end if not silent then print("ok") end status = status and pstatus end end ::continue:: end return status end local function readFile(name, silent) local f = io.open(name) if f == nil then if not silent then print(MSG_FAILOPENCFG:format(name)) end return nil end local text, _ = io.read(f) -- We might have read in the whole file, this won't be needed any more. io.close(f) if text == nil and not silent then print(MSG_FAILREADCFG:format(name)) end return text end local function checkNextboot() local nextboot_file = loader.getenv("nextboot_conf") if nextboot_file == nil then return end local text = readFile(nextboot_file, true) if text == nil then return end if text:match("^nextboot_enable=\"NO\"") ~= nil then -- We're done; nextboot is not enabled return end if not config.parse(text) then print(MSG_FAILPARSECFG:format(nextboot_file)) end -- Attempt to rewrite the first line and only the first line of the -- nextboot_file. We overwrite it with nextboot_enable="NO", then -- check for that on load. -- It's worth noting that this won't work on every filesystem, so we -- won't do anything notable if we have any errors in this process. local nfile = io.open(nextboot_file, 'w') if nfile ~= nil then -- We need the trailing space here to account for the extra -- character taken up by the string nextboot_enable="YES" -- Or new end quotation mark lands on the S, and we want to -- rewrite the entirety of the first line. io.write(nfile, "nextboot_enable=\"NO\" ") io.close(nfile) end end -- Module exports config.verbose = false -- The first item in every carousel is always the default item. function config.getCarouselIndex(id) return carousel_choices[id] or 1 end function config.setCarouselIndex(id, idx) carousel_choices[id] = idx end -- Returns true if we processed the file successfully, false if we did not. -- If 'silent' is true, being unable to read the file is not considered a -- failure. function config.processFile(name, silent) if silent == nil then silent = false end local text = readFile(name, silent) if text == nil then return silent end return config.parse(text) end -- silent runs will not return false if we fail to open the file function config.parse(text) local n = 1 local status = true for line in text:gmatch("([^\n]+)") do if line:match("^%s*$") == nil then for _, val in ipairs(pattern_table) do local pattern = '^%s*' .. val.str .. '%s*(.*)'; local cgroups = val.groups or 2 local k, v, c = checkPattern(line, pattern) if k ~= nil then -- Offset by one, drats if cgroups == 1 then c = v v = nil end if isValidComment(c) then val.process(k, v) goto nextline end break end end print(MSG_MALFORMED:format(n, line)) status = false end ::nextline:: n = n + 1 end return status end function config.readConf(file, loaded_files) if loaded_files == nil then loaded_files = {} end if loaded_files[file] ~= nil then return end print("Loading " .. file) -- The final value of loader_conf_files is not important, so just -- clobber it here. We'll later check if it's no longer nil and process -- the new value for files to read. setEnv("loader_conf_files", nil) -- These may or may not exist, and that's ok. Do a -- silent parse so that we complain on parse errors but -- not for them simply not existing. if not config.processFile(file, true) then print(MSG_FAILPARSECFG:format(file)) end loaded_files[file] = true -- Going to process "loader_conf_files" extra-files local loader_conf_files = getEnv("loader_conf_files") if loader_conf_files ~= nil then for name in loader_conf_files:gmatch("[%w%p]+") do config.readConf(name, loaded_files) end end 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 function tryLoad(names) for name in names:gmatch("([^;]+)%s*;?") do local r = loader.perform("load " .. name .. " " .. flags) if r == 0 then return name end end return nil end local function getModulePath() local module_path = loader.getenv("module_path") local kernel_path = loader.getenv("kernel_path") if kernel_path == nil then return module_path end -- Strip the loaded kernel path from module_path. This currently assumes -- that the kernel path will be prepended to the module_path when it's -- found. kernel_path = escapeName(kernel_path .. ';') return module_path:gsub(kernel_path, '') end local function loadBootfile() local bootfile = loader.getenv("bootfile") -- append default kernel name if bootfile == nil then bootfile = "kernel" else bootfile = bootfile .. ";kernel" end return tryLoad(bootfile) end -- kernel not set, try load from default module_path if kernel == nil then local res = loadBootfile() if res ~= nil then -- Default kernel is loaded config.kernel_loaded = nil return true else print(MSG_DEFAULTKERNFAIL) 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 = getModulePath() local res 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 _, v in pairs(paths) do loader.setenv("module_path", v) res = loadBootfile() -- 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) loader.setenv("kernel_path", v) end return true end end -- failed to load with ${kernel} as a directory -- try as a file res = tryLoad(kernel) if res ~= nil then config.kernel_loaded = kernel return true else print(MSG_KERNFAIL:format(kernel)) return false end end end function config.selectKernel(kernel) config.kernel_selected = kernel end function config.load(file, reloading) if not file then file = "/boot/defaults/loader.conf" end config.readConf(file) checkNextboot() local verbose = loader.getenv("verbose_loading") or "no" config.verbose = verbose:lower() == "yes" if not reloading then hook.runAll("config.loaded") end end -- Reload configuration function config.reload(file) modules = {} restoreEnv() config.load(file, true) hook.runAll("config.reloaded") end function config.loadelf() local xen_kernel = loader.getenv('xen_kernel') local kernel = config.kernel_selected or config.kernel_loaded local status if xen_kernel ~= nil then print(MSG_XENKERNLOADING) if cli_execute_unparsed('load ' .. xen_kernel) ~= 0 then print(MSG_XENKERNFAIL:format(xen_kernel)) return false end end print(MSG_KERNLOADING) if not config.loadKernel(kernel) then return false end hook.runAll("kernel.loaded") print(MSG_MODLOADING) status = loadModule(modules, not config.verbose) hook.runAll("modules.loaded") return status end +function config.enableModule(modname) + if modules[modname] == nil then + modules[modname] = {} + elseif modules[modname].load == "YES" then + modules[modname].force = true + return true + end + + modules[modname].load = "YES" + modules[modname].force = true + return true +end + +function config.disableModule(modname) + if modules[modname] == nil then + return false + elseif modules[modname].load ~= "YES" then + return true + end + + modules[modname].load = "NO" + modules[modname].force = nil + return true +end + +function config.isModuleEnabled(modname) + local mod = modules[modname] + if not mod or mod.load ~= "YES" then + return false + end + + if mod.force then + return true + end + + local blacklist = getBlacklist() + return not blacklist[modname] +end + +function config.getModuleInfo() + return { + modules = modules, + blacklist = getBlacklist() + } +end + hook.registerType("config.loaded") hook.registerType("config.reloaded") hook.registerType("kernel.loaded") hook.registerType("modules.loaded") return config diff --git a/stand/lua/config.lua.8 b/stand/lua/config.lua.8 index 098b607271f7..4fc51c90045f 100644 --- a/stand/lua/config.lua.8 +++ b/stand/lua/config.lua.8 @@ -1,208 +1,236 @@ .\" .\" SPDX-License-Identifier: BSD-2-Clause-FreeBSD .\" .\" Copyright (c) 2018 Kyle Evans .\" .\" 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$ .\" -.Dd April 30, 2020 +.Dd December 17, 2020 .Dt CONFIG.LUA 8 .Os .Sh NAME .Nm config.lua .Nd FreeBSD config module .Sh DESCRIPTION .Nm contains configuration and module loading functionality. .Pp Before hooking into or using the functionality provided by .Nm , it must be included with a statement such as the following: .Pp .Dl local config = require("config") .Ss Exported functions The following functions are exported from .Nm : .Bl -tag -width "config.setCarouselIndex(id, idx)" -offset indent .It Fn config.getCarouselIndex id Returns the currently chosen index in the carousel menu entry described by .Ev id . See the definition of .Xr menu.lua 8 for a more in-depth explanation of carousels. .It Fn config.setCarouselIndex id idx Set the chosen index for the carousel menu entry described by .Ev id to .Ev idx . A lookup will be done as needed to determine what value .Ev idx actually corresponds to. .It Fn config.readConf file loaded_files Process .Pa file as a configuration file .Po e.g., as .Pa loader.conf .Pc and then processing files listed in .Ev loader_conf_files variable .Po see .Xr loader.conf 5 .Pc . The caller may optionally pass in a table as the .Ev loaded_files argument, which uses filenames as keys and any non-nil value to indicate that the file named by the key has already been loaded and should not be loaded again. .It Fn config.processFile name silent Process and parse .Ev name as a configuration file. Returns true if .Ev name exists and parses without error, false otherwise. If .Ev silent is true, .Fn config.processFile will not consider a failure to read the file as a failure. .It Fn config.parse text Parse .Ev text as a configuration file. This is used internally by .Fn config.processFile to parse the contents of a configuration file. Returns true if parsing succeeds without error, false if an error occurred. A message is also printed to the console if an error is encountered. .It Fn config.loadKernel other_kernel Attempts to load .Ev other_kernel as a kernel. If .Ev other_kernel is unset .Fn config.loadKernel will attempt to load .Dq kernel . Otherwise, it will try to load .Dq kernel first from .Pa /boot/{other_kernel} , then from .Pa {other_kernel} . .Pp The latter is tried in case an absolute path has been specified to the kernel to use. .Ev module_path is amended to include the directory the kernel was found in if either of these paths result in a loaded kernel. .Pp If no kernel was loaded from either of these paths, .Fn config.loadKernel will attempt to load a kernel named .Dq {other_kernel} from .Ev module_path instead of attempting to load a kernel named .Dq kernel . .Pp Returns true if a kernel was loaded, false if no kernel was loaded. .It Fn config.selectKernel kernel Set .Ev kernel to the kernel that will be loaded when either .Ic autoboot or .Ic boot are invoked. This is usually called by the menu system as the kernel selector carousel is toggled through. .It Fn config.load file reload Loads .Ev file as a configuration file. If .Ev file is not specified, .Pa /boot/defaults/loader.conf is used. .Fn config.load will then silently attempt to process any files specified in .Ev loader_conf_files after .Ev file has been processed. .Xr nextboot 8 configuration will also be checked as part of .Fn config.load . Before returning, all .Dq config.loaded hooks will be run if .Ev reload is not set to true. .It Fn config.reload file Reloads .Ev file as a configuration file. .Fn config.reload will restore the environment to how it existed before the last config was loaded, then it will invoke .Fn config.load file . Before returning, all .Dq config.reloaded hooks will be run. .It Fn config.loadelf Loads all ELF objects, the selected kernel as well as any modules configured to be preloaded in .Xr loader.conf 5 . This will be called by the Lua intercepted .Ic autoboot and .Ic boot commands. +.It Fn config.enableModule modname +Marks a module named +.Fa modname +to be loaded during +.Fn config.loadelf . +If the module was previously blacklisted, then it will be forcefully allowed to +load. +.It Fn config.disableModule modname +Marks a module named +.Fa modname +to not be loaded during +.Fn config.loadelf . +.It Fn config.isModuleEnabled modname +Checks if the module named +.Fa modname +will be loaded during +.Fn config.loadelf . +It checks both that the module is marked for loading and that it is either +forced or not blacklisted. +.It Fn config.getModuleInfo +Returns a table with +.Dq modules +and +.Dq blacklist +tables describing the modules that the config module has been made aware of via +.Xr loader.conf 5 +as well as a representation of +.Ar module_blacklist . .El .Ss Defined Hooks The following hooks are defined in .Nm : .Bl -tag -width "config.reloaded" -offset indent .It Fn config.loaded .It Fn config.reloaded .It Fn kernel.loaded .It Fn modules.loaded .El .Sh SEE ALSO .Xr loader.conf 5 , .Xr loader 8 , .Xr menu.lua 8 , .Xr nextboot 8 .Sh AUTHORS The .Nm file was originally written by .An Pedro Souza Aq Mt pedrosouza@FreeBSD.org . Later work and this manual page was done by .An Kyle Evans Aq Mt kevans@FreeBSD.org .