1088b4f5fSWarner Losh-- 272e39d71SKyle Evans-- SPDX-License-Identifier: BSD-2-Clause-FreeBSD 372e39d71SKyle Evans-- 4088b4f5fSWarner Losh-- Copyright (c) 2015 Pedro Souza <[email protected]> 5e12ff891SKyle Evans-- Copyright (c) 2018 Kyle Evans <[email protected]> 6088b4f5fSWarner Losh-- All rights reserved. 7088b4f5fSWarner Losh-- 8088b4f5fSWarner Losh-- Redistribution and use in source and binary forms, with or without 9088b4f5fSWarner Losh-- modification, are permitted provided that the following conditions 10088b4f5fSWarner Losh-- are met: 11088b4f5fSWarner Losh-- 1. Redistributions of source code must retain the above copyright 12088b4f5fSWarner Losh-- notice, this list of conditions and the following disclaimer. 13088b4f5fSWarner Losh-- 2. Redistributions in binary form must reproduce the above copyright 14088b4f5fSWarner Losh-- notice, this list of conditions and the following disclaimer in the 15088b4f5fSWarner Losh-- documentation and/or other materials provided with the distribution. 16088b4f5fSWarner Losh-- 17088b4f5fSWarner Losh-- THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18088b4f5fSWarner Losh-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19088b4f5fSWarner Losh-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20088b4f5fSWarner Losh-- ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21088b4f5fSWarner Losh-- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22088b4f5fSWarner Losh-- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23088b4f5fSWarner Losh-- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24088b4f5fSWarner Losh-- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25088b4f5fSWarner Losh-- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26088b4f5fSWarner Losh-- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27088b4f5fSWarner Losh-- SUCH DAMAGE. 28088b4f5fSWarner Losh-- 29088b4f5fSWarner Losh-- $FreeBSD$ 30088b4f5fSWarner Losh-- 31088b4f5fSWarner Losh 32aea262bfSKyle Evanslocal hook = require("hook") 33aea262bfSKyle Evans 34aedd6be5SKyle Evanslocal config = {} 35aedd6be5SKyle Evanslocal modules = {} 36aedd6be5SKyle Evanslocal carousel_choices = {} 3764c91742SKyle Evans-- Which variables we changed 3864c91742SKyle Evanslocal env_changed = {} 3964c91742SKyle Evans-- Values to restore env to (nil to unset) 4064c91742SKyle Evanslocal env_restore = {} 4125c4c7a5SKyle Evans 4272cf7db3SKyle Evanslocal MSG_FAILDIR = "Failed to load conf dir '%s': not a directory" 43fdabb5f5SKyle Evanslocal MSG_FAILEXEC = "Failed to exec '%s'" 44fdabb5f5SKyle Evanslocal MSG_FAILSETENV = "Failed to '%s' with value: %s" 45fdabb5f5SKyle Evanslocal MSG_FAILOPENCFG = "Failed to open config: '%s'" 46fdabb5f5SKyle Evanslocal MSG_FAILREADCFG = "Failed to read config: '%s'" 47fdabb5f5SKyle Evanslocal MSG_FAILPARSECFG = "Failed to parse config: '%s'" 48633f8212SKyle Evanslocal MSG_FAILPARSEVAR = "Failed to parse variable '%s': %s" 49fdabb5f5SKyle Evanslocal MSG_FAILEXBEF = "Failed to execute '%s' before loading '%s'" 50fdabb5f5SKyle Evanslocal MSG_FAILEXAF = "Failed to execute '%s' after loading '%s'" 51fdabb5f5SKyle Evanslocal MSG_MALFORMED = "Malformed line (%d):\n\t'%s'" 52fdabb5f5SKyle Evanslocal MSG_DEFAULTKERNFAIL = "No kernel set, failed to load from module_path" 53fdabb5f5SKyle Evanslocal MSG_KERNFAIL = "Failed to load kernel '%s'" 540d7bee6aSKyle Evanslocal MSG_XENKERNFAIL = "Failed to load Xen kernel '%s'" 550d7bee6aSKyle Evanslocal MSG_XENKERNLOADING = "Loading Xen kernel..." 56fdabb5f5SKyle Evanslocal MSG_KERNLOADING = "Loading kernel..." 57fdabb5f5SKyle Evanslocal MSG_MODLOADING = "Loading configured modules..." 58532dc172SKyle Evanslocal MSG_MODBLACKLIST = "Not loading blacklisted module '%s'" 59fdabb5f5SKyle Evans 60633f8212SKyle Evanslocal MSG_FAILSYN_QUOTE = "Stray quote at position '%d'" 61633f8212SKyle Evanslocal MSG_FAILSYN_EOLESC = "Stray escape at end of line" 62633f8212SKyle Evanslocal MSG_FAILSYN_EOLVAR = "Unescaped $ at end of line" 63633f8212SKyle Evanslocal MSG_FAILSYN_BADVAR = "Malformed variable expression at position '%d'" 64633f8212SKyle Evans 65*e3cd8246SKyle Evanslocal MODULEEXPR = '([-%w_]+)' 66633f8212SKyle Evanslocal QVALEXPR = '"(.*)"' 671ee89ab5SKyle Evanslocal QVALREPL = QVALEXPR:gsub('%%', '%%%%') 68*e3cd8246SKyle Evanslocal WORDEXPR = "([-%w%d][-%w%d_.]*)" 691ee89ab5SKyle Evanslocal WORDREPL = WORDEXPR:gsub('%%', '%%%%') 70058c692eSKyle Evans 71bf832717SKyle Evans-- Entries that should never make it into the environment; each one should have 72bf832717SKyle Evans-- a documented reason for its existence, and these should all be implementation 73bf832717SKyle Evans-- details of the config module. 74bf832717SKyle Evanslocal loader_env_restricted_table = { 75bf832717SKyle Evans -- loader_conf_files should be considered write-only, and consumers 76bf832717SKyle Evans -- should not rely on any particular value; it's a loader implementation 77bf832717SKyle Evans -- detail. Moreover, it's not a particularly useful variable to have in 78bf832717SKyle Evans -- the kenv. Save the overhead, let it get fetched other ways. 79bf832717SKyle Evans loader_conf_files = true, 80bf832717SKyle Evans} 81bf832717SKyle Evans 8264c91742SKyle Evanslocal function restoreEnv() 8364c91742SKyle Evans -- Examine changed environment variables 8464c91742SKyle Evans for k, v in pairs(env_changed) do 8564c91742SKyle Evans local restore_value = env_restore[k] 8664c91742SKyle Evans if restore_value == nil then 8764c91742SKyle Evans -- This one doesn't need restored for some reason 8864c91742SKyle Evans goto continue 8964c91742SKyle Evans end 9064c91742SKyle Evans local current_value = loader.getenv(k) 9164c91742SKyle Evans if current_value ~= v then 9264c91742SKyle Evans -- This was overwritten by some action taken on the menu 9364c91742SKyle Evans -- most likely; we'll leave it be. 9464c91742SKyle Evans goto continue 9564c91742SKyle Evans end 9664c91742SKyle Evans restore_value = restore_value.value 9764c91742SKyle Evans if restore_value ~= nil then 9864c91742SKyle Evans loader.setenv(k, restore_value) 9964c91742SKyle Evans else 10064c91742SKyle Evans loader.unsetenv(k) 10164c91742SKyle Evans end 10264c91742SKyle Evans ::continue:: 10364c91742SKyle Evans end 10464c91742SKyle Evans 10564c91742SKyle Evans env_changed = {} 10664c91742SKyle Evans env_restore = {} 10764c91742SKyle Evansend 10864c91742SKyle Evans 109bf832717SKyle Evans-- XXX This getEnv/setEnv should likely be exported at some point. We can save 110bf832717SKyle Evans-- the call back into loader.getenv for any variable that's been set or 111bf832717SKyle Evans-- overridden by any loader.conf using this implementation with little overhead 112bf832717SKyle Evans-- since we're already tracking the values. 113bf832717SKyle Evanslocal function getEnv(key) 114bf832717SKyle Evans if loader_env_restricted_table[key] ~= nil or 115bf832717SKyle Evans env_changed[key] ~= nil then 116bf832717SKyle Evans return env_changed[key] 117bf832717SKyle Evans end 118bf832717SKyle Evans 119bf832717SKyle Evans return loader.getenv(key) 120bf832717SKyle Evansend 121bf832717SKyle Evans 12264c91742SKyle Evanslocal function setEnv(key, value) 123bf832717SKyle Evans env_changed[key] = value 124bf832717SKyle Evans 125bf832717SKyle Evans if loader_env_restricted_table[key] ~= nil then 126bf832717SKyle Evans return 0 127bf832717SKyle Evans end 128bf832717SKyle Evans 12964c91742SKyle Evans -- Track the original value for this if we haven't already 13064c91742SKyle Evans if env_restore[key] == nil then 13164c91742SKyle Evans env_restore[key] = {value = loader.getenv(key)} 13264c91742SKyle Evans end 13364c91742SKyle Evans 13464c91742SKyle Evans return loader.setenv(key, value) 13564c91742SKyle Evansend 13664c91742SKyle Evans 137dbef5253SKyle Evans-- name here is one of 'name', 'type', flags', 'before', 'after', or 'error.' 138dbef5253SKyle Evans-- These are set from lines in loader.conf(5): ${key}_${name}="${value}" where 139dbef5253SKyle Evans-- ${key} is a module name. 140dbef5253SKyle Evanslocal function setKey(key, name, value) 141dbef5253SKyle Evans if modules[key] == nil then 142dbef5253SKyle Evans modules[key] = {} 143dbef5253SKyle Evans end 144dbef5253SKyle Evans modules[key][name] = value 145dbef5253SKyle Evansend 146dbef5253SKyle Evans 147deb8c8f5SKyle Evans-- Escapes the named value for use as a literal in a replacement pattern. 148deb8c8f5SKyle Evans-- e.g. dhcp.host-name gets turned into dhcp%.host%-name to remove the special 149deb8c8f5SKyle Evans-- meaning. 150deb8c8f5SKyle Evanslocal function escapeName(name) 151deb8c8f5SKyle Evans return name:gsub("([%p])", "%%%1") 152deb8c8f5SKyle Evansend 153deb8c8f5SKyle Evans 154deb8c8f5SKyle Evanslocal function processEnvVar(value) 155633f8212SKyle Evans local pval, vlen = '', #value 156633f8212SKyle Evans local nextpos, vdelim, vinit = 1 157633f8212SKyle Evans local vpat 158633f8212SKyle Evans for i = 1, vlen do 159633f8212SKyle Evans if i < nextpos then 160633f8212SKyle Evans goto nextc 161deb8c8f5SKyle Evans end 162633f8212SKyle Evans 163633f8212SKyle Evans local c = value:sub(i, i) 164633f8212SKyle Evans if c == '\\' then 165633f8212SKyle Evans if i == vlen then 166633f8212SKyle Evans return nil, MSG_FAILSYN_EOLESC 167deb8c8f5SKyle Evans end 168633f8212SKyle Evans nextpos = i + 2 169633f8212SKyle Evans pval = pval .. value:sub(i + 1, i + 1) 170633f8212SKyle Evans elseif c == '"' then 171633f8212SKyle Evans return nil, MSG_FAILSYN_QUOTE:format(i) 172633f8212SKyle Evans elseif c == "$" then 173633f8212SKyle Evans if i == vlen then 174633f8212SKyle Evans return nil, MSG_FAILSYN_EOLVAR 175633f8212SKyle Evans else 176633f8212SKyle Evans if value:sub(i + 1, i + 1) == "{" then 177633f8212SKyle Evans -- Skip ${ 178633f8212SKyle Evans vinit = i + 2 179633f8212SKyle Evans vdelim = '}' 180633f8212SKyle Evans vpat = "^([^}]+)}" 181633f8212SKyle Evans else 182633f8212SKyle Evans -- Skip the $ 183633f8212SKyle Evans vinit = i + 1 184633f8212SKyle Evans vdelim = nil 185*e3cd8246SKyle Evans vpat = "^([%w][-%w%d_.]*)" 186633f8212SKyle Evans end 187633f8212SKyle Evans 188633f8212SKyle Evans local name = value:match(vpat, vinit) 189633f8212SKyle Evans if not name then 190633f8212SKyle Evans return nil, MSG_FAILSYN_BADVAR:format(i) 191633f8212SKyle Evans else 192633f8212SKyle Evans nextpos = vinit + #name 193633f8212SKyle Evans if vdelim then 194633f8212SKyle Evans nextpos = nextpos + 1 195633f8212SKyle Evans end 196633f8212SKyle Evans 197633f8212SKyle Evans local repl = loader.getenv(name) or "" 198633f8212SKyle Evans pval = pval .. repl 199633f8212SKyle Evans end 200633f8212SKyle Evans end 201633f8212SKyle Evans else 202633f8212SKyle Evans pval = pval .. c 203633f8212SKyle Evans end 204633f8212SKyle Evans ::nextc:: 205633f8212SKyle Evans end 206633f8212SKyle Evans 207633f8212SKyle Evans return pval 208deb8c8f5SKyle Evansend 209deb8c8f5SKyle Evans 2101ee89ab5SKyle Evanslocal function checkPattern(line, pattern) 2111ee89ab5SKyle Evans local function _realCheck(_line, _pattern) 2121ee89ab5SKyle Evans return _line:match(_pattern) 2131ee89ab5SKyle Evans end 2141ee89ab5SKyle Evans 2151ee89ab5SKyle Evans if pattern:find('$VALUE') then 2161ee89ab5SKyle Evans local k, v, c 2171ee89ab5SKyle Evans k, v, c = _realCheck(line, pattern:gsub('$VALUE', QVALREPL)) 2181ee89ab5SKyle Evans if k ~= nil then 2191ee89ab5SKyle Evans return k,v, c 2201ee89ab5SKyle Evans end 2211ee89ab5SKyle Evans return _realCheck(line, pattern:gsub('$VALUE', WORDREPL)) 2221ee89ab5SKyle Evans else 2231ee89ab5SKyle Evans return _realCheck(line, pattern) 2241ee89ab5SKyle Evans end 2251ee89ab5SKyle Evansend 2261ee89ab5SKyle Evans 227058c692eSKyle Evans-- str in this table is a regex pattern. It will automatically be anchored to 228058c692eSKyle Evans-- the beginning of a line and any preceding whitespace will be skipped. The 229058c692eSKyle Evans-- pattern should have no more than two captures patterns, which correspond to 230058c692eSKyle Evans-- the two parameters (usually 'key' and 'value') that are passed to the 2311ee89ab5SKyle Evans-- process function. All trailing characters will be validated. Any $VALUE 2321ee89ab5SKyle Evans-- token included in a pattern will be tried first with a quoted value capture 2331ee89ab5SKyle Evans-- group, then a single-word value capture group. This is our kludge for Lua 2341ee89ab5SKyle Evans-- regex not supporting branching. 2359a16e110SKyle Evans-- 2369a16e110SKyle Evans-- We have two special entries in this table: the first is the first entry, 2379a16e110SKyle Evans-- a full-line comment. The second is for 'exec' handling. Both have a single 2389a16e110SKyle Evans-- capture group, but the difference is that the full-line comment pattern will 2399a16e110SKyle Evans-- match the entire line. This does not run afoul of the later end of line 2409a16e110SKyle Evans-- validation that we'll do after a match. However, the 'exec' pattern will. 2419a16e110SKyle Evans-- We document the exceptions with a special 'groups' index that indicates 2429a16e110SKyle Evans-- the number of capture groups, if not two. We'll use this later to do 2439a16e110SKyle Evans-- validation on the proper entry. 2441ee89ab5SKyle Evans-- 245e1a8835aSKyle Evanslocal pattern_table = { 246e1a8835aSKyle Evans { 247058c692eSKyle Evans str = "(#.*)", 248e1a8835aSKyle Evans process = function(_, _) end, 2499a16e110SKyle Evans groups = 1, 250088b4f5fSWarner Losh }, 251088b4f5fSWarner Losh -- module_load="value" 252e1a8835aSKyle Evans { 2531ee89ab5SKyle Evans str = MODULEEXPR .. "_load%s*=%s*$VALUE", 254088b4f5fSWarner Losh process = function(k, v) 2559f71d421SKyle Evans if modules[k] == nil then 256aedd6be5SKyle Evans modules[k] = {} 257088b4f5fSWarner Losh end 258aedd6be5SKyle Evans modules[k].load = v:upper() 259e1a8835aSKyle Evans end, 260088b4f5fSWarner Losh }, 261088b4f5fSWarner Losh -- module_name="value" 262e1a8835aSKyle Evans { 2631ee89ab5SKyle Evans str = MODULEEXPR .. "_name%s*=%s*$VALUE", 264088b4f5fSWarner Losh process = function(k, v) 265dbef5253SKyle Evans setKey(k, "name", v) 266e1a8835aSKyle Evans end, 267088b4f5fSWarner Losh }, 268088b4f5fSWarner Losh -- module_type="value" 269e1a8835aSKyle Evans { 2701ee89ab5SKyle Evans str = MODULEEXPR .. "_type%s*=%s*$VALUE", 271088b4f5fSWarner Losh process = function(k, v) 272dbef5253SKyle Evans setKey(k, "type", v) 273e1a8835aSKyle Evans end, 274088b4f5fSWarner Losh }, 275088b4f5fSWarner Losh -- module_flags="value" 276e1a8835aSKyle Evans { 2771ee89ab5SKyle Evans str = MODULEEXPR .. "_flags%s*=%s*$VALUE", 278088b4f5fSWarner Losh process = function(k, v) 279dbef5253SKyle Evans setKey(k, "flags", v) 280e1a8835aSKyle Evans end, 281088b4f5fSWarner Losh }, 282088b4f5fSWarner Losh -- module_before="value" 283e1a8835aSKyle Evans { 2841ee89ab5SKyle Evans str = MODULEEXPR .. "_before%s*=%s*$VALUE", 285088b4f5fSWarner Losh process = function(k, v) 286dbef5253SKyle Evans setKey(k, "before", v) 287e1a8835aSKyle Evans end, 288088b4f5fSWarner Losh }, 289088b4f5fSWarner Losh -- module_after="value" 290e1a8835aSKyle Evans { 2911ee89ab5SKyle Evans str = MODULEEXPR .. "_after%s*=%s*$VALUE", 292088b4f5fSWarner Losh process = function(k, v) 293dbef5253SKyle Evans setKey(k, "after", v) 294e1a8835aSKyle Evans end, 295088b4f5fSWarner Losh }, 296088b4f5fSWarner Losh -- module_error="value" 297e1a8835aSKyle Evans { 2981ee89ab5SKyle Evans str = MODULEEXPR .. "_error%s*=%s*$VALUE", 299088b4f5fSWarner Losh process = function(k, v) 300dbef5253SKyle Evans setKey(k, "error", v) 301e1a8835aSKyle Evans end, 302088b4f5fSWarner Losh }, 303088b4f5fSWarner Losh -- exec="command" 304e1a8835aSKyle Evans { 3051ee89ab5SKyle Evans str = "exec%s*=%s*" .. QVALEXPR, 306e2df27e3SKyle Evans process = function(k, _) 3079ab2d3c5SKyle Evans if cli_execute_unparsed(k) ~= 0 then 308fdabb5f5SKyle Evans print(MSG_FAILEXEC:format(k)) 309088b4f5fSWarner Losh end 310e1a8835aSKyle Evans end, 3119a16e110SKyle Evans groups = 1, 312088b4f5fSWarner Losh }, 313633f8212SKyle Evans -- env_var="value" or env_var=[word|num] 314e1a8835aSKyle Evans { 315633f8212SKyle Evans str = "([%w][%w%d-_.]*)%s*=%s*$VALUE", 316088b4f5fSWarner Losh process = function(k, v) 317633f8212SKyle Evans local pv, msg = processEnvVar(v) 318633f8212SKyle Evans if not pv then 319633f8212SKyle Evans print(MSG_FAILPARSEVAR:format(k, msg)) 320633f8212SKyle Evans return 321088b4f5fSWarner Losh end 322633f8212SKyle Evans if setEnv(k, pv) ~= 0 then 323633f8212SKyle Evans print(MSG_FAILSETENV:format(k, v)) 324088b4f5fSWarner Losh end 325e1a8835aSKyle Evans end, 326e1a8835aSKyle Evans }, 327aedd6be5SKyle Evans} 328088b4f5fSWarner Losh 329dbef5253SKyle Evanslocal function isValidComment(line) 330dbef5253SKyle Evans if line ~= nil then 331dbef5253SKyle Evans local s = line:match("^%s*#.*") 332dbef5253SKyle Evans if s == nil then 333dbef5253SKyle Evans s = line:match("^%s*$") 334dbef5253SKyle Evans end 335dbef5253SKyle Evans if s == nil then 336dbef5253SKyle Evans return false 337dbef5253SKyle Evans end 338dbef5253SKyle Evans end 339dbef5253SKyle Evans return true 340dbef5253SKyle Evansend 341dbef5253SKyle Evans 342532dc172SKyle Evanslocal function getBlacklist() 343e1f1ddebSKyle Evans local blacklist = {} 344532dc172SKyle Evans local blacklist_str = loader.getenv('module_blacklist') 345532dc172SKyle Evans if blacklist_str == nil then 346e1f1ddebSKyle Evans return blacklist 347532dc172SKyle Evans end 348532dc172SKyle Evans 349*e3cd8246SKyle Evans for mod in blacklist_str:gmatch("[;, ]?([-%w_]+)[;, ]?") do 350532dc172SKyle Evans blacklist[mod] = true 351532dc172SKyle Evans end 352532dc172SKyle Evans return blacklist 353532dc172SKyle Evansend 354532dc172SKyle Evans 355dbef5253SKyle Evanslocal function loadModule(mod, silent) 356dbef5253SKyle Evans local status = true 357532dc172SKyle Evans local blacklist = getBlacklist() 358dbef5253SKyle Evans local pstatus 359dbef5253SKyle Evans for k, v in pairs(mod) do 3608d21763eSKyle Evans if v.load ~= nil and v.load:lower() == "yes" then 361532dc172SKyle Evans local module_name = v.name or k 3624634bb1fSKyle Evans if not v.force and blacklist[module_name] ~= nil then 363532dc172SKyle Evans if not silent then 364532dc172SKyle Evans print(MSG_MODBLACKLIST:format(module_name)) 365532dc172SKyle Evans end 366532dc172SKyle Evans goto continue 367532dc172SKyle Evans end 3683078173cSKyle Evans if not silent then 3693078173cSKyle Evans loader.printc(module_name .. "...") 3703078173cSKyle Evans end 371dbef5253SKyle Evans local str = "load " 372dbef5253SKyle Evans if v.type ~= nil then 373dbef5253SKyle Evans str = str .. "-t " .. v.type .. " " 374dbef5253SKyle Evans end 375532dc172SKyle Evans str = str .. module_name 37635beb928SKyle Evans if v.flags ~= nil then 37735beb928SKyle Evans str = str .. " " .. v.flags 37835beb928SKyle Evans end 379dbef5253SKyle Evans if v.before ~= nil then 380dbef5253SKyle Evans pstatus = cli_execute_unparsed(v.before) == 0 381dbef5253SKyle Evans if not pstatus and not silent then 382dbef5253SKyle Evans print(MSG_FAILEXBEF:format(v.before, k)) 383dbef5253SKyle Evans end 384dbef5253SKyle Evans status = status and pstatus 385dbef5253SKyle Evans end 386dbef5253SKyle Evans 387dbef5253SKyle Evans if cli_execute_unparsed(str) ~= 0 then 3883078173cSKyle Evans -- XXX Temporary shim: don't break the boot if 3893078173cSKyle Evans -- loader hadn't been recompiled with this 3903078173cSKyle Evans -- function exposed. 3913078173cSKyle Evans if loader.command_error then 3923078173cSKyle Evans print(loader.command_error()) 3933078173cSKyle Evans end 394dbef5253SKyle Evans if not silent then 3953078173cSKyle Evans print("failed!") 396dbef5253SKyle Evans end 397dbef5253SKyle Evans if v.error ~= nil then 398dbef5253SKyle Evans cli_execute_unparsed(v.error) 399dbef5253SKyle Evans end 400dbef5253SKyle Evans status = false 4013078173cSKyle Evans elseif v.after ~= nil then 402dbef5253SKyle Evans pstatus = cli_execute_unparsed(v.after) == 0 403dbef5253SKyle Evans if not pstatus and not silent then 404dbef5253SKyle Evans print(MSG_FAILEXAF:format(v.after, k)) 405dbef5253SKyle Evans end 4063078173cSKyle Evans if not silent then 4073078173cSKyle Evans print("ok") 4083078173cSKyle Evans end 409dbef5253SKyle Evans status = status and pstatus 410dbef5253SKyle Evans end 411dbef5253SKyle Evans end 412532dc172SKyle Evans ::continue:: 413dbef5253SKyle Evans end 414dbef5253SKyle Evans 415dbef5253SKyle Evans return status 416dbef5253SKyle Evansend 417dbef5253SKyle Evans 418322a2dddSKyle Evanslocal function readFile(name, silent) 419164b58fdSKyle Evans local f = io.open(name) 420164b58fdSKyle Evans if f == nil then 421164b58fdSKyle Evans if not silent then 422fdabb5f5SKyle Evans print(MSG_FAILOPENCFG:format(name)) 423164b58fdSKyle Evans end 424164b58fdSKyle Evans return nil 425164b58fdSKyle Evans end 426164b58fdSKyle Evans 427164b58fdSKyle Evans local text, _ = io.read(f) 428164b58fdSKyle Evans -- We might have read in the whole file, this won't be needed any more. 429164b58fdSKyle Evans io.close(f) 430164b58fdSKyle Evans 4318d21763eSKyle Evans if text == nil and not silent then 432fdabb5f5SKyle Evans print(MSG_FAILREADCFG:format(name)) 433164b58fdSKyle Evans end 434164b58fdSKyle Evans return text 435164b58fdSKyle Evansend 436164b58fdSKyle Evans 437322a2dddSKyle Evanslocal function checkNextboot() 438be2050daSKyle Evans local nextboot_file = loader.getenv("nextboot_conf") 439e307eb94SToomas Soome local nextboot_enable = loader.getenv("nextboot_enable") 440e307eb94SToomas Soome 441ddfae7e3SKyle Evans if nextboot_file == nil then 442ddfae7e3SKyle Evans return 443ddfae7e3SKyle Evans end 444ddfae7e3SKyle Evans 445e307eb94SToomas Soome -- is nextboot_enable set in nvstore? 446e307eb94SToomas Soome if nextboot_enable == "NO" then 447e307eb94SToomas Soome return 448e307eb94SToomas Soome end 449e307eb94SToomas Soome 450322a2dddSKyle Evans local text = readFile(nextboot_file, true) 451ddfae7e3SKyle Evans if text == nil then 452ddfae7e3SKyle Evans return 453ddfae7e3SKyle Evans end 454ddfae7e3SKyle Evans 455e307eb94SToomas Soome if nextboot_enable == nil and 456e307eb94SToomas Soome text:match("^nextboot_enable=\"NO\"") ~= nil then 457ddfae7e3SKyle Evans -- We're done; nextboot is not enabled 458ddfae7e3SKyle Evans return 459ddfae7e3SKyle Evans end 460ddfae7e3SKyle Evans 461ddfae7e3SKyle Evans if not config.parse(text) then 4622f3ecc87SKyle Evans print(MSG_FAILPARSECFG:format(nextboot_file)) 463ddfae7e3SKyle Evans end 464ddfae7e3SKyle Evans 465ddfae7e3SKyle Evans -- Attempt to rewrite the first line and only the first line of the 466ddfae7e3SKyle Evans -- nextboot_file. We overwrite it with nextboot_enable="NO", then 46767eae503SKyle Evans -- check for that on load. 468ddfae7e3SKyle Evans -- It's worth noting that this won't work on every filesystem, so we 469ddfae7e3SKyle Evans -- won't do anything notable if we have any errors in this process. 470ddfae7e3SKyle Evans local nfile = io.open(nextboot_file, 'w') 471ddfae7e3SKyle Evans if nfile ~= nil then 472ddfae7e3SKyle Evans -- We need the trailing space here to account for the extra 473ddfae7e3SKyle Evans -- character taken up by the string nextboot_enable="YES" 474ddfae7e3SKyle Evans -- Or new end quotation mark lands on the S, and we want to 475ddfae7e3SKyle Evans -- rewrite the entirety of the first line. 476ddfae7e3SKyle Evans io.write(nfile, "nextboot_enable=\"NO\" ") 477ddfae7e3SKyle Evans io.close(nfile) 478ddfae7e3SKyle Evans end 479e307eb94SToomas Soome loader.setenv("nextboot_enable", "NO") 480ddfae7e3SKyle Evansend 481ddfae7e3SKyle Evans 482b5746545SKyle Evans-- Module exports 483f0b03262SKyle Evansconfig.verbose = false 484b5746545SKyle Evans 48525c4c7a5SKyle Evans-- The first item in every carousel is always the default item. 48625c4c7a5SKyle Evansfunction config.getCarouselIndex(id) 4878d21763eSKyle Evans return carousel_choices[id] or 1 48825c4c7a5SKyle Evansend 48925c4c7a5SKyle Evans 49025c4c7a5SKyle Evansfunction config.setCarouselIndex(id, idx) 491aedd6be5SKyle Evans carousel_choices[id] = idx 49225c4c7a5SKyle Evansend 49325c4c7a5SKyle Evans 494fb7275beSKyle Evans-- Returns true if we processed the file successfully, false if we did not. 495fb7275beSKyle Evans-- If 'silent' is true, being unable to read the file is not considered a 496fb7275beSKyle Evans-- failure. 497ddfae7e3SKyle Evansfunction config.processFile(name, silent) 498062d62c9SKyle Evans if silent == nil then 499062d62c9SKyle Evans silent = false 500062d62c9SKyle Evans end 501088b4f5fSWarner Losh 502322a2dddSKyle Evans local text = readFile(name, silent) 5039f71d421SKyle Evans if text == nil then 504fb7275beSKyle Evans return silent 505088b4f5fSWarner Losh end 5063dcb7648SKyle Evans 5074adde50dSKyle Evans return config.parse(text) 5084adde50dSKyle Evansend 5094adde50dSKyle Evans 5104adde50dSKyle Evans-- silent runs will not return false if we fail to open the file 5114adde50dSKyle Evansfunction config.parse(text) 512aedd6be5SKyle Evans local n = 1 513aedd6be5SKyle Evans local status = true 514088b4f5fSWarner Losh 51571049173SKyle Evans for line in text:gmatch("([^\n]+)") do 5169f71d421SKyle Evans if line:match("^%s*$") == nil then 517e2df27e3SKyle Evans for _, val in ipairs(pattern_table) do 518058c692eSKyle Evans local pattern = '^%s*' .. val.str .. '%s*(.*)'; 5199a16e110SKyle Evans local cgroups = val.groups or 2 5201ee89ab5SKyle Evans local k, v, c = checkPattern(line, pattern) 5219f71d421SKyle Evans if k ~= nil then 5229a16e110SKyle Evans -- Offset by one, drats 5239a16e110SKyle Evans if cgroups == 1 then 5249a16e110SKyle Evans c = v 5259a16e110SKyle Evans v = nil 5269a16e110SKyle Evans end 527088b4f5fSWarner Losh 528dbef5253SKyle Evans if isValidComment(c) then 529aedd6be5SKyle Evans val.process(k, v) 5309a16e110SKyle Evans goto nextline 531088b4f5fSWarner Losh end 532088b4f5fSWarner Losh 533aedd6be5SKyle Evans break 534088b4f5fSWarner Losh end 535088b4f5fSWarner Losh end 536088b4f5fSWarner Losh 537fdabb5f5SKyle Evans print(MSG_MALFORMED:format(n, line)) 538aedd6be5SKyle Evans status = false 539088b4f5fSWarner Losh end 5409a16e110SKyle Evans ::nextline:: 541aedd6be5SKyle Evans n = n + 1 542088b4f5fSWarner Losh end 543088b4f5fSWarner Losh 544aedd6be5SKyle Evans return status 545088b4f5fSWarner Loshend 546088b4f5fSWarner Losh 5473fe0ac6aSKyle Evansfunction config.readConf(file, loaded_files) 5483fe0ac6aSKyle Evans if loaded_files == nil then 5493fe0ac6aSKyle Evans loaded_files = {} 5507a5c6c8bSKyle Evans end 5517a5c6c8bSKyle Evans 5523fe0ac6aSKyle Evans if loaded_files[file] ~= nil then 5533fe0ac6aSKyle Evans return 5543fe0ac6aSKyle Evans end 5553fe0ac6aSKyle Evans 55672cf7db3SKyle Evans -- We'll process loader_conf_dirs at the top-level readConf 55772cf7db3SKyle Evans local load_conf_dirs = next(loaded_files) == nil 5583fe0ac6aSKyle Evans print("Loading " .. file) 5593fe0ac6aSKyle Evans 5603fe0ac6aSKyle Evans -- The final value of loader_conf_files is not important, so just 5613fe0ac6aSKyle Evans -- clobber it here. We'll later check if it's no longer nil and process 5623fe0ac6aSKyle Evans -- the new value for files to read. 5633fe0ac6aSKyle Evans setEnv("loader_conf_files", nil) 5643fe0ac6aSKyle Evans 5657a5c6c8bSKyle Evans -- These may or may not exist, and that's ok. Do a 5667a5c6c8bSKyle Evans -- silent parse so that we complain on parse errors but 5677a5c6c8bSKyle Evans -- not for them simply not existing. 5683fe0ac6aSKyle Evans if not config.processFile(file, true) then 5693fe0ac6aSKyle Evans print(MSG_FAILPARSECFG:format(file)) 5707a5c6c8bSKyle Evans end 5717a5c6c8bSKyle Evans 5723fe0ac6aSKyle Evans loaded_files[file] = true 5733fe0ac6aSKyle Evans 5743fe0ac6aSKyle Evans -- Going to process "loader_conf_files" extra-files 5753fe0ac6aSKyle Evans local loader_conf_files = getEnv("loader_conf_files") 5763fe0ac6aSKyle Evans if loader_conf_files ~= nil then 5773fe0ac6aSKyle Evans for name in loader_conf_files:gmatch("[%w%p]+") do 5783fe0ac6aSKyle Evans config.readConf(name, loaded_files) 5797a5c6c8bSKyle Evans end 5807a5c6c8bSKyle Evans end 58172cf7db3SKyle Evans 58272cf7db3SKyle Evans if load_conf_dirs then 58372cf7db3SKyle Evans local loader_conf_dirs = getEnv("loader_conf_dirs") 58472cf7db3SKyle Evans if loader_conf_dirs ~= nil then 58572cf7db3SKyle Evans for name in loader_conf_dirs:gmatch("[%w%p]+") do 58672cf7db3SKyle Evans if lfs.attributes(name, "mode") ~= "directory" then 58772cf7db3SKyle Evans print(MSG_FAILDIR:format(name)) 58872cf7db3SKyle Evans goto nextdir 58972cf7db3SKyle Evans end 59072cf7db3SKyle Evans for cfile in lfs.dir(name) do 59172cf7db3SKyle Evans if cfile:match(".conf$") then 59272cf7db3SKyle Evans local fpath = name .. "/" .. cfile 59372cf7db3SKyle Evans if lfs.attributes(fpath, "mode") == "file" then 59472cf7db3SKyle Evans config.readConf(fpath, loaded_files) 59572cf7db3SKyle Evans end 59672cf7db3SKyle Evans end 59772cf7db3SKyle Evans end 59872cf7db3SKyle Evans ::nextdir:: 59972cf7db3SKyle Evans end 60072cf7db3SKyle Evans end 60172cf7db3SKyle Evans end 6027a5c6c8bSKyle Evansend 6037a5c6c8bSKyle Evans 60418c286a0SKyle Evans-- other_kernel is optionally the name of a kernel to load, if not the default 60518c286a0SKyle Evans-- or autoloaded default from the module_path 606322a2dddSKyle Evansfunction config.loadKernel(other_kernel) 607aedd6be5SKyle Evans local flags = loader.getenv("kernel_options") or "" 608aedd6be5SKyle Evans local kernel = other_kernel or loader.getenv("kernel") 609088b4f5fSWarner Losh 610322a2dddSKyle Evans local function tryLoad(names) 611088b4f5fSWarner Losh for name in names:gmatch("([^;]+)%s*;?") do 61280eb81f6SKyle Evans local r = loader.perform("load " .. name .. 61380eb81f6SKyle Evans " " .. flags) 6149f71d421SKyle Evans if r == 0 then 615aedd6be5SKyle Evans return name 616088b4f5fSWarner Losh end 617088b4f5fSWarner Losh end 618aedd6be5SKyle Evans return nil 61918c286a0SKyle Evans end 620088b4f5fSWarner Losh 6215dd1b834SKyle Evans local function getModulePath() 6225dd1b834SKyle Evans local module_path = loader.getenv("module_path") 6235dd1b834SKyle Evans local kernel_path = loader.getenv("kernel_path") 6245dd1b834SKyle Evans 6255dd1b834SKyle Evans if kernel_path == nil then 6265dd1b834SKyle Evans return module_path 6275dd1b834SKyle Evans end 6285dd1b834SKyle Evans 6295dd1b834SKyle Evans -- Strip the loaded kernel path from module_path. This currently assumes 6305dd1b834SKyle Evans -- that the kernel path will be prepended to the module_path when it's 6315dd1b834SKyle Evans -- found. 6325dd1b834SKyle Evans kernel_path = escapeName(kernel_path .. ';') 6335dd1b834SKyle Evans return module_path:gsub(kernel_path, '') 6345dd1b834SKyle Evans end 6355dd1b834SKyle Evans 636322a2dddSKyle Evans local function loadBootfile() 637aedd6be5SKyle Evans local bootfile = loader.getenv("bootfile") 638088b4f5fSWarner Losh 639088b4f5fSWarner Losh -- append default kernel name 6409f71d421SKyle Evans if bootfile == nil then 641aedd6be5SKyle Evans bootfile = "kernel" 642088b4f5fSWarner Losh else 643aedd6be5SKyle Evans bootfile = bootfile .. ";kernel" 644088b4f5fSWarner Losh end 645088b4f5fSWarner Losh 646322a2dddSKyle Evans return tryLoad(bootfile) 64724a1bd54SKyle Evans end 648088b4f5fSWarner Losh 649088b4f5fSWarner Losh -- kernel not set, try load from default module_path 6509f71d421SKyle Evans if kernel == nil then 651322a2dddSKyle Evans local res = loadBootfile() 652088b4f5fSWarner Losh 6539f71d421SKyle Evans if res ~= nil then 654d4591301SKyle Evans -- Default kernel is loaded 655aedd6be5SKyle Evans config.kernel_loaded = nil 656aedd6be5SKyle Evans return true 657088b4f5fSWarner Losh else 658fdabb5f5SKyle Evans print(MSG_DEFAULTKERNFAIL) 659aedd6be5SKyle Evans return false 660088b4f5fSWarner Losh end 661088b4f5fSWarner Losh else 66215d8e03cSKyle Evans -- Use our cached module_path, so we don't end up with multiple 66315d8e03cSKyle Evans -- automatically added kernel paths to our final module_path 6645dd1b834SKyle Evans local module_path = getModulePath() 665e2df27e3SKyle Evans local res 666088b4f5fSWarner Losh 6679f71d421SKyle Evans if other_kernel ~= nil then 668aedd6be5SKyle Evans kernel = other_kernel 66918c286a0SKyle Evans end 670088b4f5fSWarner Losh -- first try load kernel with module_path = /boot/${kernel} 671088b4f5fSWarner Losh -- then try load with module_path=${kernel} 672aedd6be5SKyle Evans local paths = {"/boot/" .. kernel, kernel} 673088b4f5fSWarner Losh 674e2df27e3SKyle Evans for _, v in pairs(paths) do 675aedd6be5SKyle Evans loader.setenv("module_path", v) 676322a2dddSKyle Evans res = loadBootfile() 677088b4f5fSWarner Losh 67815d8e03cSKyle Evans -- succeeded, add path to module_path 6799f71d421SKyle Evans if res ~= nil then 680aedd6be5SKyle Evans config.kernel_loaded = kernel 6819f71d421SKyle Evans if module_path ~= nil then 682c990f0a9SKyle Evans loader.setenv("module_path", v .. ";" .. 683aedd6be5SKyle Evans module_path) 6845dd1b834SKyle Evans loader.setenv("kernel_path", v) 685c990f0a9SKyle Evans end 686aedd6be5SKyle Evans return true 687088b4f5fSWarner Losh end 688088b4f5fSWarner Losh end 689088b4f5fSWarner Losh 690088b4f5fSWarner Losh -- failed to load with ${kernel} as a directory 691088b4f5fSWarner Losh -- try as a file 692322a2dddSKyle Evans res = tryLoad(kernel) 6939f71d421SKyle Evans if res ~= nil then 694aedd6be5SKyle Evans config.kernel_loaded = kernel 695aedd6be5SKyle Evans return true 696088b4f5fSWarner Losh else 697fdabb5f5SKyle Evans print(MSG_KERNFAIL:format(kernel)) 698aedd6be5SKyle Evans return false 699088b4f5fSWarner Losh end 700088b4f5fSWarner Losh end 701088b4f5fSWarner Loshend 702088b4f5fSWarner Losh 703322a2dddSKyle Evansfunction config.selectKernel(kernel) 704aedd6be5SKyle Evans config.kernel_selected = kernel 705fa4a2394SKyle Evansend 706088b4f5fSWarner Losh 7077aba5b2fSKyle Evansfunction config.load(file, reloading) 7089f71d421SKyle Evans if not file then 709aedd6be5SKyle Evans file = "/boot/defaults/loader.conf" 710088b4f5fSWarner Losh end 711088b4f5fSWarner Losh 7123fe0ac6aSKyle Evans config.readConf(file) 713088b4f5fSWarner Losh 714322a2dddSKyle Evans checkNextboot() 7153dcb7648SKyle Evans 7168d21763eSKyle Evans local verbose = loader.getenv("verbose_loading") or "no" 717f0b03262SKyle Evans config.verbose = verbose:lower() == "yes" 7187aba5b2fSKyle Evans if not reloading then 7197aba5b2fSKyle Evans hook.runAll("config.loaded") 7207aba5b2fSKyle Evans end 721fa4a2394SKyle Evansend 722fa4a2394SKyle Evans 723fa4a2394SKyle Evans-- Reload configuration 724fa4a2394SKyle Evansfunction config.reload(file) 725aedd6be5SKyle Evans modules = {} 72664c91742SKyle Evans restoreEnv() 7277aba5b2fSKyle Evans config.load(file, true) 728aea262bfSKyle Evans hook.runAll("config.reloaded") 729fa4a2394SKyle Evansend 730fa4a2394SKyle Evans 731fa4a2394SKyle Evansfunction config.loadelf() 7320d7bee6aSKyle Evans local xen_kernel = loader.getenv('xen_kernel') 733aedd6be5SKyle Evans local kernel = config.kernel_selected or config.kernel_loaded 73479c20f22SRyan Moeller local status 73515d8e03cSKyle Evans 7360d7bee6aSKyle Evans if xen_kernel ~= nil then 7370d7bee6aSKyle Evans print(MSG_XENKERNLOADING) 7380d7bee6aSKyle Evans if cli_execute_unparsed('load ' .. xen_kernel) ~= 0 then 7390d7bee6aSKyle Evans print(MSG_XENKERNFAIL:format(xen_kernel)) 7403078173cSKyle Evans return false 7410d7bee6aSKyle Evans end 7420d7bee6aSKyle Evans end 743fdabb5f5SKyle Evans print(MSG_KERNLOADING) 74479c20f22SRyan Moeller if not config.loadKernel(kernel) then 7453078173cSKyle Evans return false 746fa4a2394SKyle Evans end 74779c20f22SRyan Moeller hook.runAll("kernel.loaded") 748088b4f5fSWarner Losh 749fdabb5f5SKyle Evans print(MSG_MODLOADING) 7500db2ca0cSKyle Evans status = loadModule(modules, not config.verbose) 7510db2ca0cSKyle Evans hook.runAll("modules.loaded") 7520db2ca0cSKyle Evans return status 753088b4f5fSWarner Loshend 754088b4f5fSWarner Losh 7554634bb1fSKyle Evansfunction config.enableModule(modname) 7564634bb1fSKyle Evans if modules[modname] == nil then 7574634bb1fSKyle Evans modules[modname] = {} 7584634bb1fSKyle Evans elseif modules[modname].load == "YES" then 7594634bb1fSKyle Evans modules[modname].force = true 7604634bb1fSKyle Evans return true 7614634bb1fSKyle Evans end 7624634bb1fSKyle Evans 7634634bb1fSKyle Evans modules[modname].load = "YES" 7644634bb1fSKyle Evans modules[modname].force = true 7654634bb1fSKyle Evans return true 7664634bb1fSKyle Evansend 7674634bb1fSKyle Evans 7684634bb1fSKyle Evansfunction config.disableModule(modname) 7694634bb1fSKyle Evans if modules[modname] == nil then 7704634bb1fSKyle Evans return false 7714634bb1fSKyle Evans elseif modules[modname].load ~= "YES" then 7724634bb1fSKyle Evans return true 7734634bb1fSKyle Evans end 7744634bb1fSKyle Evans 7754634bb1fSKyle Evans modules[modname].load = "NO" 7764634bb1fSKyle Evans modules[modname].force = nil 7774634bb1fSKyle Evans return true 7784634bb1fSKyle Evansend 7794634bb1fSKyle Evans 7804634bb1fSKyle Evansfunction config.isModuleEnabled(modname) 7814634bb1fSKyle Evans local mod = modules[modname] 7824634bb1fSKyle Evans if not mod or mod.load ~= "YES" then 7834634bb1fSKyle Evans return false 7844634bb1fSKyle Evans end 7854634bb1fSKyle Evans 7864634bb1fSKyle Evans if mod.force then 7874634bb1fSKyle Evans return true 7884634bb1fSKyle Evans end 7894634bb1fSKyle Evans 7904634bb1fSKyle Evans local blacklist = getBlacklist() 79110aeb6cdSKyle Evans return not blacklist[modname] 7924634bb1fSKyle Evansend 7934634bb1fSKyle Evans 7947ed84fa1SKyle Evansfunction config.getModuleInfo() 7957ed84fa1SKyle Evans return { 7967ed84fa1SKyle Evans modules = modules, 7977ed84fa1SKyle Evans blacklist = getBlacklist() 7987ed84fa1SKyle Evans } 7997ed84fa1SKyle Evansend 8007ed84fa1SKyle Evans 8017aba5b2fSKyle Evanshook.registerType("config.loaded") 802aea262bfSKyle Evanshook.registerType("config.reloaded") 8034bee6189SRyan Moellerhook.registerType("kernel.loaded") 8040db2ca0cSKyle Evanshook.registerType("modules.loaded") 805aedd6be5SKyle Evansreturn config 806