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