1-- 2-- SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3-- 4-- Copyright (c) 2015 Pedro Souza <[email protected]> 5-- Copyright (c) 2018 Kyle Evans <[email protected]> 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 = require("config") 33 34local core = {} 35 36local function composeLoaderCmd(cmd_name, argstr) 37 if argstr ~= nil then 38 cmd_name = cmd_name .. " " .. argstr 39 end 40 return cmd_name 41end 42 43-- Module exports 44-- Commonly appearing constants 45core.KEY_BACKSPACE = 8 46core.KEY_ENTER = 13 47core.KEY_DELETE = 127 48 49core.KEYSTR_ESCAPE = "\027" 50 51core.MENU_RETURN = "return" 52core.MENU_ENTRY = "entry" 53core.MENU_SEPARATOR = "separator" 54core.MENU_SUBMENU = "submenu" 55core.MENU_CAROUSEL_ENTRY = "carousel_entry" 56 57function core.setVerbose(verbose) 58 if verbose == nil then 59 verbose = not core.verbose 60 end 61 62 if verbose then 63 loader.setenv("boot_verbose", "YES") 64 else 65 loader.unsetenv("boot_verbose") 66 end 67 core.verbose = verbose 68end 69 70function core.setSingleUser(single_user) 71 if single_user == nil then 72 single_user = not core.su 73 end 74 75 if single_user then 76 loader.setenv("boot_single", "YES") 77 else 78 loader.unsetenv("boot_single") 79 end 80 core.su = single_user 81end 82 83function core.getACPIPresent(checking_system_defaults) 84 local c = loader.getenv("hint.acpi.0.rsdp") 85 86 if c ~= nil then 87 if checking_system_defaults then 88 return true 89 end 90 -- Otherwise, respect disabled if it's set 91 c = loader.getenv("hint.acpi.0.disabled") 92 return c == nil or tonumber(c) ~= 1 93 end 94 return false 95end 96 97function core.setACPI(acpi) 98 if acpi == nil then 99 acpi = not core.acpi 100 end 101 102 if acpi then 103 loader.setenv("acpi_load", "YES") 104 loader.setenv("hint.acpi.0.disabled", "0") 105 loader.unsetenv("loader.acpi_disabled_by_user") 106 else 107 loader.unsetenv("acpi_load") 108 loader.setenv("hint.acpi.0.disabled", "1") 109 loader.setenv("loader.acpi_disabled_by_user", "1") 110 end 111 core.acpi = acpi 112end 113 114function core.setSafeMode(safe_mode) 115 if safe_mode == nil then 116 safe_mode = not core.sm 117 end 118 if safe_mode then 119 loader.setenv("kern.smp.disabled", "1") 120 loader.setenv("hw.ata.ata_dma", "0") 121 loader.setenv("hw.ata.atapi_dma", "0") 122 loader.setenv("hw.ata.wc", "0") 123 loader.setenv("hw.eisa_slots", "0") 124 loader.setenv("kern.eventtimer.periodic", "1") 125 loader.setenv("kern.geom.part.check_integrity", "0") 126 else 127 loader.unsetenv("kern.smp.disabled") 128 loader.unsetenv("hw.ata.ata_dma") 129 loader.unsetenv("hw.ata.atapi_dma") 130 loader.unsetenv("hw.ata.wc") 131 loader.unsetenv("hw.eisa_slots") 132 loader.unsetenv("kern.eventtimer.periodic") 133 loader.unsetenv("kern.geom.part.check_integrity") 134 end 135 core.sm = safe_mode 136end 137 138function core.kernelList() 139 local k = loader.getenv("kernel") 140 local v = loader.getenv("kernels") 141 local autodetect = loader.getenv("kernels_autodetect") or "" 142 143 local kernels = {} 144 local unique = {} 145 local i = 0 146 if k ~= nil then 147 i = i + 1 148 kernels[i] = k 149 unique[k] = true 150 end 151 152 if v ~= nil then 153 for n in v:gmatch("([^; ]+)[; ]?") do 154 if unique[n] == nil then 155 i = i + 1 156 kernels[i] = n 157 unique[n] = true 158 end 159 end 160 end 161 162 -- Base whether we autodetect kernels or not on a loader.conf(5) 163 -- setting, kernels_autodetect. If it's set to 'yes', we'll add 164 -- any kernels we detect based on the criteria described. 165 if autodetect:lower() ~= "yes" then 166 return kernels 167 end 168 169 -- Automatically detect other bootable kernel directories using a 170 -- heuristic. Any directory in /boot that contains an ordinary file 171 -- named "kernel" is considered eligible. 172 for file in lfs.dir("/boot") do 173 local fname = "/boot/" .. file 174 175 if file == "." or file == ".." then 176 goto continue 177 end 178 179 if lfs.attributes(fname, "mode") ~= "directory" then 180 goto continue 181 end 182 183 if lfs.attributes(fname .. "/kernel", "mode") ~= "file" then 184 goto continue 185 end 186 187 if unique[file] == nil then 188 i = i + 1 189 kernels[i] = file 190 unique[file] = true 191 end 192 193 ::continue:: 194 end 195 return kernels 196end 197 198function core.bootenvDefault() 199 return loader.getenv("zfs_be_active") 200end 201 202function core.bootenvList() 203 local bootenv_count = tonumber(loader.getenv("bootenvs_count")) 204 local bootenvs = {} 205 local curenv 206 local envcount = 0 207 local unique = {} 208 209 if bootenv_count == nil or bootenv_count <= 0 then 210 return bootenvs 211 end 212 213 -- Currently selected bootenv is always first/default 214 curenv = core.bootenvDefault() 215 if curenv ~= nil then 216 envcount = envcount + 1 217 bootenvs[envcount] = curenv 218 unique[curenv] = true 219 end 220 221 for curenv_idx = 0, bootenv_count - 1 do 222 curenv = loader.getenv("bootenvs[" .. curenv_idx .. "]") 223 if curenv ~= nil and unique[curenv] == nil then 224 envcount = envcount + 1 225 bootenvs[envcount] = curenv 226 unique[curenv] = true 227 end 228 end 229 return bootenvs 230end 231 232function core.setDefaults() 233 core.setACPI(core.getACPIPresent(true)) 234 core.setSafeMode(false) 235 core.setSingleUser(false) 236 core.setVerbose(false) 237end 238 239function core.autoboot(argstr) 240 config.loadelf() 241 loader.perform(composeLoaderCmd("autoboot", argstr)) 242end 243 244function core.boot(argstr) 245 config.loadelf() 246 loader.perform(composeLoaderCmd("boot", argstr)) 247end 248 249function core.isSingleUserBoot() 250 local single_user = loader.getenv("boot_single") 251 return single_user ~= nil and single_user:lower() == "yes" 252end 253 254function core.isZFSBoot() 255 local c = loader.getenv("currdev") 256 257 if c ~= nil then 258 return c:match("^zfs:") ~= nil 259 end 260 return false 261end 262 263function core.isSerialBoot() 264 local c = loader.getenv("console") 265 266 if c ~= nil then 267 if c:find("comconsole") ~= nil then 268 return true 269 end 270 end 271 272 local s = loader.getenv("boot_serial") 273 if s ~= nil then 274 return true 275 end 276 277 local m = loader.getenv("boot_multicons") 278 if m ~= nil then 279 return true 280 end 281 return false 282end 283 284function core.isSystem386() 285 return loader.machine_arch == "i386" 286end 287 288-- Is the menu skipped in the environment in which we've booted? 289function core.isMenuSkipped() 290 if core.isSerialBoot() then 291 return true 292 end 293 local c = string.lower(loader.getenv("console") or "") 294 if c:match("^efi[ ;]") ~= nil or c:match("[ ;]efi[ ;]") ~= nil then 295 return true 296 end 297 298 c = string.lower(loader.getenv("beastie_disable") or "") 299 return c == "yes" 300end 301 302-- This may be a better candidate for a 'utility' module. 303function core.deepCopyTable(tbl) 304 local new_tbl = {} 305 for k, v in pairs(tbl) do 306 if type(v) == "table" then 307 new_tbl[k] = core.deepCopyTable(v) 308 else 309 new_tbl[k] = v 310 end 311 end 312 return new_tbl 313end 314 315-- XXX This should go away if we get the table lib into shape for importing. 316-- As of now, it requires some 'os' functions, so we'll implement this in lua 317-- for our uses 318function core.popFrontTable(tbl) 319 -- Shouldn't reasonably happen 320 if #tbl == 0 then 321 return nil, nil 322 elseif #tbl == 1 then 323 return tbl[1], {} 324 end 325 326 local first_value = tbl[1] 327 local new_tbl = {} 328 -- This is not a cheap operation 329 for k, v in ipairs(tbl) do 330 if k > 1 then 331 new_tbl[k - 1] = v 332 end 333 end 334 335 return first_value, new_tbl 336end 337 338-- On i386, hint.acpi.0.rsdp will be set before we're loaded. On !i386, it will 339-- generally be set upon execution of the kernel. Because of this, we can't (or 340-- don't really want to) detect/disable ACPI on !i386 reliably. Just set it 341-- enabled if we detect it and leave well enough alone if we don't. 342if core.isSystem386() and core.getACPIPresent(false) then 343 core.setACPI(true) 344end 345return core 346