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