xref: /freebsd-13.1/stand/lua/core.lua (revision 2bb86aef)
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