xref: /freebsd/stand/lua/core.lua (revision 5bf5ca772c6de2d53344a78cf461447cc322ccea)
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