xref: /freebsd-12.1/stand/lua/config.lua (revision 94b8a54a)
1--
2-- Copyright (c) 2015 Pedro Souza <[email protected]>
3-- Copyright (C) 2018 Kyle Evans <[email protected]>
4-- All rights reserved.
5--
6-- Redistribution and use in source and binary forms, with or without
7-- modification, are permitted provided that the following conditions
8-- are met:
9-- 1. Redistributions of source code must retain the above copyright
10--    notice, this list of conditions and the following disclaimer.
11-- 2. Redistributions in binary form must reproduce the above copyright
12--    notice, this list of conditions and the following disclaimer in the
13--    documentation and/or other materials provided with the distribution.
14--
15-- THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18-- ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19-- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20-- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21-- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22-- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23-- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24-- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25-- SUCH DAMAGE.
26--
27-- $FreeBSD$
28--
29
30local config = {}
31
32local modules = {}
33
34local pattern_table
35local carousel_choices = {}
36
37pattern_table = {
38	[1] = {
39		str = "^%s*(#.*)",
40		process = function(_, _)  end
41	},
42	--  module_load="value"
43	[2] = {
44		str = "^%s*([%w_]+)_load%s*=%s*\"([%w%s%p]-)\"%s*(.*)",
45		process = function(k, v)
46			if modules[k] == nil then
47				modules[k] = {}
48			end
49			modules[k].load = v:upper()
50		end
51	},
52	--  module_name="value"
53	[3] = {
54		str = "^%s*([%w_]+)_name%s*=%s*\"([%w%s%p]-)\"%s*(.*)",
55		process = function(k, v)
56			config.setKey(k, "name", v)
57		end
58	},
59	--  module_type="value"
60	[4] = {
61		str = "^%s*([%w_]+)_type%s*=%s*\"([%w%s%p]-)\"%s*(.*)",
62		process = function(k, v)
63			config.setKey(k, "type", v)
64		end
65	},
66	--  module_flags="value"
67	[5] = {
68		str = "^%s*([%w_]+)_flags%s*=%s*\"([%w%s%p]-)\"%s*(.*)",
69		process = function(k, v)
70			config.setKey(k, "flags", v)
71		end
72	},
73	--  module_before="value"
74	[6] = {
75		str = "^%s*([%w_]+)_before%s*=%s*\"([%w%s%p]-)\"%s*(.*)",
76		process = function(k, v)
77			config.setKey(k, "before", v)
78		end
79	},
80	--  module_after="value"
81	[7] = {
82		str = "^%s*([%w_]+)_after%s*=%s*\"([%w%s%p]-)\"%s*(.*)",
83		process = function(k, v)
84			config.setKey(k, "after", v)
85		end
86	},
87	--  module_error="value"
88	[8] = {
89		str = "^%s*([%w_]+)_error%s*=%s*\"([%w%s%p]-)\"%s*(.*)",
90		process = function(k, v)
91			config.setKey(k, "error", v)
92		end
93	},
94	--  exec="command"
95	[9] = {
96		str = "^%s*exec%s*=%s*\"([%w%s%p]-)\"%s*(.*)",
97		process = function(k, _)
98			if loader.perform(k) ~= 0 then
99				print("Failed to exec '" .. k .. "'")
100			end
101		end
102	},
103	--  env_var="value"
104	[10] = {
105		str = "^%s*([%w%p]+)%s*=%s*\"([%w%s%p]-)\"%s*(.*)",
106		process = function(k, v)
107			if config.setenv(k, v) ~= 0 then
108				print("Failed to set '" .. k ..
109				    "' with value: " .. v .. "")
110			end
111		end
112	},
113	--  env_var=num
114	[11] = {
115		str = "^%s*([%w%p]+)%s*=%s*(%d+)%s*(.*)",
116		process = function(k, v)
117			if config.setenv(k, v) ~= 0 then
118				print("Failed to set '" .. k ..
119				    "' with value: " .. v .. "")
120			end
121		end
122	}
123}
124
125-- Module exports
126-- Which variables we changed
127config.env_changed = {}
128-- Values to restore env to (nil to unset)
129config.env_restore = {}
130
131-- The first item in every carousel is always the default item.
132function config.getCarouselIndex(id)
133	local val = carousel_choices[id]
134	if val == nil then
135		return 1
136	end
137	return val
138end
139
140function config.setCarouselIndex(id, idx)
141	carousel_choices[id] = idx
142end
143
144function config.restoreEnv()
145	-- Examine changed environment variables
146	for k, v in pairs(config.env_changed) do
147		local restore_value = config.env_restore[k]
148		if restore_value == nil then
149			-- This one doesn't need restored for some reason
150			goto continue
151		end
152		local current_value = loader.getenv(k)
153		if current_value ~= v then
154			-- This was overwritten by some action taken on the menu
155			-- most likely; we'll leave it be.
156			goto continue
157		end
158		restore_value = restore_value.value
159		if restore_value ~= nil then
160			loader.setenv(k, restore_value)
161		else
162			loader.unsetenv(k)
163		end
164		::continue::
165	end
166
167	config.env_changed = {}
168	config.env_restore = {}
169end
170
171function config.setenv(k, v)
172	-- Track the original value for this if we haven't already
173	if config.env_restore[k] == nil then
174		config.env_restore[k] = {value = loader.getenv(k)}
175	end
176
177	config.env_changed[k] = v
178
179	return loader.setenv(k, v)
180end
181
182function config.setKey(k, n, v)
183	if modules[k] == nil then
184		modules[k] = {}
185	end
186	modules[k][n] = v
187end
188
189function config.lsModules()
190	print("== Listing modules")
191	for k, v in pairs(modules) do
192		print(k, v.load)
193	end
194	print("== List of modules ended")
195end
196
197
198function config.isValidComment(c)
199	if c ~= nil then
200		local s = c:match("^%s*#.*")
201		if s == nil then
202			s = c:match("^%s*$")
203		end
204		if s == nil then
205			return false
206		end
207	end
208	return true
209end
210
211function config.loadmod(mod, silent)
212	local status = true
213	for k, v in pairs(mod) do
214		if v.load == "YES" then
215			local str = "load "
216			if v.flags ~= nil then
217				str = str .. v.flags .. " "
218			end
219			if v.type ~= nil then
220				str = str .. "-t " .. v.type .. " "
221			end
222			if v.name ~= nil then
223				str = str .. v.name
224			else
225				str = str .. k
226			end
227
228			if v.before ~= nil then
229				if loader.perform(v.before) ~= 0 then
230					if not silent then
231						print("Failed to execute '" ..
232						    v.before ..
233						    "' before loading '" .. k ..
234						    "'")
235					end
236					status = false
237				end
238			end
239
240			if loader.perform(str) ~= 0 then
241				if not silent then
242					print("Failed to execute '" .. str ..
243					    "'")
244				end
245				if v.error ~= nil then
246					loader.perform(v.error)
247				end
248				status = false
249			end
250
251			if v.after ~= nil then
252				if loader.perform(v.after) ~= 0 then
253					if not silent then
254						print("Failed to execute '" ..
255						    v.after ..
256						    "' after loading '" .. k ..
257						    "'")
258					end
259					status = false
260				end
261			end
262
263--		else
264--			if not silent then
265--				print("Skipping module '". . k .. "'")
266--			end
267		end
268	end
269
270	return status
271end
272
273-- silent runs will not return false if we fail to open the file
274function config.parse(name, silent)
275	if silent == nil then
276		silent = false
277	end
278	local f = io.open(name)
279	if f == nil then
280		if not silent then
281			print("Failed to open config: '" .. name .. "'")
282		end
283		return silent
284	end
285
286	local text, _ = io.read(f)
287
288	if text == nil then
289		if not silent then
290			print("Failed to read config: '" .. name .. "'")
291		end
292		return silent
293	end
294
295	local n = 1
296	local status = true
297
298	for line in text:gmatch("([^\n]+)") do
299		if line:match("^%s*$") == nil then
300			local found = false
301
302			for _, val in ipairs(pattern_table) do
303				local k, v, c = line:match(val.str)
304				if k ~= nil then
305					found = true
306
307					if config.isValidComment(c) then
308						val.process(k, v)
309					else
310						print("Malformed line (" .. n ..
311						    "):\n\t'" .. line .. "'")
312						status = false
313					end
314
315					break
316				end
317			end
318
319			if not found then
320				print("Malformed line (" .. n .. "):\n\t'" ..
321				    line .. "'")
322				status = false
323			end
324		end
325		n = n + 1
326	end
327
328	return status
329end
330
331-- other_kernel is optionally the name of a kernel to load, if not the default
332-- or autoloaded default from the module_path
333function config.loadkernel(other_kernel)
334	local flags = loader.getenv("kernel_options") or ""
335	local kernel = other_kernel or loader.getenv("kernel")
336
337	local try_load = function (names)
338		for name in names:gmatch("([^;]+)%s*;?") do
339			local r = loader.perform("load " .. flags .. " " .. name)
340			if r == 0 then
341				return name
342			end
343		end
344		return nil
345	end
346
347	local load_bootfile = function()
348		local bootfile = loader.getenv("bootfile")
349
350		-- append default kernel name
351		if bootfile == nil then
352			bootfile = "kernel"
353		else
354			bootfile = bootfile .. ";kernel"
355		end
356
357		return try_load(bootfile)
358	end
359
360	-- kernel not set, try load from default module_path
361	if kernel == nil then
362		local res = load_bootfile()
363
364		if res ~= nil then
365			-- Default kernel is loaded
366			config.kernel_loaded = nil
367			return true
368		else
369			print("No kernel set, failed to load from module_path")
370			return false
371		end
372	else
373		-- Use our cached module_path, so we don't end up with multiple
374		-- automatically added kernel paths to our final module_path
375		local module_path = config.module_path
376		local res
377
378		if other_kernel ~= nil then
379			kernel = other_kernel
380		end
381		-- first try load kernel with module_path = /boot/${kernel}
382		-- then try load with module_path=${kernel}
383		local paths = {"/boot/" .. kernel, kernel}
384
385		for _, v in pairs(paths) do
386			loader.setenv("module_path", v)
387			res = load_bootfile()
388
389			-- succeeded, add path to module_path
390			if res ~= nil then
391				config.kernel_loaded = kernel
392				if module_path ~= nil then
393					loader.setenv("module_path", v .. ";" ..
394					    module_path)
395				end
396				return true
397			end
398		end
399
400		-- failed to load with ${kernel} as a directory
401		-- try as a file
402		res = try_load(kernel)
403		if res ~= nil then
404			config.kernel_loaded = kernel
405			return true
406		else
407			print("Failed to load kernel '" .. kernel .. "'")
408			return false
409		end
410	end
411end
412
413function config.selectkernel(kernel)
414	config.kernel_selected = kernel
415end
416
417function config.load(file)
418	if not file then
419		file = "/boot/defaults/loader.conf"
420	end
421
422	if not config.parse(file) then
423		print("Failed to parse configuration: '" .. file .. "'")
424	end
425
426	local f = loader.getenv("loader_conf_files")
427	if f ~= nil then
428		for name in f:gmatch("([%w%p]+)%s*") do
429			-- These may or may not exist, and that's ok. Do a
430			-- silent parse so that we complain on parse errors but
431			-- not for them simply not existing.
432			if not config.parse(name, true) then
433				print("Failed to parse configuration: '" ..
434				    name .. "'")
435			end
436		end
437	end
438
439	-- Cache the provided module_path at load time for later use
440	config.module_path = loader.getenv("module_path")
441end
442
443-- Reload configuration
444function config.reload(file)
445	modules = {}
446	config.restoreEnv()
447	config.load(file)
448end
449
450function config.loadelf()
451	local kernel = config.kernel_selected or config.kernel_loaded
452	local loaded
453
454	print("Loading kernel...")
455	loaded = config.loadkernel(kernel)
456
457	if not loaded then
458		print("Failed to load any kernel")
459		return
460	end
461
462	print("Loading configured modules...")
463	if not config.loadmod(modules) then
464		print("Could not load one or more modules!")
465	end
466end
467
468return config
469