1-- 2-- Copyright (c) 2015 Pedro Souza <pedrosouza@freebsd.org> 3-- Copyright (C) 2018 Kyle Evans <kevans@FreeBSD.org> 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(k, v) 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, v) 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 287 local r 288 289 text, r = io.read(f) 290 291 if text == nil then 292 if not silent then 293 print("Failed to read config: '" .. name .. "'") 294 end 295 return silent 296 end 297 298 local n = 1 299 local status = true 300 301 for line in text:gmatch("([^\n]+)") do 302 if line:match("^%s*$") == nil then 303 local found = false 304 305 for i, val in ipairs(pattern_table) do 306 local k, v, c = line:match(val.str) 307 if k ~= nil then 308 found = true 309 310 if config.isValidComment(c) then 311 val.process(k, v) 312 else 313 print("Malformed line (" .. n .. 314 "):\n\t'" .. line .. "'") 315 status = false 316 end 317 318 break 319 end 320 end 321 322 if not found then 323 print("Malformed line (" .. n .. "):\n\t'" .. 324 line .. "'") 325 status = false 326 end 327 end 328 n = n + 1 329 end 330 331 return status 332end 333 334-- other_kernel is optionally the name of a kernel to load, if not the default 335-- or autoloaded default from the module_path 336function config.loadkernel(other_kernel) 337 local flags = loader.getenv("kernel_options") or "" 338 local kernel = other_kernel or loader.getenv("kernel") 339 340 local try_load = function (names) 341 for name in names:gmatch("([^;]+)%s*;?") do 342 r = loader.perform("load " .. flags .. " " .. name) 343 if r == 0 then 344 return name 345 end 346 end 347 return nil 348 end 349 350 local load_bootfile = function() 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 = nil 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 k,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 = false 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