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