xref: /freebsd/stand/lua/core.lua (revision 07c17b2b00d8c1c8a2d58d4d8f99e64ec1182476)
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 compose_loader_cmd(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(b)
58	if b == nil then
59		b = not core.verbose
60	end
61
62	if b then
63		loader.setenv("boot_verbose", "YES")
64	else
65		loader.unsetenv("boot_verbose")
66	end
67	core.verbose = b
68end
69
70function core.setSingleUser(b)
71	if b == nil then
72		b = not core.su
73	end
74
75	if b then
76		loader.setenv("boot_single", "YES")
77	else
78		loader.unsetenv("boot_single")
79	end
80	core.su = b
81end
82
83function core.getACPIPresent(checkingSystemDefaults)
84	local c = loader.getenv("hint.acpi.0.rsdp")
85
86	if c ~= nil then
87		if checkingSystemDefaults 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(b)
98	if b == nil then
99		b = not core.acpi
100	end
101
102	if b 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 = b
112end
113
114function core.setSafeMode(b)
115	if b == nil then
116		b = not core.sm
117	end
118	if b 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 = b
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(compose_loader_cmd("autoboot", argstr))
242end
243
244function core.boot(argstr)
245	config.loadelf()
246	loader.perform(compose_loader_cmd("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-- This may be a better candidate for a 'utility' module.
289function core.deepCopyTable(tbl)
290	local new_tbl = {}
291	for k, v in pairs(tbl) do
292		if type(v) == "table" then
293			new_tbl[k] = core.deepCopyTable(v)
294		else
295			new_tbl[k] = v
296		end
297	end
298	return new_tbl
299end
300
301-- XXX This should go away if we get the table lib into shape for importing.
302-- As of now, it requires some 'os' functions, so we'll implement this in lua
303-- for our uses
304function core.popFrontTable(tbl)
305	-- Shouldn't reasonably happen
306	if #tbl == 0 then
307		return nil, nil
308	elseif #tbl == 1 then
309		return tbl[1], {}
310	end
311
312	local first_value = tbl[1]
313	local new_tbl = {}
314	-- This is not a cheap operation
315	for k, v in ipairs(tbl) do
316		if k > 1 then
317			new_tbl[k - 1] = v
318		end
319	end
320
321	return first_value, new_tbl
322end
323
324-- On i386, hint.acpi.0.rsdp will be set before we're loaded. On !i386, it will
325-- generally be set upon execution of the kernel. Because of this, we can't (or
326-- don't really want to) detect/disable ACPI on !i386 reliably. Just set it
327-- enabled if we detect it and leave well enough alone if we don't.
328if core.isSystem386() and core.getACPIPresent(false) then
329	core.setACPI(true)
330end
331return core
332