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