xref: /freebsd-12.1/stand/lua/config.lua (revision 363b2c7f)
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 ..
340			    " " .. name)
341			if r == 0 then
342				return name
343			end
344		end
345		return nil
346	end
347
348	local load_bootfile = function()
349		local bootfile = loader.getenv("bootfile")
350
351		-- append default kernel name
352		if bootfile == nil then
353			bootfile = "kernel"
354		else
355			bootfile = bootfile .. ";kernel"
356		end
357
358		return try_load(bootfile)
359	end
360
361	-- kernel not set, try load from default module_path
362	if kernel == nil then
363		local res = load_bootfile()
364
365		if res ~= nil then
366			-- Default kernel is loaded
367			config.kernel_loaded = nil
368			return true
369		else
370			print("No kernel set, failed to load from module_path")
371			return false
372		end
373	else
374		-- Use our cached module_path, so we don't end up with multiple
375		-- automatically added kernel paths to our final module_path
376		local module_path = config.module_path
377		local res
378
379		if other_kernel ~= nil then
380			kernel = other_kernel
381		end
382		-- first try load kernel with module_path = /boot/${kernel}
383		-- then try load with module_path=${kernel}
384		local paths = {"/boot/" .. kernel, kernel}
385
386		for _, v in pairs(paths) do
387			loader.setenv("module_path", v)
388			res = load_bootfile()
389
390			-- succeeded, add path to module_path
391			if res ~= nil then
392				config.kernel_loaded = kernel
393				if module_path ~= nil then
394					loader.setenv("module_path", v .. ";" ..
395					    module_path)
396				end
397				return true
398			end
399		end
400
401		-- failed to load with ${kernel} as a directory
402		-- try as a file
403		res = try_load(kernel)
404		if res ~= nil then
405			config.kernel_loaded = kernel
406			return true
407		else
408			print("Failed to load kernel '" .. kernel .. "'")
409			return false
410		end
411	end
412end
413
414function config.selectkernel(kernel)
415	config.kernel_selected = kernel
416end
417
418function config.load(file)
419	if not file then
420		file = "/boot/defaults/loader.conf"
421	end
422
423	if not config.parse(file) then
424		print("Failed to parse configuration: '" .. file .. "'")
425	end
426
427	local f = loader.getenv("loader_conf_files")
428	if f ~= nil then
429		for name in f:gmatch("([%w%p]+)%s*") do
430			-- These may or may not exist, and that's ok. Do a
431			-- silent parse so that we complain on parse errors but
432			-- not for them simply not existing.
433			if not config.parse(name, true) then
434				print("Failed to parse configuration: '" ..
435				    name .. "'")
436			end
437		end
438	end
439
440	-- Cache the provided module_path at load time for later use
441	config.module_path = loader.getenv("module_path")
442end
443
444-- Reload configuration
445function config.reload(file)
446	modules = {}
447	config.restoreEnv()
448	config.load(file)
449end
450
451function config.loadelf()
452	local kernel = config.kernel_selected or config.kernel_loaded
453	local loaded
454
455	print("Loading kernel...")
456	loaded = config.loadkernel(kernel)
457
458	if not loaded then
459		print("Failed to load any kernel")
460		return
461	end
462
463	print("Loading configured modules...")
464	if not config.loadmod(modules) then
465		print("Could not load one or more modules!")
466	end
467end
468
469return config
470