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