xref: /freebsd-13.1/stand/lua/config.lua (revision e3cd8246)
1088b4f5fSWarner Losh--
272e39d71SKyle Evans-- SPDX-License-Identifier: BSD-2-Clause-FreeBSD
372e39d71SKyle Evans--
4088b4f5fSWarner Losh-- Copyright (c) 2015 Pedro Souza <[email protected]>
5e12ff891SKyle Evans-- Copyright (c) 2018 Kyle Evans <[email protected]>
6088b4f5fSWarner Losh-- All rights reserved.
7088b4f5fSWarner Losh--
8088b4f5fSWarner Losh-- Redistribution and use in source and binary forms, with or without
9088b4f5fSWarner Losh-- modification, are permitted provided that the following conditions
10088b4f5fSWarner Losh-- are met:
11088b4f5fSWarner Losh-- 1. Redistributions of source code must retain the above copyright
12088b4f5fSWarner Losh--    notice, this list of conditions and the following disclaimer.
13088b4f5fSWarner Losh-- 2. Redistributions in binary form must reproduce the above copyright
14088b4f5fSWarner Losh--    notice, this list of conditions and the following disclaimer in the
15088b4f5fSWarner Losh--    documentation and/or other materials provided with the distribution.
16088b4f5fSWarner Losh--
17088b4f5fSWarner Losh-- THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18088b4f5fSWarner Losh-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19088b4f5fSWarner Losh-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20088b4f5fSWarner Losh-- ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21088b4f5fSWarner Losh-- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22088b4f5fSWarner Losh-- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23088b4f5fSWarner Losh-- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24088b4f5fSWarner Losh-- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25088b4f5fSWarner Losh-- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26088b4f5fSWarner Losh-- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27088b4f5fSWarner Losh-- SUCH DAMAGE.
28088b4f5fSWarner Losh--
29088b4f5fSWarner Losh-- $FreeBSD$
30088b4f5fSWarner Losh--
31088b4f5fSWarner Losh
32aea262bfSKyle Evanslocal hook = require("hook")
33aea262bfSKyle Evans
34aedd6be5SKyle Evanslocal config = {}
35aedd6be5SKyle Evanslocal modules = {}
36aedd6be5SKyle Evanslocal carousel_choices = {}
3764c91742SKyle Evans-- Which variables we changed
3864c91742SKyle Evanslocal env_changed = {}
3964c91742SKyle Evans-- Values to restore env to (nil to unset)
4064c91742SKyle Evanslocal env_restore = {}
4125c4c7a5SKyle Evans
4272cf7db3SKyle Evanslocal MSG_FAILDIR = "Failed to load conf dir '%s': not a directory"
43fdabb5f5SKyle Evanslocal MSG_FAILEXEC = "Failed to exec '%s'"
44fdabb5f5SKyle Evanslocal MSG_FAILSETENV = "Failed to '%s' with value: %s"
45fdabb5f5SKyle Evanslocal MSG_FAILOPENCFG = "Failed to open config: '%s'"
46fdabb5f5SKyle Evanslocal MSG_FAILREADCFG = "Failed to read config: '%s'"
47fdabb5f5SKyle Evanslocal MSG_FAILPARSECFG = "Failed to parse config: '%s'"
48633f8212SKyle Evanslocal MSG_FAILPARSEVAR = "Failed to parse variable '%s': %s"
49fdabb5f5SKyle Evanslocal MSG_FAILEXBEF = "Failed to execute '%s' before loading '%s'"
50fdabb5f5SKyle Evanslocal MSG_FAILEXAF = "Failed to execute '%s' after loading '%s'"
51fdabb5f5SKyle Evanslocal MSG_MALFORMED = "Malformed line (%d):\n\t'%s'"
52fdabb5f5SKyle Evanslocal MSG_DEFAULTKERNFAIL = "No kernel set, failed to load from module_path"
53fdabb5f5SKyle Evanslocal MSG_KERNFAIL = "Failed to load kernel '%s'"
540d7bee6aSKyle Evanslocal MSG_XENKERNFAIL = "Failed to load Xen kernel '%s'"
550d7bee6aSKyle Evanslocal MSG_XENKERNLOADING = "Loading Xen kernel..."
56fdabb5f5SKyle Evanslocal MSG_KERNLOADING = "Loading kernel..."
57fdabb5f5SKyle Evanslocal MSG_MODLOADING = "Loading configured modules..."
58532dc172SKyle Evanslocal MSG_MODBLACKLIST = "Not loading blacklisted module '%s'"
59fdabb5f5SKyle Evans
60633f8212SKyle Evanslocal MSG_FAILSYN_QUOTE = "Stray quote at position '%d'"
61633f8212SKyle Evanslocal MSG_FAILSYN_EOLESC = "Stray escape at end of line"
62633f8212SKyle Evanslocal MSG_FAILSYN_EOLVAR = "Unescaped $ at end of line"
63633f8212SKyle Evanslocal MSG_FAILSYN_BADVAR = "Malformed variable expression at position '%d'"
64633f8212SKyle Evans
65*e3cd8246SKyle Evanslocal MODULEEXPR = '([-%w_]+)'
66633f8212SKyle Evanslocal QVALEXPR = '"(.*)"'
671ee89ab5SKyle Evanslocal QVALREPL = QVALEXPR:gsub('%%', '%%%%')
68*e3cd8246SKyle Evanslocal WORDEXPR = "([-%w%d][-%w%d_.]*)"
691ee89ab5SKyle Evanslocal WORDREPL = WORDEXPR:gsub('%%', '%%%%')
70058c692eSKyle Evans
71bf832717SKyle Evans-- Entries that should never make it into the environment; each one should have
72bf832717SKyle Evans-- a documented reason for its existence, and these should all be implementation
73bf832717SKyle Evans-- details of the config module.
74bf832717SKyle Evanslocal loader_env_restricted_table = {
75bf832717SKyle Evans	-- loader_conf_files should be considered write-only, and consumers
76bf832717SKyle Evans	-- should not rely on any particular value; it's a loader implementation
77bf832717SKyle Evans	-- detail.  Moreover, it's not a particularly useful variable to have in
78bf832717SKyle Evans	-- the kenv.  Save the overhead, let it get fetched other ways.
79bf832717SKyle Evans	loader_conf_files = true,
80bf832717SKyle Evans}
81bf832717SKyle Evans
8264c91742SKyle Evanslocal function restoreEnv()
8364c91742SKyle Evans	-- Examine changed environment variables
8464c91742SKyle Evans	for k, v in pairs(env_changed) do
8564c91742SKyle Evans		local restore_value = env_restore[k]
8664c91742SKyle Evans		if restore_value == nil then
8764c91742SKyle Evans			-- This one doesn't need restored for some reason
8864c91742SKyle Evans			goto continue
8964c91742SKyle Evans		end
9064c91742SKyle Evans		local current_value = loader.getenv(k)
9164c91742SKyle Evans		if current_value ~= v then
9264c91742SKyle Evans			-- This was overwritten by some action taken on the menu
9364c91742SKyle Evans			-- most likely; we'll leave it be.
9464c91742SKyle Evans			goto continue
9564c91742SKyle Evans		end
9664c91742SKyle Evans		restore_value = restore_value.value
9764c91742SKyle Evans		if restore_value ~= nil then
9864c91742SKyle Evans			loader.setenv(k, restore_value)
9964c91742SKyle Evans		else
10064c91742SKyle Evans			loader.unsetenv(k)
10164c91742SKyle Evans		end
10264c91742SKyle Evans		::continue::
10364c91742SKyle Evans	end
10464c91742SKyle Evans
10564c91742SKyle Evans	env_changed = {}
10664c91742SKyle Evans	env_restore = {}
10764c91742SKyle Evansend
10864c91742SKyle Evans
109bf832717SKyle Evans-- XXX This getEnv/setEnv should likely be exported at some point.  We can save
110bf832717SKyle Evans-- the call back into loader.getenv for any variable that's been set or
111bf832717SKyle Evans-- overridden by any loader.conf using this implementation with little overhead
112bf832717SKyle Evans-- since we're already tracking the values.
113bf832717SKyle Evanslocal function getEnv(key)
114bf832717SKyle Evans	if loader_env_restricted_table[key] ~= nil or
115bf832717SKyle Evans	    env_changed[key] ~= nil then
116bf832717SKyle Evans		return env_changed[key]
117bf832717SKyle Evans	end
118bf832717SKyle Evans
119bf832717SKyle Evans	return loader.getenv(key)
120bf832717SKyle Evansend
121bf832717SKyle Evans
12264c91742SKyle Evanslocal function setEnv(key, value)
123bf832717SKyle Evans	env_changed[key] = value
124bf832717SKyle Evans
125bf832717SKyle Evans	if loader_env_restricted_table[key] ~= nil then
126bf832717SKyle Evans		return 0
127bf832717SKyle Evans	end
128bf832717SKyle Evans
12964c91742SKyle Evans	-- Track the original value for this if we haven't already
13064c91742SKyle Evans	if env_restore[key] == nil then
13164c91742SKyle Evans		env_restore[key] = {value = loader.getenv(key)}
13264c91742SKyle Evans	end
13364c91742SKyle Evans
13464c91742SKyle Evans	return loader.setenv(key, value)
13564c91742SKyle Evansend
13664c91742SKyle Evans
137dbef5253SKyle Evans-- name here is one of 'name', 'type', flags', 'before', 'after', or 'error.'
138dbef5253SKyle Evans-- These are set from lines in loader.conf(5): ${key}_${name}="${value}" where
139dbef5253SKyle Evans-- ${key} is a module name.
140dbef5253SKyle Evanslocal function setKey(key, name, value)
141dbef5253SKyle Evans	if modules[key] == nil then
142dbef5253SKyle Evans		modules[key] = {}
143dbef5253SKyle Evans	end
144dbef5253SKyle Evans	modules[key][name] = value
145dbef5253SKyle Evansend
146dbef5253SKyle Evans
147deb8c8f5SKyle Evans-- Escapes the named value for use as a literal in a replacement pattern.
148deb8c8f5SKyle Evans-- e.g. dhcp.host-name gets turned into dhcp%.host%-name to remove the special
149deb8c8f5SKyle Evans-- meaning.
150deb8c8f5SKyle Evanslocal function escapeName(name)
151deb8c8f5SKyle Evans	return name:gsub("([%p])", "%%%1")
152deb8c8f5SKyle Evansend
153deb8c8f5SKyle Evans
154deb8c8f5SKyle Evanslocal function processEnvVar(value)
155633f8212SKyle Evans	local pval, vlen = '', #value
156633f8212SKyle Evans	local nextpos, vdelim, vinit = 1
157633f8212SKyle Evans	local vpat
158633f8212SKyle Evans	for i = 1, vlen do
159633f8212SKyle Evans		if i < nextpos then
160633f8212SKyle Evans			goto nextc
161deb8c8f5SKyle Evans		end
162633f8212SKyle Evans
163633f8212SKyle Evans		local c = value:sub(i, i)
164633f8212SKyle Evans		if c == '\\' then
165633f8212SKyle Evans			if i == vlen then
166633f8212SKyle Evans				return nil, MSG_FAILSYN_EOLESC
167deb8c8f5SKyle Evans			end
168633f8212SKyle Evans			nextpos = i + 2
169633f8212SKyle Evans			pval = pval .. value:sub(i + 1, i + 1)
170633f8212SKyle Evans		elseif c == '"' then
171633f8212SKyle Evans			return nil, MSG_FAILSYN_QUOTE:format(i)
172633f8212SKyle Evans		elseif c == "$" then
173633f8212SKyle Evans			if i == vlen then
174633f8212SKyle Evans				return nil, MSG_FAILSYN_EOLVAR
175633f8212SKyle Evans			else
176633f8212SKyle Evans				if value:sub(i + 1, i + 1) == "{" then
177633f8212SKyle Evans					-- Skip ${
178633f8212SKyle Evans					vinit = i + 2
179633f8212SKyle Evans					vdelim = '}'
180633f8212SKyle Evans					vpat = "^([^}]+)}"
181633f8212SKyle Evans				else
182633f8212SKyle Evans					-- Skip the $
183633f8212SKyle Evans					vinit = i + 1
184633f8212SKyle Evans					vdelim = nil
185*e3cd8246SKyle Evans					vpat = "^([%w][-%w%d_.]*)"
186633f8212SKyle Evans				end
187633f8212SKyle Evans
188633f8212SKyle Evans				local name = value:match(vpat, vinit)
189633f8212SKyle Evans				if not name then
190633f8212SKyle Evans					return nil, MSG_FAILSYN_BADVAR:format(i)
191633f8212SKyle Evans				else
192633f8212SKyle Evans					nextpos = vinit + #name
193633f8212SKyle Evans					if vdelim then
194633f8212SKyle Evans						nextpos = nextpos + 1
195633f8212SKyle Evans					end
196633f8212SKyle Evans
197633f8212SKyle Evans					local repl = loader.getenv(name) or ""
198633f8212SKyle Evans					pval = pval .. repl
199633f8212SKyle Evans				end
200633f8212SKyle Evans			end
201633f8212SKyle Evans		else
202633f8212SKyle Evans			pval = pval .. c
203633f8212SKyle Evans		end
204633f8212SKyle Evans		::nextc::
205633f8212SKyle Evans	end
206633f8212SKyle Evans
207633f8212SKyle Evans	return pval
208deb8c8f5SKyle Evansend
209deb8c8f5SKyle Evans
2101ee89ab5SKyle Evanslocal function checkPattern(line, pattern)
2111ee89ab5SKyle Evans	local function _realCheck(_line, _pattern)
2121ee89ab5SKyle Evans		return _line:match(_pattern)
2131ee89ab5SKyle Evans	end
2141ee89ab5SKyle Evans
2151ee89ab5SKyle Evans	if pattern:find('$VALUE') then
2161ee89ab5SKyle Evans		local k, v, c
2171ee89ab5SKyle Evans		k, v, c = _realCheck(line, pattern:gsub('$VALUE', QVALREPL))
2181ee89ab5SKyle Evans		if k ~= nil then
2191ee89ab5SKyle Evans			return k,v, c
2201ee89ab5SKyle Evans		end
2211ee89ab5SKyle Evans		return _realCheck(line, pattern:gsub('$VALUE', WORDREPL))
2221ee89ab5SKyle Evans	else
2231ee89ab5SKyle Evans		return _realCheck(line, pattern)
2241ee89ab5SKyle Evans	end
2251ee89ab5SKyle Evansend
2261ee89ab5SKyle Evans
227058c692eSKyle Evans-- str in this table is a regex pattern.  It will automatically be anchored to
228058c692eSKyle Evans-- the beginning of a line and any preceding whitespace will be skipped.  The
229058c692eSKyle Evans-- pattern should have no more than two captures patterns, which correspond to
230058c692eSKyle Evans-- the two parameters (usually 'key' and 'value') that are passed to the
2311ee89ab5SKyle Evans-- process function.  All trailing characters will be validated.  Any $VALUE
2321ee89ab5SKyle Evans-- token included in a pattern will be tried first with a quoted value capture
2331ee89ab5SKyle Evans-- group, then a single-word value capture group.  This is our kludge for Lua
2341ee89ab5SKyle Evans-- regex not supporting branching.
2359a16e110SKyle Evans--
2369a16e110SKyle Evans-- We have two special entries in this table: the first is the first entry,
2379a16e110SKyle Evans-- a full-line comment.  The second is for 'exec' handling.  Both have a single
2389a16e110SKyle Evans-- capture group, but the difference is that the full-line comment pattern will
2399a16e110SKyle Evans-- match the entire line.  This does not run afoul of the later end of line
2409a16e110SKyle Evans-- validation that we'll do after a match.  However, the 'exec' pattern will.
2419a16e110SKyle Evans-- We document the exceptions with a special 'groups' index that indicates
2429a16e110SKyle Evans-- the number of capture groups, if not two.  We'll use this later to do
2439a16e110SKyle Evans-- validation on the proper entry.
2441ee89ab5SKyle Evans--
245e1a8835aSKyle Evanslocal pattern_table = {
246e1a8835aSKyle Evans	{
247058c692eSKyle Evans		str = "(#.*)",
248e1a8835aSKyle Evans		process = function(_, _)  end,
2499a16e110SKyle Evans		groups = 1,
250088b4f5fSWarner Losh	},
251088b4f5fSWarner Losh	--  module_load="value"
252e1a8835aSKyle Evans	{
2531ee89ab5SKyle Evans		str = MODULEEXPR .. "_load%s*=%s*$VALUE",
254088b4f5fSWarner Losh		process = function(k, v)
2559f71d421SKyle Evans			if modules[k] == nil then
256aedd6be5SKyle Evans				modules[k] = {}
257088b4f5fSWarner Losh			end
258aedd6be5SKyle Evans			modules[k].load = v:upper()
259e1a8835aSKyle Evans		end,
260088b4f5fSWarner Losh	},
261088b4f5fSWarner Losh	--  module_name="value"
262e1a8835aSKyle Evans	{
2631ee89ab5SKyle Evans		str = MODULEEXPR .. "_name%s*=%s*$VALUE",
264088b4f5fSWarner Losh		process = function(k, v)
265dbef5253SKyle Evans			setKey(k, "name", v)
266e1a8835aSKyle Evans		end,
267088b4f5fSWarner Losh	},
268088b4f5fSWarner Losh	--  module_type="value"
269e1a8835aSKyle Evans	{
2701ee89ab5SKyle Evans		str = MODULEEXPR .. "_type%s*=%s*$VALUE",
271088b4f5fSWarner Losh		process = function(k, v)
272dbef5253SKyle Evans			setKey(k, "type", v)
273e1a8835aSKyle Evans		end,
274088b4f5fSWarner Losh	},
275088b4f5fSWarner Losh	--  module_flags="value"
276e1a8835aSKyle Evans	{
2771ee89ab5SKyle Evans		str = MODULEEXPR .. "_flags%s*=%s*$VALUE",
278088b4f5fSWarner Losh		process = function(k, v)
279dbef5253SKyle Evans			setKey(k, "flags", v)
280e1a8835aSKyle Evans		end,
281088b4f5fSWarner Losh	},
282088b4f5fSWarner Losh	--  module_before="value"
283e1a8835aSKyle Evans	{
2841ee89ab5SKyle Evans		str = MODULEEXPR .. "_before%s*=%s*$VALUE",
285088b4f5fSWarner Losh		process = function(k, v)
286dbef5253SKyle Evans			setKey(k, "before", v)
287e1a8835aSKyle Evans		end,
288088b4f5fSWarner Losh	},
289088b4f5fSWarner Losh	--  module_after="value"
290e1a8835aSKyle Evans	{
2911ee89ab5SKyle Evans		str = MODULEEXPR .. "_after%s*=%s*$VALUE",
292088b4f5fSWarner Losh		process = function(k, v)
293dbef5253SKyle Evans			setKey(k, "after", v)
294e1a8835aSKyle Evans		end,
295088b4f5fSWarner Losh	},
296088b4f5fSWarner Losh	--  module_error="value"
297e1a8835aSKyle Evans	{
2981ee89ab5SKyle Evans		str = MODULEEXPR .. "_error%s*=%s*$VALUE",
299088b4f5fSWarner Losh		process = function(k, v)
300dbef5253SKyle Evans			setKey(k, "error", v)
301e1a8835aSKyle Evans		end,
302088b4f5fSWarner Losh	},
303088b4f5fSWarner Losh	--  exec="command"
304e1a8835aSKyle Evans	{
3051ee89ab5SKyle Evans		str = "exec%s*=%s*" .. QVALEXPR,
306e2df27e3SKyle Evans		process = function(k, _)
3079ab2d3c5SKyle Evans			if cli_execute_unparsed(k) ~= 0 then
308fdabb5f5SKyle Evans				print(MSG_FAILEXEC:format(k))
309088b4f5fSWarner Losh			end
310e1a8835aSKyle Evans		end,
3119a16e110SKyle Evans		groups = 1,
312088b4f5fSWarner Losh	},
313633f8212SKyle Evans	--  env_var="value" or env_var=[word|num]
314e1a8835aSKyle Evans	{
315633f8212SKyle Evans		str = "([%w][%w%d-_.]*)%s*=%s*$VALUE",
316088b4f5fSWarner Losh		process = function(k, v)
317633f8212SKyle Evans			local pv, msg = processEnvVar(v)
318633f8212SKyle Evans			if not pv then
319633f8212SKyle Evans				print(MSG_FAILPARSEVAR:format(k, msg))
320633f8212SKyle Evans				return
321088b4f5fSWarner Losh			end
322633f8212SKyle Evans			if setEnv(k, pv) ~= 0 then
323633f8212SKyle Evans				print(MSG_FAILSETENV:format(k, v))
324088b4f5fSWarner Losh			end
325e1a8835aSKyle Evans		end,
326e1a8835aSKyle Evans	},
327aedd6be5SKyle Evans}
328088b4f5fSWarner Losh
329dbef5253SKyle Evanslocal function isValidComment(line)
330dbef5253SKyle Evans	if line ~= nil then
331dbef5253SKyle Evans		local s = line:match("^%s*#.*")
332dbef5253SKyle Evans		if s == nil then
333dbef5253SKyle Evans			s = line:match("^%s*$")
334dbef5253SKyle Evans		end
335dbef5253SKyle Evans		if s == nil then
336dbef5253SKyle Evans			return false
337dbef5253SKyle Evans		end
338dbef5253SKyle Evans	end
339dbef5253SKyle Evans	return true
340dbef5253SKyle Evansend
341dbef5253SKyle Evans
342532dc172SKyle Evanslocal function getBlacklist()
343e1f1ddebSKyle Evans	local blacklist = {}
344532dc172SKyle Evans	local blacklist_str = loader.getenv('module_blacklist')
345532dc172SKyle Evans	if blacklist_str == nil then
346e1f1ddebSKyle Evans		return blacklist
347532dc172SKyle Evans	end
348532dc172SKyle Evans
349*e3cd8246SKyle Evans	for mod in blacklist_str:gmatch("[;, ]?([-%w_]+)[;, ]?") do
350532dc172SKyle Evans		blacklist[mod] = true
351532dc172SKyle Evans	end
352532dc172SKyle Evans	return blacklist
353532dc172SKyle Evansend
354532dc172SKyle Evans
355dbef5253SKyle Evanslocal function loadModule(mod, silent)
356dbef5253SKyle Evans	local status = true
357532dc172SKyle Evans	local blacklist = getBlacklist()
358dbef5253SKyle Evans	local pstatus
359dbef5253SKyle Evans	for k, v in pairs(mod) do
3608d21763eSKyle Evans		if v.load ~= nil and v.load:lower() == "yes" then
361532dc172SKyle Evans			local module_name = v.name or k
3624634bb1fSKyle Evans			if not v.force and blacklist[module_name] ~= nil then
363532dc172SKyle Evans				if not silent then
364532dc172SKyle Evans					print(MSG_MODBLACKLIST:format(module_name))
365532dc172SKyle Evans				end
366532dc172SKyle Evans				goto continue
367532dc172SKyle Evans			end
3683078173cSKyle Evans			if not silent then
3693078173cSKyle Evans				loader.printc(module_name .. "...")
3703078173cSKyle Evans			end
371dbef5253SKyle Evans			local str = "load "
372dbef5253SKyle Evans			if v.type ~= nil then
373dbef5253SKyle Evans				str = str .. "-t " .. v.type .. " "
374dbef5253SKyle Evans			end
375532dc172SKyle Evans			str = str .. module_name
37635beb928SKyle Evans			if v.flags ~= nil then
37735beb928SKyle Evans				str = str .. " " .. v.flags
37835beb928SKyle Evans			end
379dbef5253SKyle Evans			if v.before ~= nil then
380dbef5253SKyle Evans				pstatus = cli_execute_unparsed(v.before) == 0
381dbef5253SKyle Evans				if not pstatus and not silent then
382dbef5253SKyle Evans					print(MSG_FAILEXBEF:format(v.before, k))
383dbef5253SKyle Evans				end
384dbef5253SKyle Evans				status = status and pstatus
385dbef5253SKyle Evans			end
386dbef5253SKyle Evans
387dbef5253SKyle Evans			if cli_execute_unparsed(str) ~= 0 then
3883078173cSKyle Evans				-- XXX Temporary shim: don't break the boot if
3893078173cSKyle Evans				-- loader hadn't been recompiled with this
3903078173cSKyle Evans				-- function exposed.
3913078173cSKyle Evans				if loader.command_error then
3923078173cSKyle Evans					print(loader.command_error())
3933078173cSKyle Evans				end
394dbef5253SKyle Evans				if not silent then
3953078173cSKyle Evans					print("failed!")
396dbef5253SKyle Evans				end
397dbef5253SKyle Evans				if v.error ~= nil then
398dbef5253SKyle Evans					cli_execute_unparsed(v.error)
399dbef5253SKyle Evans				end
400dbef5253SKyle Evans				status = false
4013078173cSKyle Evans			elseif v.after ~= nil then
402dbef5253SKyle Evans				pstatus = cli_execute_unparsed(v.after) == 0
403dbef5253SKyle Evans				if not pstatus and not silent then
404dbef5253SKyle Evans					print(MSG_FAILEXAF:format(v.after, k))
405dbef5253SKyle Evans				end
4063078173cSKyle Evans				if not silent then
4073078173cSKyle Evans					print("ok")
4083078173cSKyle Evans				end
409dbef5253SKyle Evans				status = status and pstatus
410dbef5253SKyle Evans			end
411dbef5253SKyle Evans		end
412532dc172SKyle Evans		::continue::
413dbef5253SKyle Evans	end
414dbef5253SKyle Evans
415dbef5253SKyle Evans	return status
416dbef5253SKyle Evansend
417dbef5253SKyle Evans
418322a2dddSKyle Evanslocal function readFile(name, silent)
419164b58fdSKyle Evans	local f = io.open(name)
420164b58fdSKyle Evans	if f == nil then
421164b58fdSKyle Evans		if not silent then
422fdabb5f5SKyle Evans			print(MSG_FAILOPENCFG:format(name))
423164b58fdSKyle Evans		end
424164b58fdSKyle Evans		return nil
425164b58fdSKyle Evans	end
426164b58fdSKyle Evans
427164b58fdSKyle Evans	local text, _ = io.read(f)
428164b58fdSKyle Evans	-- We might have read in the whole file, this won't be needed any more.
429164b58fdSKyle Evans	io.close(f)
430164b58fdSKyle Evans
4318d21763eSKyle Evans	if text == nil and not silent then
432fdabb5f5SKyle Evans		print(MSG_FAILREADCFG:format(name))
433164b58fdSKyle Evans	end
434164b58fdSKyle Evans	return text
435164b58fdSKyle Evansend
436164b58fdSKyle Evans
437322a2dddSKyle Evanslocal function checkNextboot()
438be2050daSKyle Evans	local nextboot_file = loader.getenv("nextboot_conf")
439e307eb94SToomas Soome	local nextboot_enable = loader.getenv("nextboot_enable")
440e307eb94SToomas Soome
441ddfae7e3SKyle Evans	if nextboot_file == nil then
442ddfae7e3SKyle Evans		return
443ddfae7e3SKyle Evans	end
444ddfae7e3SKyle Evans
445e307eb94SToomas Soome	-- is nextboot_enable set in nvstore?
446e307eb94SToomas Soome	if nextboot_enable == "NO" then
447e307eb94SToomas Soome		return
448e307eb94SToomas Soome	end
449e307eb94SToomas Soome
450322a2dddSKyle Evans	local text = readFile(nextboot_file, true)
451ddfae7e3SKyle Evans	if text == nil then
452ddfae7e3SKyle Evans		return
453ddfae7e3SKyle Evans	end
454ddfae7e3SKyle Evans
455e307eb94SToomas Soome	if nextboot_enable == nil and
456e307eb94SToomas Soome	    text:match("^nextboot_enable=\"NO\"") ~= nil then
457ddfae7e3SKyle Evans		-- We're done; nextboot is not enabled
458ddfae7e3SKyle Evans		return
459ddfae7e3SKyle Evans	end
460ddfae7e3SKyle Evans
461ddfae7e3SKyle Evans	if not config.parse(text) then
4622f3ecc87SKyle Evans		print(MSG_FAILPARSECFG:format(nextboot_file))
463ddfae7e3SKyle Evans	end
464ddfae7e3SKyle Evans
465ddfae7e3SKyle Evans	-- Attempt to rewrite the first line and only the first line of the
466ddfae7e3SKyle Evans	-- nextboot_file. We overwrite it with nextboot_enable="NO", then
46767eae503SKyle Evans	-- check for that on load.
468ddfae7e3SKyle Evans	-- It's worth noting that this won't work on every filesystem, so we
469ddfae7e3SKyle Evans	-- won't do anything notable if we have any errors in this process.
470ddfae7e3SKyle Evans	local nfile = io.open(nextboot_file, 'w')
471ddfae7e3SKyle Evans	if nfile ~= nil then
472ddfae7e3SKyle Evans		-- We need the trailing space here to account for the extra
473ddfae7e3SKyle Evans		-- character taken up by the string nextboot_enable="YES"
474ddfae7e3SKyle Evans		-- Or new end quotation mark lands on the S, and we want to
475ddfae7e3SKyle Evans		-- rewrite the entirety of the first line.
476ddfae7e3SKyle Evans		io.write(nfile, "nextboot_enable=\"NO\" ")
477ddfae7e3SKyle Evans		io.close(nfile)
478ddfae7e3SKyle Evans	end
479e307eb94SToomas Soome	loader.setenv("nextboot_enable", "NO")
480ddfae7e3SKyle Evansend
481ddfae7e3SKyle Evans
482b5746545SKyle Evans-- Module exports
483f0b03262SKyle Evansconfig.verbose = false
484b5746545SKyle Evans
48525c4c7a5SKyle Evans-- The first item in every carousel is always the default item.
48625c4c7a5SKyle Evansfunction config.getCarouselIndex(id)
4878d21763eSKyle Evans	return carousel_choices[id] or 1
48825c4c7a5SKyle Evansend
48925c4c7a5SKyle Evans
49025c4c7a5SKyle Evansfunction config.setCarouselIndex(id, idx)
491aedd6be5SKyle Evans	carousel_choices[id] = idx
49225c4c7a5SKyle Evansend
49325c4c7a5SKyle Evans
494fb7275beSKyle Evans-- Returns true if we processed the file successfully, false if we did not.
495fb7275beSKyle Evans-- If 'silent' is true, being unable to read the file is not considered a
496fb7275beSKyle Evans-- failure.
497ddfae7e3SKyle Evansfunction config.processFile(name, silent)
498062d62c9SKyle Evans	if silent == nil then
499062d62c9SKyle Evans		silent = false
500062d62c9SKyle Evans	end
501088b4f5fSWarner Losh
502322a2dddSKyle Evans	local text = readFile(name, silent)
5039f71d421SKyle Evans	if text == nil then
504fb7275beSKyle Evans		return silent
505088b4f5fSWarner Losh	end
5063dcb7648SKyle Evans
5074adde50dSKyle Evans	return config.parse(text)
5084adde50dSKyle Evansend
5094adde50dSKyle Evans
5104adde50dSKyle Evans-- silent runs will not return false if we fail to open the file
5114adde50dSKyle Evansfunction config.parse(text)
512aedd6be5SKyle Evans	local n = 1
513aedd6be5SKyle Evans	local status = true
514088b4f5fSWarner Losh
51571049173SKyle Evans	for line in text:gmatch("([^\n]+)") do
5169f71d421SKyle Evans		if line:match("^%s*$") == nil then
517e2df27e3SKyle Evans			for _, val in ipairs(pattern_table) do
518058c692eSKyle Evans				local pattern = '^%s*' .. val.str .. '%s*(.*)';
5199a16e110SKyle Evans				local cgroups = val.groups or 2
5201ee89ab5SKyle Evans				local k, v, c = checkPattern(line, pattern)
5219f71d421SKyle Evans				if k ~= nil then
5229a16e110SKyle Evans					-- Offset by one, drats
5239a16e110SKyle Evans					if cgroups == 1 then
5249a16e110SKyle Evans						c = v
5259a16e110SKyle Evans						v = nil
5269a16e110SKyle Evans					end
527088b4f5fSWarner Losh
528dbef5253SKyle Evans					if isValidComment(c) then
529aedd6be5SKyle Evans						val.process(k, v)
5309a16e110SKyle Evans						goto nextline
531088b4f5fSWarner Losh					end
532088b4f5fSWarner Losh
533aedd6be5SKyle Evans					break
534088b4f5fSWarner Losh				end
535088b4f5fSWarner Losh			end
536088b4f5fSWarner Losh
537fdabb5f5SKyle Evans			print(MSG_MALFORMED:format(n, line))
538aedd6be5SKyle Evans			status = false
539088b4f5fSWarner Losh		end
5409a16e110SKyle Evans		::nextline::
541aedd6be5SKyle Evans		n = n + 1
542088b4f5fSWarner Losh	end
543088b4f5fSWarner Losh
544aedd6be5SKyle Evans	return status
545088b4f5fSWarner Loshend
546088b4f5fSWarner Losh
5473fe0ac6aSKyle Evansfunction config.readConf(file, loaded_files)
5483fe0ac6aSKyle Evans	if loaded_files == nil then
5493fe0ac6aSKyle Evans		loaded_files = {}
5507a5c6c8bSKyle Evans	end
5517a5c6c8bSKyle Evans
5523fe0ac6aSKyle Evans	if loaded_files[file] ~= nil then
5533fe0ac6aSKyle Evans		return
5543fe0ac6aSKyle Evans	end
5553fe0ac6aSKyle Evans
55672cf7db3SKyle Evans	-- We'll process loader_conf_dirs at the top-level readConf
55772cf7db3SKyle Evans	local load_conf_dirs = next(loaded_files) == nil
5583fe0ac6aSKyle Evans	print("Loading " .. file)
5593fe0ac6aSKyle Evans
5603fe0ac6aSKyle Evans	-- The final value of loader_conf_files is not important, so just
5613fe0ac6aSKyle Evans	-- clobber it here.  We'll later check if it's no longer nil and process
5623fe0ac6aSKyle Evans	-- the new value for files to read.
5633fe0ac6aSKyle Evans	setEnv("loader_conf_files", nil)
5643fe0ac6aSKyle Evans
5657a5c6c8bSKyle Evans	-- These may or may not exist, and that's ok. Do a
5667a5c6c8bSKyle Evans	-- silent parse so that we complain on parse errors but
5677a5c6c8bSKyle Evans	-- not for them simply not existing.
5683fe0ac6aSKyle Evans	if not config.processFile(file, true) then
5693fe0ac6aSKyle Evans		print(MSG_FAILPARSECFG:format(file))
5707a5c6c8bSKyle Evans	end
5717a5c6c8bSKyle Evans
5723fe0ac6aSKyle Evans	loaded_files[file] = true
5733fe0ac6aSKyle Evans
5743fe0ac6aSKyle Evans	-- Going to process "loader_conf_files" extra-files
5753fe0ac6aSKyle Evans	local loader_conf_files = getEnv("loader_conf_files")
5763fe0ac6aSKyle Evans	if loader_conf_files ~= nil then
5773fe0ac6aSKyle Evans		for name in loader_conf_files:gmatch("[%w%p]+") do
5783fe0ac6aSKyle Evans			config.readConf(name, loaded_files)
5797a5c6c8bSKyle Evans		end
5807a5c6c8bSKyle Evans	end
58172cf7db3SKyle Evans
58272cf7db3SKyle Evans	if load_conf_dirs then
58372cf7db3SKyle Evans		local loader_conf_dirs = getEnv("loader_conf_dirs")
58472cf7db3SKyle Evans		if loader_conf_dirs ~= nil then
58572cf7db3SKyle Evans			for name in loader_conf_dirs:gmatch("[%w%p]+") do
58672cf7db3SKyle Evans				if lfs.attributes(name, "mode") ~= "directory" then
58772cf7db3SKyle Evans					print(MSG_FAILDIR:format(name))
58872cf7db3SKyle Evans					goto nextdir
58972cf7db3SKyle Evans				end
59072cf7db3SKyle Evans				for cfile in lfs.dir(name) do
59172cf7db3SKyle Evans					if cfile:match(".conf$") then
59272cf7db3SKyle Evans						local fpath = name .. "/" .. cfile
59372cf7db3SKyle Evans						if lfs.attributes(fpath, "mode") == "file" then
59472cf7db3SKyle Evans							config.readConf(fpath, loaded_files)
59572cf7db3SKyle Evans						end
59672cf7db3SKyle Evans					end
59772cf7db3SKyle Evans				end
59872cf7db3SKyle Evans				::nextdir::
59972cf7db3SKyle Evans			end
60072cf7db3SKyle Evans		end
60172cf7db3SKyle Evans	end
6027a5c6c8bSKyle Evansend
6037a5c6c8bSKyle Evans
60418c286a0SKyle Evans-- other_kernel is optionally the name of a kernel to load, if not the default
60518c286a0SKyle Evans-- or autoloaded default from the module_path
606322a2dddSKyle Evansfunction config.loadKernel(other_kernel)
607aedd6be5SKyle Evans	local flags = loader.getenv("kernel_options") or ""
608aedd6be5SKyle Evans	local kernel = other_kernel or loader.getenv("kernel")
609088b4f5fSWarner Losh
610322a2dddSKyle Evans	local function tryLoad(names)
611088b4f5fSWarner Losh		for name in names:gmatch("([^;]+)%s*;?") do
61280eb81f6SKyle Evans			local r = loader.perform("load " .. name ..
61380eb81f6SKyle Evans			     " " .. flags)
6149f71d421SKyle Evans			if r == 0 then
615aedd6be5SKyle Evans				return name
616088b4f5fSWarner Losh			end
617088b4f5fSWarner Losh		end
618aedd6be5SKyle Evans		return nil
61918c286a0SKyle Evans	end
620088b4f5fSWarner Losh
6215dd1b834SKyle Evans	local function getModulePath()
6225dd1b834SKyle Evans		local module_path = loader.getenv("module_path")
6235dd1b834SKyle Evans		local kernel_path = loader.getenv("kernel_path")
6245dd1b834SKyle Evans
6255dd1b834SKyle Evans		if kernel_path == nil then
6265dd1b834SKyle Evans			return module_path
6275dd1b834SKyle Evans		end
6285dd1b834SKyle Evans
6295dd1b834SKyle Evans		-- Strip the loaded kernel path from module_path. This currently assumes
6305dd1b834SKyle Evans		-- that the kernel path will be prepended to the module_path when it's
6315dd1b834SKyle Evans		-- found.
6325dd1b834SKyle Evans		kernel_path = escapeName(kernel_path .. ';')
6335dd1b834SKyle Evans		return module_path:gsub(kernel_path, '')
6345dd1b834SKyle Evans	end
6355dd1b834SKyle Evans
636322a2dddSKyle Evans	local function loadBootfile()
637aedd6be5SKyle Evans		local bootfile = loader.getenv("bootfile")
638088b4f5fSWarner Losh
639088b4f5fSWarner Losh		-- append default kernel name
6409f71d421SKyle Evans		if bootfile == nil then
641aedd6be5SKyle Evans			bootfile = "kernel"
642088b4f5fSWarner Losh		else
643aedd6be5SKyle Evans			bootfile = bootfile .. ";kernel"
644088b4f5fSWarner Losh		end
645088b4f5fSWarner Losh
646322a2dddSKyle Evans		return tryLoad(bootfile)
64724a1bd54SKyle Evans	end
648088b4f5fSWarner Losh
649088b4f5fSWarner Losh	-- kernel not set, try load from default module_path
6509f71d421SKyle Evans	if kernel == nil then
651322a2dddSKyle Evans		local res = loadBootfile()
652088b4f5fSWarner Losh
6539f71d421SKyle Evans		if res ~= nil then
654d4591301SKyle Evans			-- Default kernel is loaded
655aedd6be5SKyle Evans			config.kernel_loaded = nil
656aedd6be5SKyle Evans			return true
657088b4f5fSWarner Losh		else
658fdabb5f5SKyle Evans			print(MSG_DEFAULTKERNFAIL)
659aedd6be5SKyle Evans			return false
660088b4f5fSWarner Losh		end
661088b4f5fSWarner Losh	else
66215d8e03cSKyle Evans		-- Use our cached module_path, so we don't end up with multiple
66315d8e03cSKyle Evans		-- automatically added kernel paths to our final module_path
6645dd1b834SKyle Evans		local module_path = getModulePath()
665e2df27e3SKyle Evans		local res
666088b4f5fSWarner Losh
6679f71d421SKyle Evans		if other_kernel ~= nil then
668aedd6be5SKyle Evans			kernel = other_kernel
66918c286a0SKyle Evans		end
670088b4f5fSWarner Losh		-- first try load kernel with module_path = /boot/${kernel}
671088b4f5fSWarner Losh		-- then try load with module_path=${kernel}
672aedd6be5SKyle Evans		local paths = {"/boot/" .. kernel, kernel}
673088b4f5fSWarner Losh
674e2df27e3SKyle Evans		for _, v in pairs(paths) do
675aedd6be5SKyle Evans			loader.setenv("module_path", v)
676322a2dddSKyle Evans			res = loadBootfile()
677088b4f5fSWarner Losh
67815d8e03cSKyle Evans			-- succeeded, add path to module_path
6799f71d421SKyle Evans			if res ~= nil then
680aedd6be5SKyle Evans				config.kernel_loaded = kernel
6819f71d421SKyle Evans				if module_path ~= nil then
682c990f0a9SKyle Evans					loader.setenv("module_path", v .. ";" ..
683aedd6be5SKyle Evans					    module_path)
6845dd1b834SKyle Evans					loader.setenv("kernel_path", v)
685c990f0a9SKyle Evans				end
686aedd6be5SKyle Evans				return true
687088b4f5fSWarner Losh			end
688088b4f5fSWarner Losh		end
689088b4f5fSWarner Losh
690088b4f5fSWarner Losh		-- failed to load with ${kernel} as a directory
691088b4f5fSWarner Losh		-- try as a file
692322a2dddSKyle Evans		res = tryLoad(kernel)
6939f71d421SKyle Evans		if res ~= nil then
694aedd6be5SKyle Evans			config.kernel_loaded = kernel
695aedd6be5SKyle Evans			return true
696088b4f5fSWarner Losh		else
697fdabb5f5SKyle Evans			print(MSG_KERNFAIL:format(kernel))
698aedd6be5SKyle Evans			return false
699088b4f5fSWarner Losh		end
700088b4f5fSWarner Losh	end
701088b4f5fSWarner Loshend
702088b4f5fSWarner Losh
703322a2dddSKyle Evansfunction config.selectKernel(kernel)
704aedd6be5SKyle Evans	config.kernel_selected = kernel
705fa4a2394SKyle Evansend
706088b4f5fSWarner Losh
7077aba5b2fSKyle Evansfunction config.load(file, reloading)
7089f71d421SKyle Evans	if not file then
709aedd6be5SKyle Evans		file = "/boot/defaults/loader.conf"
710088b4f5fSWarner Losh	end
711088b4f5fSWarner Losh
7123fe0ac6aSKyle Evans	config.readConf(file)
713088b4f5fSWarner Losh
714322a2dddSKyle Evans	checkNextboot()
7153dcb7648SKyle Evans
7168d21763eSKyle Evans	local verbose = loader.getenv("verbose_loading") or "no"
717f0b03262SKyle Evans	config.verbose = verbose:lower() == "yes"
7187aba5b2fSKyle Evans	if not reloading then
7197aba5b2fSKyle Evans		hook.runAll("config.loaded")
7207aba5b2fSKyle Evans	end
721fa4a2394SKyle Evansend
722fa4a2394SKyle Evans
723fa4a2394SKyle Evans-- Reload configuration
724fa4a2394SKyle Evansfunction config.reload(file)
725aedd6be5SKyle Evans	modules = {}
72664c91742SKyle Evans	restoreEnv()
7277aba5b2fSKyle Evans	config.load(file, true)
728aea262bfSKyle Evans	hook.runAll("config.reloaded")
729fa4a2394SKyle Evansend
730fa4a2394SKyle Evans
731fa4a2394SKyle Evansfunction config.loadelf()
7320d7bee6aSKyle Evans	local xen_kernel = loader.getenv('xen_kernel')
733aedd6be5SKyle Evans	local kernel = config.kernel_selected or config.kernel_loaded
73479c20f22SRyan Moeller	local status
73515d8e03cSKyle Evans
7360d7bee6aSKyle Evans	if xen_kernel ~= nil then
7370d7bee6aSKyle Evans		print(MSG_XENKERNLOADING)
7380d7bee6aSKyle Evans		if cli_execute_unparsed('load ' .. xen_kernel) ~= 0 then
7390d7bee6aSKyle Evans			print(MSG_XENKERNFAIL:format(xen_kernel))
7403078173cSKyle Evans			return false
7410d7bee6aSKyle Evans		end
7420d7bee6aSKyle Evans	end
743fdabb5f5SKyle Evans	print(MSG_KERNLOADING)
74479c20f22SRyan Moeller	if not config.loadKernel(kernel) then
7453078173cSKyle Evans		return false
746fa4a2394SKyle Evans	end
74779c20f22SRyan Moeller	hook.runAll("kernel.loaded")
748088b4f5fSWarner Losh
749fdabb5f5SKyle Evans	print(MSG_MODLOADING)
7500db2ca0cSKyle Evans	status = loadModule(modules, not config.verbose)
7510db2ca0cSKyle Evans	hook.runAll("modules.loaded")
7520db2ca0cSKyle Evans	return status
753088b4f5fSWarner Loshend
754088b4f5fSWarner Losh
7554634bb1fSKyle Evansfunction config.enableModule(modname)
7564634bb1fSKyle Evans	if modules[modname] == nil then
7574634bb1fSKyle Evans		modules[modname] = {}
7584634bb1fSKyle Evans	elseif modules[modname].load == "YES" then
7594634bb1fSKyle Evans		modules[modname].force = true
7604634bb1fSKyle Evans		return true
7614634bb1fSKyle Evans	end
7624634bb1fSKyle Evans
7634634bb1fSKyle Evans	modules[modname].load = "YES"
7644634bb1fSKyle Evans	modules[modname].force = true
7654634bb1fSKyle Evans	return true
7664634bb1fSKyle Evansend
7674634bb1fSKyle Evans
7684634bb1fSKyle Evansfunction config.disableModule(modname)
7694634bb1fSKyle Evans	if modules[modname] == nil then
7704634bb1fSKyle Evans		return false
7714634bb1fSKyle Evans	elseif modules[modname].load ~= "YES" then
7724634bb1fSKyle Evans		return true
7734634bb1fSKyle Evans	end
7744634bb1fSKyle Evans
7754634bb1fSKyle Evans	modules[modname].load = "NO"
7764634bb1fSKyle Evans	modules[modname].force = nil
7774634bb1fSKyle Evans	return true
7784634bb1fSKyle Evansend
7794634bb1fSKyle Evans
7804634bb1fSKyle Evansfunction config.isModuleEnabled(modname)
7814634bb1fSKyle Evans	local mod = modules[modname]
7824634bb1fSKyle Evans	if not mod or mod.load ~= "YES" then
7834634bb1fSKyle Evans		return false
7844634bb1fSKyle Evans	end
7854634bb1fSKyle Evans
7864634bb1fSKyle Evans	if mod.force then
7874634bb1fSKyle Evans		return true
7884634bb1fSKyle Evans	end
7894634bb1fSKyle Evans
7904634bb1fSKyle Evans	local blacklist = getBlacklist()
79110aeb6cdSKyle Evans	return not blacklist[modname]
7924634bb1fSKyle Evansend
7934634bb1fSKyle Evans
7947ed84fa1SKyle Evansfunction config.getModuleInfo()
7957ed84fa1SKyle Evans	return {
7967ed84fa1SKyle Evans		modules = modules,
7977ed84fa1SKyle Evans		blacklist = getBlacklist()
7987ed84fa1SKyle Evans	}
7997ed84fa1SKyle Evansend
8007ed84fa1SKyle Evans
8017aba5b2fSKyle Evanshook.registerType("config.loaded")
802aea262bfSKyle Evanshook.registerType("config.reloaded")
8034bee6189SRyan Moellerhook.registerType("kernel.loaded")
8040db2ca0cSKyle Evanshook.registerType("modules.loaded")
805aedd6be5SKyle Evansreturn config
806