xref: /freebsd-14.2/stand/lua/core.lua (revision 3049d4cc)
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