2 -- SPDX-License-Identifier: BSD-2-Clause-FreeBSD
4 -- Copyright (c) 2015 Pedro Souza <pedrosouza@freebsd.org>
5 -- Copyright (C) 2018 Kyle Evans <kevans@FreeBSD.org>
6 -- All rights reserved.
8 -- Redistribution and use in source and binary forms, with or without
9 -- modification, are permitted provided that the following conditions
11 -- 1. Redistributions of source code must retain the above copyright
12 -- notice, this list of conditions and the following disclaimer.
13 -- 2. Redistributions in binary form must reproduce the above copyright
14 -- notice, this list of conditions and the following disclaimer in the
15 -- documentation and/or other materials provided with the distribution.
17 -- THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 -- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 -- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 -- ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 -- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 -- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 -- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 -- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 -- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 -- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 local carousel_choices = {}
42 process = function(_, _) end
44 -- module_load="value"
46 str = "^%s*([%w_]+)_load%s*=%s*\"([%w%s%p]-)\"%s*(.*)",
47 process = function(k, v)
48 if modules[k] == nil then
51 modules[k].load = v:upper()
54 -- module_name="value"
56 str = "^%s*([%w_]+)_name%s*=%s*\"([%w%s%p]-)\"%s*(.*)",
57 process = function(k, v)
58 config.setKey(k, "name", v)
61 -- module_type="value"
63 str = "^%s*([%w_]+)_type%s*=%s*\"([%w%s%p]-)\"%s*(.*)",
64 process = function(k, v)
65 config.setKey(k, "type", v)
68 -- module_flags="value"
70 str = "^%s*([%w_]+)_flags%s*=%s*\"([%w%s%p]-)\"%s*(.*)",
71 process = function(k, v)
72 config.setKey(k, "flags", v)
75 -- module_before="value"
77 str = "^%s*([%w_]+)_before%s*=%s*\"([%w%s%p]-)\"%s*(.*)",
78 process = function(k, v)
79 config.setKey(k, "before", v)
82 -- module_after="value"
84 str = "^%s*([%w_]+)_after%s*=%s*\"([%w%s%p]-)\"%s*(.*)",
85 process = function(k, v)
86 config.setKey(k, "after", v)
89 -- module_error="value"
91 str = "^%s*([%w_]+)_error%s*=%s*\"([%w%s%p]-)\"%s*(.*)",
92 process = function(k, v)
93 config.setKey(k, "error", v)
98 str = "^%s*exec%s*=%s*\"([%w%s%p]-)\"%s*(.*)",
99 process = function(k, _)
100 if loader.perform(k) ~= 0 then
101 print("Failed to exec '" .. k .. "'")
107 str = "^%s*([%w%p]+)%s*=%s*\"([%w%s%p]-)\"%s*(.*)",
108 process = function(k, v)
109 if config.setenv(k, v) ~= 0 then
110 print("Failed to set '" .. k ..
111 "' with value: " .. v .. "")
117 str = "^%s*([%w%p]+)%s*=%s*(%d+)%s*(.*)",
118 process = function(k, v)
119 if config.setenv(k, v) ~= 0 then
120 print("Failed to set '" .. k ..
121 "' with value: " .. v .. "")
127 local function read_file(name, silent)
128 local f = io.open(name)
131 print("Failed to open config: '" .. name .. "'")
136 local text, _ = io.read(f)
137 -- We might have read in the whole file, this won't be needed any more.
142 print("Failed to read config: '" .. name .. "'")
149 local function check_nextboot()
150 local nextboot_file = loader.getenv("nextboot_file")
151 if nextboot_file == nil then
155 local text = read_file(nextboot_file, true)
160 if text:match("^nextboot_enable=\"NO\"") ~= nil then
161 -- We're done; nextboot is not enabled
165 if not config.parse(text) then
166 print("Failed to parse nextboot configuration: '" ..
167 nextboot_file .. "'")
170 -- Attempt to rewrite the first line and only the first line of the
171 -- nextboot_file. We overwrite it with nextboot_enable="NO", then
172 -- check for that on load. See: check_nextboot_enabled
173 -- It's worth noting that this won't work on every filesystem, so we
174 -- won't do anything notable if we have any errors in this process.
175 local nfile = io.open(nextboot_file, 'w')
177 -- We need the trailing space here to account for the extra
178 -- character taken up by the string nextboot_enable="YES"
179 -- Or new end quotation mark lands on the S, and we want to
180 -- rewrite the entirety of the first line.
181 io.write(nfile, "nextboot_enable=\"NO\" ")
187 -- Which variables we changed
188 config.env_changed = {}
189 -- Values to restore env to (nil to unset)
190 config.env_restore = {}
192 -- The first item in every carousel is always the default item.
193 function config.getCarouselIndex(id)
194 local val = carousel_choices[id]
201 function config.setCarouselIndex(id, idx)
202 carousel_choices[id] = idx
205 function config.restoreEnv()
206 -- Examine changed environment variables
207 for k, v in pairs(config.env_changed) do
208 local restore_value = config.env_restore[k]
209 if restore_value == nil then
210 -- This one doesn't need restored for some reason
213 local current_value = loader.getenv(k)
214 if current_value ~= v then
215 -- This was overwritten by some action taken on the menu
216 -- most likely; we'll leave it be.
219 restore_value = restore_value.value
220 if restore_value ~= nil then
221 loader.setenv(k, restore_value)
228 config.env_changed = {}
229 config.env_restore = {}
232 function config.setenv(k, v)
233 -- Track the original value for this if we haven't already
234 if config.env_restore[k] == nil then
235 config.env_restore[k] = {value = loader.getenv(k)}
238 config.env_changed[k] = v
240 return loader.setenv(k, v)
243 function config.setKey(k, n, v)
244 if modules[k] == nil then
250 function config.lsModules()
251 print("== Listing modules")
252 for k, v in pairs(modules) do
255 print("== List of modules ended")
259 function config.isValidComment(c)
261 local s = c:match("^%s*#.*")
272 function config.loadmod(mod, silent)
274 for k, v in pairs(mod) do
275 if v.load == "YES" then
277 if v.flags ~= nil then
278 str = str .. v.flags .. " "
280 if v.type ~= nil then
281 str = str .. "-t " .. v.type .. " "
283 if v.name ~= nil then
289 if v.before ~= nil then
290 if loader.perform(v.before) ~= 0 then
292 print("Failed to execute '" ..
294 "' before loading '" .. k ..
301 if loader.perform(str) ~= 0 then
303 print("Failed to execute '" .. str ..
306 if v.error ~= nil then
307 loader.perform(v.error)
312 if v.after ~= nil then
313 if loader.perform(v.after) ~= 0 then
315 print("Failed to execute '" ..
317 "' after loading '" .. k ..
325 -- if not silent then
326 -- print("Skipping module '". . k .. "'")
334 function config.processFile(name, silent)
335 if silent == nil then
339 local text = read_file(name, silent)
344 return config.parse(text)
347 -- silent runs will not return false if we fail to open the file
348 -- check_and_halt, if it's set, will be executed on the full text of the config
349 -- file. If it returns false, we are to halt immediately.
350 function config.parse(text)
354 for line in text:gmatch("([^\n]+)") do
355 if line:match("^%s*$") == nil then
358 for _, val in ipairs(pattern_table) do
359 local k, v, c = line:match(val.str)
363 if config.isValidComment(c) then
366 print("Malformed line (" .. n ..
367 "):\n\t'" .. line .. "'")
376 print("Malformed line (" .. n .. "):\n\t'" ..
387 -- other_kernel is optionally the name of a kernel to load, if not the default
388 -- or autoloaded default from the module_path
389 function config.loadkernel(other_kernel)
390 local flags = loader.getenv("kernel_options") or ""
391 local kernel = other_kernel or loader.getenv("kernel")
393 local function try_load(names)
394 for name in names:gmatch("([^;]+)%s*;?") do
395 local r = loader.perform("load " .. flags ..
404 local function load_bootfile()
405 local bootfile = loader.getenv("bootfile")
407 -- append default kernel name
408 if bootfile == nil then
411 bootfile = bootfile .. ";kernel"
414 return try_load(bootfile)
417 -- kernel not set, try load from default module_path
418 if kernel == nil then
419 local res = load_bootfile()
422 -- Default kernel is loaded
423 config.kernel_loaded = nil
426 print("No kernel set, failed to load from module_path")
430 -- Use our cached module_path, so we don't end up with multiple
431 -- automatically added kernel paths to our final module_path
432 local module_path = config.module_path
435 if other_kernel ~= nil then
436 kernel = other_kernel
438 -- first try load kernel with module_path = /boot/${kernel}
439 -- then try load with module_path=${kernel}
440 local paths = {"/boot/" .. kernel, kernel}
442 for _, v in pairs(paths) do
443 loader.setenv("module_path", v)
444 res = load_bootfile()
446 -- succeeded, add path to module_path
448 config.kernel_loaded = kernel
449 if module_path ~= nil then
450 loader.setenv("module_path", v .. ";" ..
457 -- failed to load with ${kernel} as a directory
459 res = try_load(kernel)
461 config.kernel_loaded = kernel
464 print("Failed to load kernel '" .. kernel .. "'")
470 function config.selectkernel(kernel)
471 config.kernel_selected = kernel
474 function config.load(file)
476 file = "/boot/defaults/loader.conf"
479 if not config.processFile(file) then
480 print("Failed to parse configuration: '" .. file .. "'")
483 local f = loader.getenv("loader_conf_files")
485 for name in f:gmatch("([%w%p]+)%s*") do
486 -- These may or may not exist, and that's ok. Do a
487 -- silent parse so that we complain on parse errors but
488 -- not for them simply not existing.
489 if not config.processFile(name, true) then
490 print("Failed to parse configuration: '" ..
498 -- Cache the provided module_path at load time for later use
499 config.module_path = loader.getenv("module_path")
502 -- Reload configuration
503 function config.reload(file)
509 function config.loadelf()
510 local kernel = config.kernel_selected or config.kernel_loaded
513 print("Loading kernel...")
514 loaded = config.loadkernel(kernel)
517 print("Failed to load any kernel")
521 print("Loading configured modules...")
522 if not config.loadmod(modules) then
523 print("Could not load one or more modules!")