diff --git a/stand/defaults/loader.conf.5 b/stand/defaults/loader.conf.5 --- a/stand/defaults/loader.conf.5 +++ b/stand/defaults/loader.conf.5 @@ -427,10 +427,13 @@ default settings \(em do not change this file. .It Pa /boot/loader.conf user defined settings. +.It Pa /boot/loader.conf.lua +user defined settings written in lua. .It Pa /boot/loader.conf.local machine-specific settings for sites with a common loader.conf. .El .Sh SEE ALSO +.Xr loader.conf.lua 5 , .Xr rc.conf 5 , .Xr boot 8 , .Xr cpucontrol 8 , diff --git a/stand/lua/Makefile b/stand/lua/Makefile --- a/stand/lua/Makefile +++ b/stand/lua/Makefile @@ -2,7 +2,8 @@ .include -MAN= cli.lua.8 \ +MAN= loader.conf.lua.5 \ + cli.lua.8 \ color.lua.8 \ config.lua.8 \ core.lua.8 \ diff --git a/stand/lua/config.lua b/stand/lua/config.lua --- a/stand/lua/config.lua +++ b/stand/lua/config.lua @@ -45,6 +45,7 @@ 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_FAILEXECLUA = "Failed to execute lua conf '%s': '%s'" local MSG_FAILPARSEVAR = "Failed to parse variable '%s': %s" local MSG_FAILEXBEF = "Failed to execute '%s' before loading '%s'" local MSG_FAILEXAF = "Failed to execute '%s' after loading '%s'" @@ -244,13 +245,15 @@ -- local pattern_table = { { + luaexempt = true, str = "(#.*)", process = function(_, _) end, groups = 1, }, -- module_load="value" { - str = MODULEEXPR .. "_load%s*=%s*$VALUE", + name = MODULEEXPR .. "_load%s*", + val = "%s*$VALUE", process = function(k, v) if modules[k] == nil then modules[k] = {} @@ -261,7 +264,8 @@ }, -- module_name="value" { - str = MODULEEXPR .. "_name%s*=%s*$VALUE", + name = MODULEEXPR .. "_name%s*", + val = "%s*$VALUE", process = function(k, v) setKey(k, "name", v) setEnv(k .. "_name", v) @@ -269,7 +273,8 @@ }, -- module_type="value" { - str = MODULEEXPR .. "_type%s*=%s*$VALUE", + name = MODULEEXPR .. "_type%s*", + val = "%s*$VALUE", process = function(k, v) setKey(k, "type", v) setEnv(k .. "_type", v) @@ -277,7 +282,8 @@ }, -- module_flags="value" { - str = MODULEEXPR .. "_flags%s*=%s*$VALUE", + name = MODULEEXPR .. "_flags%s*", + val = "%s*$VALUE", process = function(k, v) setKey(k, "flags", v) setEnv(k .. "_flags", v) @@ -285,7 +291,8 @@ }, -- module_before="value" { - str = MODULEEXPR .. "_before%s*=%s*$VALUE", + name = MODULEEXPR .. "_before%s*", + val = "%s*$VALUE", process = function(k, v) setKey(k, "before", v) setEnv(k .. "_before", v) @@ -293,7 +300,8 @@ }, -- module_after="value" { - str = MODULEEXPR .. "_after%s*=%s*$VALUE", + name = MODULEEXPR .. "_after%s*", + val = "%s*$VALUE", process = function(k, v) setKey(k, "after", v) setEnv(k .. "_after", v) @@ -301,7 +309,8 @@ }, -- module_error="value" { - str = MODULEEXPR .. "_error%s*=%s*$VALUE", + name = MODULEEXPR .. "_error%s*", + val = "%s*$VALUE", process = function(k, v) setKey(k, "error", v) setEnv(k .. "_error", v) @@ -309,7 +318,9 @@ }, -- exec="command" { - str = "exec%s*=%s*" .. QVALEXPR, + luaexempt = true, + name = "exec%s*", + val = "%s*" .. QVALEXPR, process = function(k, _) if cli_execute_unparsed(k) ~= 0 then print(MSG_FAILEXEC:format(k)) @@ -319,7 +330,8 @@ }, -- env_var="value" or env_var=[word|num] { - str = "([%w][%w%d-_.]*)%s*=%s*$VALUE", + name = "([%w][%w%d-_.]*)%s*", + val = "%s*$VALUE", process = function(k, v) local pv, msg = processEnvVar(v) if not pv then @@ -328,6 +340,8 @@ end if setEnv(k, pv) ~= 0 then print(MSG_FAILSETENV:format(k, v)) + else + return pv end end, }, @@ -486,6 +500,18 @@ loader.setenv("nextboot_enable", "NO") end +local function processEnv(k, v) + for _, val in ipairs(pattern_table) do + if not val.luaexempt and val.name then + local matched = k:match(val.name) + + if matched then + return val.process(matched, v) + end + end + end +end + -- Module exports config.verbose = false @@ -511,7 +537,47 @@ return silent end - return config.parse(text) + if name:match(".lua$") then + local cfg_env = setmetatable({}, { + indices = {}, + __index = function(env, key) + if getmetatable(env).indices[key] then + return rawget(env, key) + end + + return loader.getenv(key) + end, + __newindex = function(env, key, val) + getmetatable(env).indices[key] = true + rawset(env, key, val) + end, + }) + + -- Give local modules a chance to populate the config + -- environment. + hook.runAll("config.buildenv", cfg_env) + local res, err = pcall(load(text, name, "t", cfg_env)) + if res then + for k, v in pairs(cfg_env) do + local t = type(v) + if t ~= "function" and t ~= "table" then + if t ~= "string" then + v = tostring(v) + end + local pval = processEnv(k, v) + if pval then + setEnv(k, pval) + end + end + end + else + print(MSG_FAILEXECLUA:format(name, err)) + end + + return res + else + return config.parse(text) + end end -- silent runs will not return false if we fail to open the file @@ -522,6 +588,9 @@ for line in text:gmatch("([^\n]+)") do if line:match("^%s*$") == nil then for _, val in ipairs(pattern_table) do + if val.str == nil then + val.str = val.name .. "=" .. val.val + end local pattern = '^%s*' .. val.str .. '%s*(.*)'; local cgroups = val.groups or 2 local k, v, c = checkPattern(line, pattern) @@ -805,6 +874,7 @@ } end +hook.registerType("config.buildenv") hook.registerType("config.loaded") hook.registerType("config.reloaded") hook.registerType("kernel.loaded") diff --git a/stand/lua/config.lua.8 b/stand/lua/config.lua.8 --- a/stand/lua/config.lua.8 +++ b/stand/lua/config.lua.8 @@ -217,11 +217,19 @@ The following hooks are defined in .Nm : .Bl -tag -width "config.reloaded" -offset indent +.It Fn config.buildenv env .It Fn config.loaded .It Fn config.reloaded .It Fn kernel.loaded .It Fn modules.loaded .El +.Pp +Note that the +.Fn config.buildenv +hook is only invoked when an environment needs to be built to execute a lua +configuration file that has been specified in +.Ev loader_conf_files . +It will be invoked for each configuration file encountered. .Sh SEE ALSO .Xr loader.conf 5 , .Xr loader 8 , diff --git a/stand/lua/hook.lua b/stand/lua/hook.lua --- a/stand/lua/hook.lua +++ b/stand/lua/hook.lua @@ -60,7 +60,7 @@ -- Takes a hooktype and runs all functions associated with that specific hook -- type in the order that they were registered in. This ordering should likely -- not be relied upon. -function hook.runAll(hooktype) +function hook.runAll(hooktype, data) local selected_hooks = registered_hooks[hooktype] if selected_hooks == nil then -- This message, and the print() above, should only be seen by @@ -74,7 +74,7 @@ end if #selected_hooks > 0 then for _, func in ipairs(selected_hooks) do - func() + func(data) end end return #selected_hooks diff --git a/stand/lua/hook.lua.8 b/stand/lua/hook.lua.8 --- a/stand/lua/hook.lua.8 +++ b/stand/lua/hook.lua.8 @@ -26,7 +26,7 @@ .\" .\" $FreeBSD$ .\" -.Dd June 9, 2018 +.Dd April 28, 2020 .Dt HOOK.LUA 8 .Os .Sh NAME @@ -39,6 +39,8 @@ loader execution. These pre-defined points are what we refer to as .Dq hook types . +Hooks may also take an optional data parameter, which may or may not be +populated by the caller. .Pp Before using the functionality provided by .Nm , diff --git a/stand/lua/loader.conf.lua.5 b/stand/lua/loader.conf.lua.5 new file mode 100644 --- /dev/null +++ b/stand/lua/loader.conf.lua.5 @@ -0,0 +1,77 @@ +.\" +.\" SPDX-License-Identifier: BSD-2-Clause-FreeBSD +.\" +.\" Copyright (c) 2020 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 May 10, 2023 +.Dt LOADER.CONF.LUA 5 +.Os +.Sh NAME +.Nm loader.conf.lua +.Nd Lua-based system bootstrap configuration file +.Sh DESCRIPTION +When the lua-based +.Xr loader 8 +encounters a filename in +.Va loader_conf_files +that has a +.Dq .lua +suffix, it will be loaded and executed by the lua interpreter in a limited +environment. +.Pp +The limited environment does not contain the ability to reference or load other +lua modules. +Existing loader environment variables may be referenced as if they were already +defined global variables. +.Pp +A lua configuration file may set any global variable, which will subsequently +be processed and added to the environment after execution of the configuration +file has completed. +Other than the +.Ar exec +setting, all variables described in +.Xr loader.conf 5 +operate the same in the +.Nm +environment. +Note that the settings describing module options can only be set in the +environment; there is currently no way for a +.Pa loader.conf.lua +to fetch them. +At this time, global table and function values are ignored. +.Pp +The +.Fn config.buildenv +hook will be run with an empty environment provided to it that may be populated +by a custom +.Pa local.lua . +.Sh SEE ALSO +.Xr loader.conf 5 +.Sh AUTHORS +The mechanism for loading +.Nm +files was originally written by +.An Kyle Evans Aq Mt kevans@FreeBSD.org .