Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/stand/lua/core.lua
34677 views
1
--
2
-- SPDX-License-Identifier: BSD-2-Clause
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
30
local config = require("config")
31
local hook = require("hook")
32
33
local core = {}
34
35
local default_acpi = false
36
local default_safe_mode = false
37
local default_single_user = false
38
local default_verbose = false
39
40
local bootenv_list = "bootenvs"
41
42
local function composeLoaderCmd(cmd_name, argstr)
43
if argstr ~= nil then
44
cmd_name = cmd_name .. " " .. argstr
45
end
46
return cmd_name
47
end
48
49
local function recordDefaults()
50
local boot_single = loader.getenv("boot_single") or "no"
51
local boot_verbose = loader.getenv("boot_verbose") or "no"
52
53
default_acpi = core.getACPI()
54
default_single_user = boot_single:lower() ~= "no"
55
default_verbose = boot_verbose:lower() ~= "no"
56
57
core.setACPI(default_acpi)
58
core.setSingleUser(default_single_user)
59
core.setVerbose(default_verbose)
60
end
61
62
63
-- Globals
64
-- try_include will return the loaded module on success, or false and the error
65
-- message on failure.
66
function try_include(module)
67
if module:sub(1, 1) ~= "/" then
68
local lua_path = loader.lua_path
69
-- XXX Temporary compat shim; this should be removed once the
70
-- loader.lua_path export has sufficiently spread.
71
if lua_path == nil then
72
lua_path = "/boot/lua"
73
end
74
module = lua_path .. "/" .. module
75
-- We only attempt to append an extension if an absolute path
76
-- wasn't specified. This assumes that the caller either wants
77
-- to treat this like it would require() and specify just the
78
-- base filename, or they know what they're doing as they've
79
-- specified an absolute path and we shouldn't impede.
80
if module:match(".lua$") == nil then
81
module = module .. ".lua"
82
end
83
end
84
if lfs.attributes(module, "mode") ~= "file" then
85
return
86
end
87
88
return dofile(module)
89
end
90
91
-- Module exports
92
-- Commonly appearing constants
93
core.KEY_BACKSPACE = 8
94
core.KEY_ENTER = 13
95
core.KEY_DELETE = 127
96
97
-- Note that this is a decimal representation, despite the leading 0 that in
98
-- other contexts (outside of Lua) may mean 'octal'
99
core.KEYSTR_ESCAPE = "\027"
100
core.KEYSTR_CSI = core.KEYSTR_ESCAPE .. "["
101
core.KEYSTR_RESET = core.KEYSTR_ESCAPE .. "c"
102
103
core.MENU_RETURN = "return"
104
core.MENU_ENTRY = "entry"
105
core.MENU_SEPARATOR = "separator"
106
core.MENU_SUBMENU = "submenu"
107
core.MENU_CAROUSEL_ENTRY = "carousel_entry"
108
109
function core.setVerbose(verbose)
110
if verbose == nil then
111
verbose = not core.verbose
112
end
113
114
if verbose then
115
loader.setenv("boot_verbose", "YES")
116
else
117
loader.unsetenv("boot_verbose")
118
end
119
core.verbose = verbose
120
end
121
122
function core.setSingleUser(single_user)
123
if single_user == nil then
124
single_user = not core.su
125
end
126
127
if single_user then
128
loader.setenv("boot_single", "YES")
129
else
130
loader.unsetenv("boot_single")
131
end
132
core.su = single_user
133
end
134
135
function core.hasACPI()
136
-- We can't trust acpi.rsdp to be set if the loader binary doesn't do
137
-- ACPI detection early enough. UEFI loader historically didn't, so
138
-- we'll fallback to assuming ACPI is enabled if this binary does not
139
-- declare that it probes for ACPI early enough
140
if loader.getenv("acpi.rsdp") ~= nil then
141
return true
142
end
143
144
return not core.hasFeature("EARLY_ACPI")
145
end
146
147
function core.getACPI()
148
if not core.hasACPI() then
149
return false
150
end
151
152
-- Otherwise, respect disabled if it's set
153
local c = loader.getenv("hint.acpi.0.disabled")
154
return c == nil or tonumber(c) ~= 1
155
end
156
157
function core.setACPI(acpi)
158
if acpi == nil then
159
acpi = not core.acpi
160
end
161
162
if acpi then
163
config.enableModule("acpi")
164
loader.setenv("hint.acpi.0.disabled", "0")
165
else
166
config.disableModule("acpi")
167
loader.setenv("hint.acpi.0.disabled", "1")
168
end
169
core.acpi = acpi
170
end
171
172
function core.setSafeMode(safe_mode)
173
if safe_mode == nil then
174
safe_mode = not core.sm
175
end
176
if safe_mode then
177
loader.setenv("kern.smp.disabled", "1")
178
loader.setenv("hw.ata.ata_dma", "0")
179
loader.setenv("hw.ata.atapi_dma", "0")
180
loader.setenv("kern.eventtimer.periodic", "1")
181
loader.setenv("kern.geom.part.check_integrity", "0")
182
loader.setenv("boot_safe", "YES")
183
else
184
loader.unsetenv("kern.smp.disabled")
185
loader.unsetenv("hw.ata.ata_dma")
186
loader.unsetenv("hw.ata.atapi_dma")
187
loader.unsetenv("kern.eventtimer.periodic")
188
loader.unsetenv("kern.geom.part.check_integrity")
189
loader.unsetenv("boot_safe")
190
end
191
core.sm = safe_mode
192
end
193
194
function core.clearCachedKernels()
195
-- Clear the kernel cache on config changes, autodetect might have
196
-- changed or if we've switched boot environments then we could have
197
-- a new kernel set.
198
core.cached_kernels = nil
199
end
200
201
function core.kernelList()
202
if core.cached_kernels ~= nil then
203
return core.cached_kernels
204
end
205
206
local default_kernel = loader.getenv("kernel")
207
local v = loader.getenv("kernels")
208
local autodetect = loader.getenv("kernels_autodetect") or ""
209
210
local kernels = {}
211
local unique = {}
212
local i = 0
213
214
if default_kernel then
215
i = i + 1
216
kernels[i] = default_kernel
217
unique[default_kernel] = true
218
end
219
220
if v ~= nil then
221
for n in v:gmatch("([^;, ]+)[;, ]?") do
222
if unique[n] == nil then
223
i = i + 1
224
kernels[i] = n
225
unique[n] = true
226
end
227
end
228
end
229
230
-- Do not attempt to autodetect if underlying filesystem
231
-- do not support directory listing (e.g. tftp, http)
232
if not lfs.attributes("/boot", "mode") then
233
autodetect = "no"
234
loader.setenv("kernels_autodetect", "NO")
235
end
236
237
-- Base whether we autodetect kernels or not on a loader.conf(5)
238
-- setting, kernels_autodetect. If it's set to 'yes', we'll add
239
-- any kernels we detect based on the criteria described.
240
if autodetect:lower() ~= "yes" then
241
core.cached_kernels = kernels
242
return core.cached_kernels
243
end
244
245
local present = {}
246
247
-- Automatically detect other bootable kernel directories using a
248
-- heuristic. Any directory in /boot that contains an ordinary file
249
-- named "kernel" is considered eligible.
250
for file, ftype in lfs.dir("/boot") do
251
local fname = "/boot/" .. file
252
253
if file == "." or file == ".." then
254
goto continue
255
end
256
257
if ftype then
258
if ftype ~= lfs.DT_DIR then
259
goto continue
260
end
261
elseif lfs.attributes(fname, "mode") ~= "directory" then
262
goto continue
263
end
264
265
if lfs.attributes(fname .. "/kernel", "mode") ~= "file" then
266
goto continue
267
end
268
269
if unique[file] == nil then
270
i = i + 1
271
kernels[i] = file
272
unique[file] = true
273
end
274
275
present[file] = true
276
277
::continue::
278
end
279
280
-- If we found more than one kernel, prune the "kernel" specified kernel
281
-- off of the list if it wasn't found during traversal. If we didn't
282
-- actually find any kernels, we just assume that they know what they're
283
-- doing and leave it alone.
284
if default_kernel and not present[default_kernel] and #kernels > 1 then
285
for n = 1, #kernels do
286
if n == #kernels then
287
kernels[n] = nil
288
else
289
kernels[n] = kernels[n + 1]
290
end
291
end
292
end
293
294
core.cached_kernels = kernels
295
return core.cached_kernels
296
end
297
298
function core.bootenvDefault()
299
return loader.getenv("zfs_be_active")
300
end
301
302
function core.bootenvList()
303
local bootenv_count = tonumber(loader.getenv(bootenv_list .. "_count"))
304
local bootenvs = {}
305
local curenv
306
local envcount = 0
307
local unique = {}
308
309
if bootenv_count == nil or bootenv_count <= 0 then
310
return bootenvs
311
end
312
313
-- Currently selected bootenv is always first/default
314
-- On the rewinded list the bootenv may not exists
315
if core.isRewinded() then
316
curenv = core.bootenvDefaultRewinded()
317
else
318
curenv = core.bootenvDefault()
319
end
320
if curenv ~= nil then
321
envcount = envcount + 1
322
bootenvs[envcount] = curenv
323
unique[curenv] = true
324
end
325
326
for curenv_idx = 0, bootenv_count - 1 do
327
curenv = loader.getenv(bootenv_list .. "[" .. curenv_idx .. "]")
328
if curenv ~= nil and unique[curenv] == nil then
329
envcount = envcount + 1
330
bootenvs[envcount] = curenv
331
unique[curenv] = true
332
end
333
end
334
return bootenvs
335
end
336
337
function core.isCheckpointed()
338
return loader.getenv("zpool_checkpoint") ~= nil
339
end
340
341
function core.bootenvDefaultRewinded()
342
local defname = "zfs:!" .. string.sub(core.bootenvDefault(), 5)
343
local bootenv_count = tonumber("bootenvs_check_count")
344
345
if bootenv_count == nil or bootenv_count <= 0 then
346
return defname
347
end
348
349
for curenv_idx = 0, bootenv_count - 1 do
350
local curenv = loader.getenv("bootenvs_check[" .. curenv_idx .. "]")
351
if curenv == defname then
352
return defname
353
end
354
end
355
356
return loader.getenv("bootenvs_check[0]")
357
end
358
359
function core.isRewinded()
360
return bootenv_list == "bootenvs_check"
361
end
362
363
function core.changeRewindCheckpoint()
364
if core.isRewinded() then
365
bootenv_list = "bootenvs"
366
else
367
bootenv_list = "bootenvs_check"
368
end
369
end
370
371
function core.loadEntropy()
372
if core.isUEFIBoot() then
373
if (loader.getenv("entropy_efi_seed") or "no"):lower() == "yes" then
374
local seedsize = loader.getenv("entropy_efi_seed_size") or "2048"
375
loader.perform("efi-seed-entropy " .. seedsize)
376
end
377
end
378
end
379
380
function core.setDefaults()
381
core.setACPI(default_acpi)
382
core.setSafeMode(default_safe_mode)
383
core.setSingleUser(default_single_user)
384
core.setVerbose(default_verbose)
385
end
386
387
function core.autoboot(argstr)
388
-- loadelf() only if we've not already loaded a kernel
389
if loader.getenv("kernelname") == nil then
390
config.loadelf()
391
end
392
core.loadEntropy()
393
loader.perform(composeLoaderCmd("autoboot", argstr))
394
end
395
396
function core.boot(argstr)
397
-- loadelf() only if we've not already loaded a kernel
398
if loader.getenv("kernelname") == nil then
399
config.loadelf()
400
end
401
core.loadEntropy()
402
loader.perform(composeLoaderCmd("boot", argstr))
403
end
404
405
function core.hasFeature(name)
406
if not loader.has_feature then
407
-- Loader too old, no feature support
408
return nil, "No feature support in loaded loader"
409
end
410
411
return loader.has_feature(name)
412
end
413
414
function core.isSingleUserBoot()
415
local single_user = loader.getenv("boot_single")
416
return single_user ~= nil and single_user:lower() == "yes"
417
end
418
419
function core.isUEFIBoot()
420
local efiver = loader.getenv("efi-version")
421
422
return efiver ~= nil
423
end
424
425
function core.isZFSBoot()
426
local c = loader.getenv("currdev")
427
428
if c ~= nil then
429
return c:match("^zfs:") ~= nil
430
end
431
return false
432
end
433
434
function core.isFramebufferConsole()
435
local c = loader.getenv("console")
436
if c ~= nil then
437
if c:find("efi") == nil and c:find("vidconsole") == nil then
438
return false
439
end
440
if loader.getenv("screen.depth") ~= nil then
441
return true
442
end
443
end
444
return false
445
end
446
447
function core.isSerialConsole()
448
local c = loader.getenv("console")
449
if c ~= nil then
450
-- serial console is comconsole, but also userboot.
451
-- userboot is there, because we have no way to know
452
-- if the user terminal can draw unicode box chars or not.
453
if c:find("comconsole") ~= nil or c:find("userboot") ~= nil then
454
return true
455
end
456
end
457
return false
458
end
459
460
function core.isSerialBoot()
461
local s = loader.getenv("boot_serial")
462
if s ~= nil then
463
return true
464
end
465
466
local m = loader.getenv("boot_multicons")
467
if m ~= nil then
468
return true
469
end
470
return false
471
end
472
473
-- Is the menu skipped in the environment in which we've booted?
474
function core.isMenuSkipped()
475
return string.lower(loader.getenv("beastie_disable") or "") == "yes"
476
end
477
478
-- This may be a better candidate for a 'utility' module.
479
function core.deepCopyTable(tbl)
480
local new_tbl = {}
481
for k, v in pairs(tbl) do
482
if type(v) == "table" then
483
new_tbl[k] = core.deepCopyTable(v)
484
else
485
new_tbl[k] = v
486
end
487
end
488
return new_tbl
489
end
490
491
-- XXX This should go away if we get the table lib into shape for importing.
492
-- As of now, it requires some 'os' functions, so we'll implement this in lua
493
-- for our uses
494
function core.popFrontTable(tbl)
495
-- Shouldn't reasonably happen
496
if #tbl == 0 then
497
return nil, nil
498
elseif #tbl == 1 then
499
return tbl[1], {}
500
end
501
502
local first_value = tbl[1]
503
local new_tbl = {}
504
-- This is not a cheap operation
505
for k, v in ipairs(tbl) do
506
if k > 1 then
507
new_tbl[k - 1] = v
508
end
509
end
510
511
return first_value, new_tbl
512
end
513
514
function core.getConsoleName()
515
if loader.getenv("boot_multicons") ~= nil then
516
if loader.getenv("boot_serial") ~= nil then
517
return "Dual (Serial primary)"
518
else
519
return "Dual (Video primary)"
520
end
521
else
522
if loader.getenv("boot_serial") ~= nil then
523
return "Serial"
524
else
525
return "Video"
526
end
527
end
528
end
529
530
function core.nextConsoleChoice()
531
if loader.getenv("boot_multicons") ~= nil then
532
if loader.getenv("boot_serial") ~= nil then
533
loader.unsetenv("boot_serial")
534
else
535
loader.unsetenv("boot_multicons")
536
loader.setenv("boot_serial", "YES")
537
end
538
else
539
if loader.getenv("boot_serial") ~= nil then
540
loader.unsetenv("boot_serial")
541
else
542
loader.setenv("boot_multicons", "YES")
543
loader.setenv("boot_serial", "YES")
544
end
545
end
546
end
547
548
-- The graphical-enabled loaders have unicode drawing character support. The
549
-- text-only ones do not. We check the old and new bindings for term_drawrect as
550
-- a proxy for unicode support, which will work on older boot loaders as well
551
-- as be future proof for when we remove the old binding. This also abstracts
552
-- out the test to one spot in case we start to export this notion more directly.
553
function core.hasUnicode()
554
return gfx.term_drawrect ~= nil or loader.term_drawrect ~= nil
555
end
556
557
-- Sanity check the boot loader revision
558
-- Loaders with version 3.0 have everything that we need without backwards
559
-- compatible hacks. Warn users that still have old versions to upgrade so
560
-- that we can remove the backwards compatible hacks in the future since
561
-- they have been there a long time.
562
local loader_major = 3
563
564
function core.loaderTooOld()
565
return loader.version == nil or loader.version < loader_major * 1000
566
end
567
568
if core.loaderTooOld() then
569
print("**********************************************************************")
570
print("**********************************************************************")
571
print("***** *****")
572
print("***** BOOT LOADER IS TOO OLD. PLEASE UPGRADE. *****")
573
print("***** *****")
574
print("**********************************************************************")
575
print("**********************************************************************")
576
end
577
578
recordDefaults()
579
hook.register("config.reloaded", core.clearCachedKernels)
580
return core
581
582