xref: /freebsd-12.1/stand/lua/config.lua (revision 94411fc3)
1088b4f5fSWarner Losh--
272e39d71SKyle Evans-- SPDX-License-Identifier: BSD-2-Clause-FreeBSD
372e39d71SKyle Evans--
4088b4f5fSWarner Losh-- Copyright (c) 2015 Pedro Souza <[email protected]>
521d5bcbeSKyle 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
42fdabb5f5SKyle Evanslocal MSG_FAILEXEC = "Failed to exec '%s'"
43fdabb5f5SKyle Evanslocal MSG_FAILSETENV = "Failed to '%s' with value: %s"
44fdabb5f5SKyle Evanslocal MSG_FAILOPENCFG = "Failed to open config: '%s'"
45fdabb5f5SKyle Evanslocal MSG_FAILREADCFG = "Failed to read config: '%s'"
46fdabb5f5SKyle Evanslocal MSG_FAILPARSECFG = "Failed to parse config: '%s'"
47fdabb5f5SKyle Evanslocal MSG_FAILEXBEF = "Failed to execute '%s' before loading '%s'"
48fdabb5f5SKyle Evanslocal MSG_FAILEXAF = "Failed to execute '%s' after loading '%s'"
49fdabb5f5SKyle Evanslocal MSG_MALFORMED = "Malformed line (%d):\n\t'%s'"
50fdabb5f5SKyle Evanslocal MSG_DEFAULTKERNFAIL = "No kernel set, failed to load from module_path"
51fdabb5f5SKyle Evanslocal MSG_KERNFAIL = "Failed to load kernel '%s'"
520d7bee6aSKyle Evanslocal MSG_XENKERNFAIL = "Failed to load Xen kernel '%s'"
530d7bee6aSKyle Evanslocal MSG_XENKERNLOADING = "Loading Xen kernel..."
54fdabb5f5SKyle Evanslocal MSG_KERNLOADING = "Loading kernel..."
55fdabb5f5SKyle Evanslocal MSG_MODLOADING = "Loading configured modules..."
56532dc172SKyle Evanslocal MSG_MODBLACKLIST = "Not loading blacklisted module '%s'"
57fdabb5f5SKyle Evans
58058c692eSKyle Evanslocal MODULEEXPR = '([%w-_]+)'
591ee89ab5SKyle Evanslocal QVALEXPR = "\"([%w%s%p]-)\""
601ee89ab5SKyle Evanslocal QVALREPL = QVALEXPR:gsub('%%', '%%%%')
611ee89ab5SKyle Evanslocal WORDEXPR = "([%w]+)"
621ee89ab5SKyle Evanslocal WORDREPL = WORDEXPR:gsub('%%', '%%%%')
63058c692eSKyle Evans
6464c91742SKyle Evanslocal function restoreEnv()
6564c91742SKyle Evans	-- Examine changed environment variables
6664c91742SKyle Evans	for k, v in pairs(env_changed) do
6764c91742SKyle Evans		local restore_value = env_restore[k]
6864c91742SKyle Evans		if restore_value == nil then
6964c91742SKyle Evans			-- This one doesn't need restored for some reason
7064c91742SKyle Evans			goto continue
7164c91742SKyle Evans		end
7264c91742SKyle Evans		local current_value = loader.getenv(k)
7364c91742SKyle Evans		if current_value ~= v then
7464c91742SKyle Evans			-- This was overwritten by some action taken on the menu
7564c91742SKyle Evans			-- most likely; we'll leave it be.
7664c91742SKyle Evans			goto continue
7764c91742SKyle Evans		end
7864c91742SKyle Evans		restore_value = restore_value.value
7964c91742SKyle Evans		if restore_value ~= nil then
8064c91742SKyle Evans			loader.setenv(k, restore_value)
8164c91742SKyle Evans		else
8264c91742SKyle Evans			loader.unsetenv(k)
8364c91742SKyle Evans		end
8464c91742SKyle Evans		::continue::
8564c91742SKyle Evans	end
8664c91742SKyle Evans
8764c91742SKyle Evans	env_changed = {}
8864c91742SKyle Evans	env_restore = {}
8964c91742SKyle Evansend
9064c91742SKyle Evans
9164c91742SKyle Evanslocal function setEnv(key, value)
9264c91742SKyle Evans	-- Track the original value for this if we haven't already
9364c91742SKyle Evans	if env_restore[key] == nil then
9464c91742SKyle Evans		env_restore[key] = {value = loader.getenv(key)}
9564c91742SKyle Evans	end
9664c91742SKyle Evans
9764c91742SKyle Evans	env_changed[key] = value
9864c91742SKyle Evans
9964c91742SKyle Evans	return loader.setenv(key, value)
10064c91742SKyle Evansend
10164c91742SKyle Evans
102dbef5253SKyle Evans-- name here is one of 'name', 'type', flags', 'before', 'after', or 'error.'
103dbef5253SKyle Evans-- These are set from lines in loader.conf(5): ${key}_${name}="${value}" where
104dbef5253SKyle Evans-- ${key} is a module name.
105dbef5253SKyle Evanslocal function setKey(key, name, value)
106dbef5253SKyle Evans	if modules[key] == nil then
107dbef5253SKyle Evans		modules[key] = {}
108dbef5253SKyle Evans	end
109dbef5253SKyle Evans	modules[key][name] = value
110dbef5253SKyle Evansend
111dbef5253SKyle Evans
112deb8c8f5SKyle Evans-- Escapes the named value for use as a literal in a replacement pattern.
113deb8c8f5SKyle Evans-- e.g. dhcp.host-name gets turned into dhcp%.host%-name to remove the special
114deb8c8f5SKyle Evans-- meaning.
115deb8c8f5SKyle Evanslocal function escapeName(name)
116deb8c8f5SKyle Evans	return name:gsub("([%p])", "%%%1")
117deb8c8f5SKyle Evansend
118deb8c8f5SKyle Evans
119deb8c8f5SKyle Evanslocal function processEnvVar(value)
120deb8c8f5SKyle Evans	for name in value:gmatch("${([^}]+)}") do
121deb8c8f5SKyle Evans		local replacement = loader.getenv(name) or ""
122deb8c8f5SKyle Evans		value = value:gsub("${" .. escapeName(name) .. "}", replacement)
123deb8c8f5SKyle Evans	end
124deb8c8f5SKyle Evans	for name in value:gmatch("$([%w%p]+)%s*") do
125deb8c8f5SKyle Evans		local replacement = loader.getenv(name) or ""
126deb8c8f5SKyle Evans		value = value:gsub("$" .. escapeName(name), replacement)
127deb8c8f5SKyle Evans	end
128deb8c8f5SKyle Evans	return value
129deb8c8f5SKyle Evansend
130deb8c8f5SKyle Evans
1311ee89ab5SKyle Evanslocal function checkPattern(line, pattern)
1321ee89ab5SKyle Evans	local function _realCheck(_line, _pattern)
1331ee89ab5SKyle Evans		return _line:match(_pattern)
1341ee89ab5SKyle Evans	end
1351ee89ab5SKyle Evans
1361ee89ab5SKyle Evans	if pattern:find('$VALUE') then
1371ee89ab5SKyle Evans		local k, v, c
1381ee89ab5SKyle Evans		k, v, c = _realCheck(line, pattern:gsub('$VALUE', QVALREPL))
1391ee89ab5SKyle Evans		if k ~= nil then
1401ee89ab5SKyle Evans			return k,v, c
1411ee89ab5SKyle Evans		end
1421ee89ab5SKyle Evans		return _realCheck(line, pattern:gsub('$VALUE', WORDREPL))
1431ee89ab5SKyle Evans	else
1441ee89ab5SKyle Evans		return _realCheck(line, pattern)
1451ee89ab5SKyle Evans	end
1461ee89ab5SKyle Evansend
1471ee89ab5SKyle Evans
148058c692eSKyle Evans-- str in this table is a regex pattern.  It will automatically be anchored to
149058c692eSKyle Evans-- the beginning of a line and any preceding whitespace will be skipped.  The
150058c692eSKyle Evans-- pattern should have no more than two captures patterns, which correspond to
151058c692eSKyle Evans-- the two parameters (usually 'key' and 'value') that are passed to the
1521ee89ab5SKyle Evans-- process function.  All trailing characters will be validated.  Any $VALUE
1531ee89ab5SKyle Evans-- token included in a pattern will be tried first with a quoted value capture
1541ee89ab5SKyle Evans-- group, then a single-word value capture group.  This is our kludge for Lua
1551ee89ab5SKyle Evans-- regex not supporting branching.
1569a16e110SKyle Evans--
1579a16e110SKyle Evans-- We have two special entries in this table: the first is the first entry,
1589a16e110SKyle Evans-- a full-line comment.  The second is for 'exec' handling.  Both have a single
1599a16e110SKyle Evans-- capture group, but the difference is that the full-line comment pattern will
1609a16e110SKyle Evans-- match the entire line.  This does not run afoul of the later end of line
1619a16e110SKyle Evans-- validation that we'll do after a match.  However, the 'exec' pattern will.
1629a16e110SKyle Evans-- We document the exceptions with a special 'groups' index that indicates
1639a16e110SKyle Evans-- the number of capture groups, if not two.  We'll use this later to do
1649a16e110SKyle Evans-- validation on the proper entry.
1651ee89ab5SKyle Evans--
166e1a8835aSKyle Evanslocal pattern_table = {
167e1a8835aSKyle Evans	{
168058c692eSKyle Evans		str = "(#.*)",
169e1a8835aSKyle Evans		process = function(_, _)  end,
1709a16e110SKyle Evans		groups = 1,
171088b4f5fSWarner Losh	},
172088b4f5fSWarner Losh	--  module_load="value"
173e1a8835aSKyle Evans	{
1741ee89ab5SKyle Evans		str = MODULEEXPR .. "_load%s*=%s*$VALUE",
175088b4f5fSWarner Losh		process = function(k, v)
1769f71d421SKyle Evans			if modules[k] == nil then
177aedd6be5SKyle Evans				modules[k] = {}
178088b4f5fSWarner Losh			end
179aedd6be5SKyle Evans			modules[k].load = v:upper()
180e1a8835aSKyle Evans		end,
181088b4f5fSWarner Losh	},
182088b4f5fSWarner Losh	--  module_name="value"
183e1a8835aSKyle Evans	{
1841ee89ab5SKyle Evans		str = MODULEEXPR .. "_name%s*=%s*$VALUE",
185088b4f5fSWarner Losh		process = function(k, v)
186dbef5253SKyle Evans			setKey(k, "name", v)
187e1a8835aSKyle Evans		end,
188088b4f5fSWarner Losh	},
189088b4f5fSWarner Losh	--  module_type="value"
190e1a8835aSKyle Evans	{
1911ee89ab5SKyle Evans		str = MODULEEXPR .. "_type%s*=%s*$VALUE",
192088b4f5fSWarner Losh		process = function(k, v)
193dbef5253SKyle Evans			setKey(k, "type", v)
194e1a8835aSKyle Evans		end,
195088b4f5fSWarner Losh	},
196088b4f5fSWarner Losh	--  module_flags="value"
197e1a8835aSKyle Evans	{
1981ee89ab5SKyle Evans		str = MODULEEXPR .. "_flags%s*=%s*$VALUE",
199088b4f5fSWarner Losh		process = function(k, v)
200dbef5253SKyle Evans			setKey(k, "flags", v)
201e1a8835aSKyle Evans		end,
202088b4f5fSWarner Losh	},
203088b4f5fSWarner Losh	--  module_before="value"
204e1a8835aSKyle Evans	{
2051ee89ab5SKyle Evans		str = MODULEEXPR .. "_before%s*=%s*$VALUE",
206088b4f5fSWarner Losh		process = function(k, v)
207dbef5253SKyle Evans			setKey(k, "before", v)
208e1a8835aSKyle Evans		end,
209088b4f5fSWarner Losh	},
210088b4f5fSWarner Losh	--  module_after="value"
211e1a8835aSKyle Evans	{
2121ee89ab5SKyle Evans		str = MODULEEXPR .. "_after%s*=%s*$VALUE",
213088b4f5fSWarner Losh		process = function(k, v)
214dbef5253SKyle Evans			setKey(k, "after", v)
215e1a8835aSKyle Evans		end,
216088b4f5fSWarner Losh	},
217088b4f5fSWarner Losh	--  module_error="value"
218e1a8835aSKyle Evans	{
2191ee89ab5SKyle Evans		str = MODULEEXPR .. "_error%s*=%s*$VALUE",
220088b4f5fSWarner Losh		process = function(k, v)
221dbef5253SKyle Evans			setKey(k, "error", v)
222e1a8835aSKyle Evans		end,
223088b4f5fSWarner Losh	},
224088b4f5fSWarner Losh	--  exec="command"
225e1a8835aSKyle Evans	{
2261ee89ab5SKyle Evans		str = "exec%s*=%s*" .. QVALEXPR,
227e2df27e3SKyle Evans		process = function(k, _)
2289ab2d3c5SKyle Evans			if cli_execute_unparsed(k) ~= 0 then
229fdabb5f5SKyle Evans				print(MSG_FAILEXEC:format(k))
230088b4f5fSWarner Losh			end
231e1a8835aSKyle Evans		end,
2329a16e110SKyle Evans		groups = 1,
233088b4f5fSWarner Losh	},
234088b4f5fSWarner Losh	--  env_var="value"
235e1a8835aSKyle Evans	{
2361ee89ab5SKyle Evans		str = "([%w%p]+)%s*=%s*$VALUE",
237088b4f5fSWarner Losh		process = function(k, v)
238deb8c8f5SKyle Evans			if setEnv(k, processEnvVar(v)) ~= 0 then
239fdabb5f5SKyle Evans				print(MSG_FAILSETENV:format(k, v))
240088b4f5fSWarner Losh			end
241e1a8835aSKyle Evans		end,
242088b4f5fSWarner Losh	},
243088b4f5fSWarner Losh	--  env_var=num
244e1a8835aSKyle Evans	{
245058c692eSKyle Evans		str = "([%w%p]+)%s*=%s*(-?%d+)",
246088b4f5fSWarner Losh		process = function(k, v)
247deb8c8f5SKyle Evans			if setEnv(k, processEnvVar(v)) ~= 0 then
248fdabb5f5SKyle Evans				print(MSG_FAILSETENV:format(k, tostring(v)))
249088b4f5fSWarner Losh			end
250e1a8835aSKyle Evans		end,
251e1a8835aSKyle Evans	},
252aedd6be5SKyle Evans}
253088b4f5fSWarner Losh
254dbef5253SKyle Evanslocal function isValidComment(line)
255dbef5253SKyle Evans	if line ~= nil then
256dbef5253SKyle Evans		local s = line:match("^%s*#.*")
257dbef5253SKyle Evans		if s == nil then
258dbef5253SKyle Evans			s = line:match("^%s*$")
259dbef5253SKyle Evans		end
260dbef5253SKyle Evans		if s == nil then
261dbef5253SKyle Evans			return false
262dbef5253SKyle Evans		end
263dbef5253SKyle Evans	end
264dbef5253SKyle Evans	return true
265dbef5253SKyle Evansend
266dbef5253SKyle Evans
267532dc172SKyle Evanslocal function getBlacklist()
268*fdc0a80dSKyle Evans	local blacklist = {}
269532dc172SKyle Evans	local blacklist_str = loader.getenv('module_blacklist')
270532dc172SKyle Evans	if blacklist_str == nil then
271*fdc0a80dSKyle Evans		return blacklist
272532dc172SKyle Evans	end
273532dc172SKyle Evans
274532dc172SKyle Evans	for mod in blacklist_str:gmatch("[;, ]?([%w-_]+)[;, ]?") do
275532dc172SKyle Evans		blacklist[mod] = true
276532dc172SKyle Evans	end
277532dc172SKyle Evans	return blacklist
278532dc172SKyle Evansend
279532dc172SKyle Evans
280dbef5253SKyle Evanslocal function loadModule(mod, silent)
281dbef5253SKyle Evans	local status = true
282532dc172SKyle Evans	local blacklist = getBlacklist()
283dbef5253SKyle Evans	local pstatus
284dbef5253SKyle Evans	for k, v in pairs(mod) do
2858d21763eSKyle Evans		if v.load ~= nil and v.load:lower() == "yes" then
286532dc172SKyle Evans			local module_name = v.name or k
287532dc172SKyle Evans			if blacklist[module_name] ~= nil then
288532dc172SKyle Evans				if not silent then
289532dc172SKyle Evans					print(MSG_MODBLACKLIST:format(module_name))
290532dc172SKyle Evans				end
291532dc172SKyle Evans				goto continue
292532dc172SKyle Evans			end
293*fdc0a80dSKyle Evans			if not silent then
294*fdc0a80dSKyle Evans				loader.printc(module_name .. "...")
295*fdc0a80dSKyle Evans			end
296dbef5253SKyle Evans			local str = "load "
297dbef5253SKyle Evans			if v.type ~= nil then
298dbef5253SKyle Evans				str = str .. "-t " .. v.type .. " "
299dbef5253SKyle Evans			end
300532dc172SKyle Evans			str = str .. module_name
30135beb928SKyle Evans			if v.flags ~= nil then
30235beb928SKyle Evans				str = str .. " " .. v.flags
30335beb928SKyle Evans			end
304dbef5253SKyle Evans			if v.before ~= nil then
305dbef5253SKyle Evans				pstatus = cli_execute_unparsed(v.before) == 0
306dbef5253SKyle Evans				if not pstatus and not silent then
307dbef5253SKyle Evans					print(MSG_FAILEXBEF:format(v.before, k))
308dbef5253SKyle Evans				end
309dbef5253SKyle Evans				status = status and pstatus
310dbef5253SKyle Evans			end
311dbef5253SKyle Evans
312dbef5253SKyle Evans			if cli_execute_unparsed(str) ~= 0 then
313*fdc0a80dSKyle Evans				-- XXX Temporary shim: don't break the boot if
314*fdc0a80dSKyle Evans				-- loader hadn't been recompiled with this
315*fdc0a80dSKyle Evans				-- function exposed.
316*fdc0a80dSKyle Evans				if loader.command_error then
317*fdc0a80dSKyle Evans					print(loader.command_error())
318*fdc0a80dSKyle Evans				end
319dbef5253SKyle Evans				if not silent then
320*fdc0a80dSKyle Evans					print("failed!")
321dbef5253SKyle Evans				end
322dbef5253SKyle Evans				if v.error ~= nil then
323dbef5253SKyle Evans					cli_execute_unparsed(v.error)
324dbef5253SKyle Evans				end
325dbef5253SKyle Evans				status = false
326*fdc0a80dSKyle Evans			elseif v.after ~= nil then
327dbef5253SKyle Evans				pstatus = cli_execute_unparsed(v.after) == 0
328dbef5253SKyle Evans				if not pstatus and not silent then
329dbef5253SKyle Evans					print(MSG_FAILEXAF:format(v.after, k))
330dbef5253SKyle Evans				end
331*fdc0a80dSKyle Evans				if not silent then
332*fdc0a80dSKyle Evans					print("ok")
333*fdc0a80dSKyle Evans				end
334dbef5253SKyle Evans				status = status and pstatus
335dbef5253SKyle Evans			end
336dbef5253SKyle Evans		end
337532dc172SKyle Evans		::continue::
338dbef5253SKyle Evans	end
339dbef5253SKyle Evans
340dbef5253SKyle Evans	return status
341dbef5253SKyle Evansend
342dbef5253SKyle Evans
3434072dcb3SKyle Evanslocal function readConfFiles(loaded_files)
3444072dcb3SKyle Evans	local f = loader.getenv("loader_conf_files")
3454072dcb3SKyle Evans	if f ~= nil then
3464072dcb3SKyle Evans		for name in f:gmatch("([%w%p]+)%s*") do
3474072dcb3SKyle Evans			if loaded_files[name] ~= nil then
3484072dcb3SKyle Evans				goto continue
3494072dcb3SKyle Evans			end
3504072dcb3SKyle Evans
3514072dcb3SKyle Evans			local prefiles = loader.getenv("loader_conf_files")
3524072dcb3SKyle Evans
3534072dcb3SKyle Evans			print("Loading " .. name)
3544072dcb3SKyle Evans			-- These may or may not exist, and that's ok. Do a
3554072dcb3SKyle Evans			-- silent parse so that we complain on parse errors but
3564072dcb3SKyle Evans			-- not for them simply not existing.
3574072dcb3SKyle Evans			if not config.processFile(name, true) then
3584072dcb3SKyle Evans				print(MSG_FAILPARSECFG:format(name))
3594072dcb3SKyle Evans			end
3604072dcb3SKyle Evans
3614072dcb3SKyle Evans			loaded_files[name] = true
3624072dcb3SKyle Evans			local newfiles = loader.getenv("loader_conf_files")
3634072dcb3SKyle Evans			if prefiles ~= newfiles then
3644072dcb3SKyle Evans				readConfFiles(loaded_files)
3654072dcb3SKyle Evans			end
3664072dcb3SKyle Evans			::continue::
3674072dcb3SKyle Evans		end
3684072dcb3SKyle Evans	end
3694072dcb3SKyle Evansend
370dbef5253SKyle Evans
371322a2dddSKyle Evanslocal function readFile(name, silent)
372164b58fdSKyle Evans	local f = io.open(name)
373164b58fdSKyle Evans	if f == nil then
374164b58fdSKyle Evans		if not silent then
375fdabb5f5SKyle Evans			print(MSG_FAILOPENCFG:format(name))
376164b58fdSKyle Evans		end
377164b58fdSKyle Evans		return nil
378164b58fdSKyle Evans	end
379164b58fdSKyle Evans
380164b58fdSKyle Evans	local text, _ = io.read(f)
381164b58fdSKyle Evans	-- We might have read in the whole file, this won't be needed any more.
382164b58fdSKyle Evans	io.close(f)
383164b58fdSKyle Evans
3848d21763eSKyle Evans	if text == nil and not silent then
385fdabb5f5SKyle Evans		print(MSG_FAILREADCFG:format(name))
386164b58fdSKyle Evans	end
387164b58fdSKyle Evans	return text
388164b58fdSKyle Evansend
389164b58fdSKyle Evans
390322a2dddSKyle Evanslocal function checkNextboot()
391be2050daSKyle Evans	local nextboot_file = loader.getenv("nextboot_conf")
392ddfae7e3SKyle Evans	if nextboot_file == nil then
393ddfae7e3SKyle Evans		return
394ddfae7e3SKyle Evans	end
395ddfae7e3SKyle Evans
396322a2dddSKyle Evans	local text = readFile(nextboot_file, true)
397ddfae7e3SKyle Evans	if text == nil then
398ddfae7e3SKyle Evans		return
399ddfae7e3SKyle Evans	end
400ddfae7e3SKyle Evans
401ddfae7e3SKyle Evans	if text:match("^nextboot_enable=\"NO\"") ~= nil then
402ddfae7e3SKyle Evans		-- We're done; nextboot is not enabled
403ddfae7e3SKyle Evans		return
404ddfae7e3SKyle Evans	end
405ddfae7e3SKyle Evans
406ddfae7e3SKyle Evans	if not config.parse(text) then
4072f3ecc87SKyle Evans		print(MSG_FAILPARSECFG:format(nextboot_file))
408ddfae7e3SKyle Evans	end
409ddfae7e3SKyle Evans
410ddfae7e3SKyle Evans	-- Attempt to rewrite the first line and only the first line of the
411ddfae7e3SKyle Evans	-- nextboot_file. We overwrite it with nextboot_enable="NO", then
41267eae503SKyle Evans	-- check for that on load.
413ddfae7e3SKyle Evans	-- It's worth noting that this won't work on every filesystem, so we
414ddfae7e3SKyle Evans	-- won't do anything notable if we have any errors in this process.
415ddfae7e3SKyle Evans	local nfile = io.open(nextboot_file, 'w')
416ddfae7e3SKyle Evans	if nfile ~= nil then
417ddfae7e3SKyle Evans		-- We need the trailing space here to account for the extra
418ddfae7e3SKyle Evans		-- character taken up by the string nextboot_enable="YES"
419ddfae7e3SKyle Evans		-- Or new end quotation mark lands on the S, and we want to
420ddfae7e3SKyle Evans		-- rewrite the entirety of the first line.
421ddfae7e3SKyle Evans		io.write(nfile, "nextboot_enable=\"NO\" ")
422ddfae7e3SKyle Evans		io.close(nfile)
423ddfae7e3SKyle Evans	end
424ddfae7e3SKyle Evansend
425ddfae7e3SKyle Evans
426b5746545SKyle Evans-- Module exports
427f0b03262SKyle Evansconfig.verbose = false
428b5746545SKyle Evans
42925c4c7a5SKyle Evans-- The first item in every carousel is always the default item.
43025c4c7a5SKyle Evansfunction config.getCarouselIndex(id)
4318d21763eSKyle Evans	return carousel_choices[id] or 1
43225c4c7a5SKyle Evansend
43325c4c7a5SKyle Evans
43425c4c7a5SKyle Evansfunction config.setCarouselIndex(id, idx)
435aedd6be5SKyle Evans	carousel_choices[id] = idx
43625c4c7a5SKyle Evansend
43725c4c7a5SKyle Evans
438fb7275beSKyle Evans-- Returns true if we processed the file successfully, false if we did not.
439fb7275beSKyle Evans-- If 'silent' is true, being unable to read the file is not considered a
440fb7275beSKyle Evans-- failure.
441ddfae7e3SKyle Evansfunction config.processFile(name, silent)
442062d62c9SKyle Evans	if silent == nil then
443062d62c9SKyle Evans		silent = false
444062d62c9SKyle Evans	end
445088b4f5fSWarner Losh
446322a2dddSKyle Evans	local text = readFile(name, silent)
4479f71d421SKyle Evans	if text == nil then
448fb7275beSKyle Evans		return silent
449088b4f5fSWarner Losh	end
4503dcb7648SKyle Evans
4514adde50dSKyle Evans	return config.parse(text)
4524adde50dSKyle Evansend
4534adde50dSKyle Evans
4544adde50dSKyle Evans-- silent runs will not return false if we fail to open the file
4554adde50dSKyle Evansfunction config.parse(text)
456aedd6be5SKyle Evans	local n = 1
457aedd6be5SKyle Evans	local status = true
458088b4f5fSWarner Losh
45971049173SKyle Evans	for line in text:gmatch("([^\n]+)") do
4609f71d421SKyle Evans		if line:match("^%s*$") == nil then
461e2df27e3SKyle Evans			for _, val in ipairs(pattern_table) do
462058c692eSKyle Evans				local pattern = '^%s*' .. val.str .. '%s*(.*)';
4639a16e110SKyle Evans				local cgroups = val.groups or 2
4641ee89ab5SKyle Evans				local k, v, c = checkPattern(line, pattern)
4659f71d421SKyle Evans				if k ~= nil then
4669a16e110SKyle Evans					-- Offset by one, drats
4679a16e110SKyle Evans					if cgroups == 1 then
4689a16e110SKyle Evans						c = v
4699a16e110SKyle Evans						v = nil
4709a16e110SKyle Evans					end
471088b4f5fSWarner Losh
472dbef5253SKyle Evans					if isValidComment(c) then
473aedd6be5SKyle Evans						val.process(k, v)
4749a16e110SKyle Evans						goto nextline
475088b4f5fSWarner Losh					end
476088b4f5fSWarner Losh
477aedd6be5SKyle Evans					break
478088b4f5fSWarner Losh				end
479088b4f5fSWarner Losh			end
480088b4f5fSWarner Losh
481fdabb5f5SKyle Evans			print(MSG_MALFORMED:format(n, line))
482aedd6be5SKyle Evans			status = false
483088b4f5fSWarner Losh		end
4849a16e110SKyle Evans		::nextline::
485aedd6be5SKyle Evans		n = n + 1
486088b4f5fSWarner Losh	end
487088b4f5fSWarner Losh
488aedd6be5SKyle Evans	return status
489088b4f5fSWarner Loshend
490088b4f5fSWarner Losh
49118c286a0SKyle Evans-- other_kernel is optionally the name of a kernel to load, if not the default
49218c286a0SKyle Evans-- or autoloaded default from the module_path
493322a2dddSKyle Evansfunction config.loadKernel(other_kernel)
494aedd6be5SKyle Evans	local flags = loader.getenv("kernel_options") or ""
495aedd6be5SKyle Evans	local kernel = other_kernel or loader.getenv("kernel")
496088b4f5fSWarner Losh
497322a2dddSKyle Evans	local function tryLoad(names)
498088b4f5fSWarner Losh		for name in names:gmatch("([^;]+)%s*;?") do
49980eb81f6SKyle Evans			local r = loader.perform("load " .. name ..
50080eb81f6SKyle Evans			     " " .. flags)
5019f71d421SKyle Evans			if r == 0 then
502aedd6be5SKyle Evans				return name
503088b4f5fSWarner Losh			end
504088b4f5fSWarner Losh		end
505aedd6be5SKyle Evans		return nil
50618c286a0SKyle Evans	end
507088b4f5fSWarner Losh
5085dd1b834SKyle Evans	local function getModulePath()
5095dd1b834SKyle Evans		local module_path = loader.getenv("module_path")
5105dd1b834SKyle Evans		local kernel_path = loader.getenv("kernel_path")
5115dd1b834SKyle Evans
5125dd1b834SKyle Evans		if kernel_path == nil then
5135dd1b834SKyle Evans			return module_path
5145dd1b834SKyle Evans		end
5155dd1b834SKyle Evans
5165dd1b834SKyle Evans		-- Strip the loaded kernel path from module_path. This currently assumes
5175dd1b834SKyle Evans		-- that the kernel path will be prepended to the module_path when it's
5185dd1b834SKyle Evans		-- found.
5195dd1b834SKyle Evans		kernel_path = escapeName(kernel_path .. ';')
5205dd1b834SKyle Evans		return module_path:gsub(kernel_path, '')
5215dd1b834SKyle Evans	end
5225dd1b834SKyle Evans
523322a2dddSKyle Evans	local function loadBootfile()
524aedd6be5SKyle Evans		local bootfile = loader.getenv("bootfile")
525088b4f5fSWarner Losh
526088b4f5fSWarner Losh		-- append default kernel name
5279f71d421SKyle Evans		if bootfile == nil then
528aedd6be5SKyle Evans			bootfile = "kernel"
529088b4f5fSWarner Losh		else
530aedd6be5SKyle Evans			bootfile = bootfile .. ";kernel"
531088b4f5fSWarner Losh		end
532088b4f5fSWarner Losh
533322a2dddSKyle Evans		return tryLoad(bootfile)
53424a1bd54SKyle Evans	end
535088b4f5fSWarner Losh
536088b4f5fSWarner Losh	-- kernel not set, try load from default module_path
5379f71d421SKyle Evans	if kernel == nil then
538322a2dddSKyle Evans		local res = loadBootfile()
539088b4f5fSWarner Losh
5409f71d421SKyle Evans		if res ~= nil then
541d4591301SKyle Evans			-- Default kernel is loaded
542aedd6be5SKyle Evans			config.kernel_loaded = nil
543aedd6be5SKyle Evans			return true
544088b4f5fSWarner Losh		else
545fdabb5f5SKyle Evans			print(MSG_DEFAULTKERNFAIL)
546aedd6be5SKyle Evans			return false
547088b4f5fSWarner Losh		end
548088b4f5fSWarner Losh	else
54915d8e03cSKyle Evans		-- Use our cached module_path, so we don't end up with multiple
55015d8e03cSKyle Evans		-- automatically added kernel paths to our final module_path
5515dd1b834SKyle Evans		local module_path = getModulePath()
552e2df27e3SKyle Evans		local res
553088b4f5fSWarner Losh
5549f71d421SKyle Evans		if other_kernel ~= nil then
555aedd6be5SKyle Evans			kernel = other_kernel
55618c286a0SKyle Evans		end
557088b4f5fSWarner Losh		-- first try load kernel with module_path = /boot/${kernel}
558088b4f5fSWarner Losh		-- then try load with module_path=${kernel}
559aedd6be5SKyle Evans		local paths = {"/boot/" .. kernel, kernel}
560088b4f5fSWarner Losh
561e2df27e3SKyle Evans		for _, v in pairs(paths) do
562aedd6be5SKyle Evans			loader.setenv("module_path", v)
563322a2dddSKyle Evans			res = loadBootfile()
564088b4f5fSWarner Losh
56515d8e03cSKyle Evans			-- succeeded, add path to module_path
5669f71d421SKyle Evans			if res ~= nil then
567aedd6be5SKyle Evans				config.kernel_loaded = kernel
5689f71d421SKyle Evans				if module_path ~= nil then
569c990f0a9SKyle Evans					loader.setenv("module_path", v .. ";" ..
570aedd6be5SKyle Evans					    module_path)
5715dd1b834SKyle Evans					loader.setenv("kernel_path", v)
572c990f0a9SKyle Evans				end
573aedd6be5SKyle Evans				return true
574088b4f5fSWarner Losh			end
575088b4f5fSWarner Losh		end
576088b4f5fSWarner Losh
577088b4f5fSWarner Losh		-- failed to load with ${kernel} as a directory
578088b4f5fSWarner Losh		-- try as a file
579322a2dddSKyle Evans		res = tryLoad(kernel)
5809f71d421SKyle Evans		if res ~= nil then
581aedd6be5SKyle Evans			config.kernel_loaded = kernel
582aedd6be5SKyle Evans			return true
583088b4f5fSWarner Losh		else
584fdabb5f5SKyle Evans			print(MSG_KERNFAIL:format(kernel))
585aedd6be5SKyle Evans			return false
586088b4f5fSWarner Losh		end
587088b4f5fSWarner Losh	end
588088b4f5fSWarner Loshend
589088b4f5fSWarner Losh
590322a2dddSKyle Evansfunction config.selectKernel(kernel)
591aedd6be5SKyle Evans	config.kernel_selected = kernel
592fa4a2394SKyle Evansend
593088b4f5fSWarner Losh
5947aba5b2fSKyle Evansfunction config.load(file, reloading)
5959f71d421SKyle Evans	if not file then
596aedd6be5SKyle Evans		file = "/boot/defaults/loader.conf"
597088b4f5fSWarner Losh	end
598088b4f5fSWarner Losh
5994adde50dSKyle Evans	if not config.processFile(file) then
600fdabb5f5SKyle Evans		print(MSG_FAILPARSECFG:format(file))
601088b4f5fSWarner Losh	end
602088b4f5fSWarner Losh
6034072dcb3SKyle Evans	local loaded_files = {file = true}
6044072dcb3SKyle Evans	readConfFiles(loaded_files)
605088b4f5fSWarner Losh
606322a2dddSKyle Evans	checkNextboot()
6073dcb7648SKyle Evans
6088d21763eSKyle Evans	local verbose = loader.getenv("verbose_loading") or "no"
609f0b03262SKyle Evans	config.verbose = verbose:lower() == "yes"
6107aba5b2fSKyle Evans	if not reloading then
6117aba5b2fSKyle Evans		hook.runAll("config.loaded")
6127aba5b2fSKyle Evans	end
613fa4a2394SKyle Evansend
614fa4a2394SKyle Evans
615fa4a2394SKyle Evans-- Reload configuration
616fa4a2394SKyle Evansfunction config.reload(file)
617aedd6be5SKyle Evans	modules = {}
61864c91742SKyle Evans	restoreEnv()
6197aba5b2fSKyle Evans	config.load(file, true)
620aea262bfSKyle Evans	hook.runAll("config.reloaded")
621fa4a2394SKyle Evansend
622fa4a2394SKyle Evans
623fa4a2394SKyle Evansfunction config.loadelf()
6240d7bee6aSKyle Evans	local xen_kernel = loader.getenv('xen_kernel')
625aedd6be5SKyle Evans	local kernel = config.kernel_selected or config.kernel_loaded
626e2df27e3SKyle Evans	local loaded
62715d8e03cSKyle Evans
6280d7bee6aSKyle Evans	if xen_kernel ~= nil then
6290d7bee6aSKyle Evans		print(MSG_XENKERNLOADING)
6300d7bee6aSKyle Evans		if cli_execute_unparsed('load ' .. xen_kernel) ~= 0 then
6310d7bee6aSKyle Evans			print(MSG_XENKERNFAIL:format(xen_kernel))
632*fdc0a80dSKyle Evans			return false
6330d7bee6aSKyle Evans		end
6340d7bee6aSKyle Evans	end
635fdabb5f5SKyle Evans	print(MSG_KERNLOADING)
636322a2dddSKyle Evans	loaded = config.loadKernel(kernel)
637fa4a2394SKyle Evans
6389f71d421SKyle Evans	if not loaded then
639*fdc0a80dSKyle Evans		return false
640fa4a2394SKyle Evans	end
641088b4f5fSWarner Losh
642fdabb5f5SKyle Evans	print(MSG_MODLOADING)
643*fdc0a80dSKyle Evans	return loadModule(modules, not config.verbose)
644088b4f5fSWarner Loshend
645088b4f5fSWarner Losh
6467aba5b2fSKyle Evanshook.registerType("config.loaded")
647aea262bfSKyle Evanshook.registerType("config.reloaded")
648aedd6be5SKyle Evansreturn config
649