Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/stand/lua/core.lua
101890 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
293
-- The config/boot bits use the env var as a fallback if the
294
-- menu's kernel selector remains untouched, so we want to
295
-- update our notion of the default kernel to one that is
296
-- actually present.
297
loader.setenv("kernel", kernels[1])
298
end
299
300
core.cached_kernels = kernels
301
return core.cached_kernels
302
end
303
304
function core.bootenvDefault()
305
return loader.getenv("zfs_be_active")
306
end
307
308
function core.bootenvList()
309
local bootenv_count = tonumber(loader.getenv(bootenv_list .. "_count"))
310
local bootenvs = {}
311
local curenv
312
local envcount = 0
313
local unique = {}
314
315
if bootenv_count == nil or bootenv_count <= 0 then
316
return bootenvs
317
end
318
319
-- Currently selected bootenv is always first/default
320
-- On the rewinded list the bootenv may not exists
321
if core.isRewinded() then
322
curenv = core.bootenvDefaultRewinded()
323
else
324
curenv = core.bootenvDefault()
325
end
326
if curenv ~= nil then
327
envcount = envcount + 1
328
bootenvs[envcount] = curenv
329
unique[curenv] = true
330
end
331
332
for curenv_idx = 0, bootenv_count - 1 do
333
curenv = loader.getenv(bootenv_list .. "[" .. curenv_idx .. "]")
334
if curenv ~= nil and unique[curenv] == nil then
335
envcount = envcount + 1
336
bootenvs[envcount] = curenv
337
unique[curenv] = true
338
end
339
end
340
return bootenvs
341
end
342
343
function core.isCheckpointed()
344
return loader.getenv("zpool_checkpoint") ~= nil
345
end
346
347
function core.bootenvDefaultRewinded()
348
local defname = "zfs:!" .. string.sub(core.bootenvDefault(), 5)
349
local bootenv_count = tonumber("bootenvs_check_count")
350
351
if bootenv_count == nil or bootenv_count <= 0 then
352
return defname
353
end
354
355
for curenv_idx = 0, bootenv_count - 1 do
356
local curenv = loader.getenv("bootenvs_check[" .. curenv_idx .. "]")
357
if curenv == defname then
358
return defname
359
end
360
end
361
362
return loader.getenv("bootenvs_check[0]")
363
end
364
365
function core.isRewinded()
366
return bootenv_list == "bootenvs_check"
367
end
368
369
function core.changeRewindCheckpoint()
370
if core.isRewinded() then
371
bootenv_list = "bootenvs"
372
else
373
bootenv_list = "bootenvs_check"
374
end
375
end
376
377
function core.loadEntropy()
378
if core.isUEFIBoot() then
379
if (loader.getenv("entropy_efi_seed") or "no"):lower() == "yes" then
380
local seedsize = loader.getenv("entropy_efi_seed_size") or "2048"
381
loader.perform("efi-seed-entropy " .. seedsize)
382
end
383
end
384
end
385
386
function core.setDefaults()
387
core.setACPI(default_acpi)
388
core.setSafeMode(default_safe_mode)
389
core.setSingleUser(default_single_user)
390
core.setVerbose(default_verbose)
391
end
392
393
function core.autoboot(argstr)
394
-- loadelf() only if we've not already loaded a kernel
395
if loader.getenv("kernelname") == nil then
396
config.loadelf()
397
end
398
core.loadEntropy()
399
loader.perform(composeLoaderCmd("autoboot", argstr))
400
end
401
402
function core.boot(argstr)
403
-- loadelf() only if we've not already loaded a kernel
404
if loader.getenv("kernelname") == nil then
405
config.loadelf()
406
end
407
core.loadEntropy()
408
loader.perform(composeLoaderCmd("boot", argstr))
409
end
410
411
function core.hasFeature(name)
412
if not loader.has_feature then
413
-- Loader too old, no feature support
414
return nil, "No feature support in loaded loader"
415
end
416
417
return loader.has_feature(name)
418
end
419
420
function core.isSingleUserBoot()
421
local single_user = loader.getenv("boot_single")
422
return single_user ~= nil and single_user:lower() ~= "no"
423
end
424
425
function core.isUEFIBoot()
426
local efiver = loader.getenv("efi-version")
427
428
return efiver ~= nil
429
end
430
431
function core.isZFSBoot()
432
local c = loader.getenv("currdev")
433
434
if c ~= nil then
435
return c:match("^zfs:") ~= nil
436
end
437
return false
438
end
439
440
function core.isFramebufferConsole()
441
local c = loader.getenv("console")
442
if c ~= nil then
443
if c:find("efi") == nil and c:find("vidconsole") == nil then
444
return false
445
end
446
if loader.getenv("screen.depth") ~= nil then
447
return true
448
end
449
end
450
return false
451
end
452
453
function core.isSerialConsole()
454
local c = loader.getenv("console")
455
if c ~= nil then
456
-- serial console is comconsole, but also userboot.
457
-- userboot is there, because we have no way to know
458
-- if the user terminal can draw unicode box chars or not.
459
if c:find("comconsole") ~= nil or c:find("userboot") ~= nil then
460
return true
461
end
462
end
463
return false
464
end
465
466
function core.isSerialBoot()
467
local s = loader.getenv("boot_serial")
468
if s ~= nil then
469
return true
470
end
471
472
local m = loader.getenv("boot_multicons")
473
if m ~= nil then
474
return true
475
end
476
return false
477
end
478
479
-- Is the menu skipped in the environment in which we've booted?
480
function core.isMenuSkipped()
481
return string.lower(loader.getenv("beastie_disable") or "") == "yes"
482
end
483
484
-- This may be a better candidate for a 'utility' module.
485
function core.deepCopyTable(tbl)
486
local new_tbl = {}
487
for k, v in pairs(tbl) do
488
if type(v) == "table" then
489
new_tbl[k] = core.deepCopyTable(v)
490
else
491
new_tbl[k] = v
492
end
493
end
494
return new_tbl
495
end
496
497
-- XXX This should go away if we get the table lib into shape for importing.
498
-- As of now, it requires some 'os' functions, so we'll implement this in lua
499
-- for our uses
500
function core.popFrontTable(tbl)
501
-- Shouldn't reasonably happen
502
if #tbl == 0 then
503
return nil, nil
504
elseif #tbl == 1 then
505
return tbl[1], {}
506
end
507
508
local first_value = tbl[1]
509
local new_tbl = {}
510
-- This is not a cheap operation
511
for k, v in ipairs(tbl) do
512
if k > 1 then
513
new_tbl[k - 1] = v
514
end
515
end
516
517
return first_value, new_tbl
518
end
519
520
function core.getConsoleName()
521
if loader.getenv("boot_multicons") ~= nil then
522
if loader.getenv("boot_serial") ~= nil then
523
return "Dual (Serial primary)"
524
else
525
return "Dual (Video primary)"
526
end
527
else
528
if loader.getenv("boot_serial") ~= nil then
529
return "Serial"
530
else
531
return "Video"
532
end
533
end
534
end
535
536
function core.nextConsoleChoice()
537
if loader.getenv("boot_multicons") ~= nil then
538
if loader.getenv("boot_serial") ~= nil then
539
loader.unsetenv("boot_serial")
540
else
541
loader.unsetenv("boot_multicons")
542
loader.setenv("boot_serial", "YES")
543
end
544
else
545
if loader.getenv("boot_serial") ~= nil then
546
loader.unsetenv("boot_serial")
547
else
548
loader.setenv("boot_multicons", "YES")
549
loader.setenv("boot_serial", "YES")
550
end
551
end
552
end
553
554
-- The graphical-enabled loaders have unicode drawing character support. The
555
-- text-only ones do not. We check the old and new bindings for term_drawrect as
556
-- a proxy for unicode support, which will work on older boot loaders as well
557
-- as be future proof for when we remove the old binding. This also abstracts
558
-- out the test to one spot in case we start to export this notion more directly.
559
function core.hasUnicode()
560
return gfx.term_drawrect ~= nil or loader.term_drawrect ~= nil
561
end
562
563
-- Sanity check the boot loader revision
564
-- Loaders with version 3.0 have everything that we need without backwards
565
-- compatible hacks. Warn users that still have old versions to upgrade so
566
-- that we can remove the backwards compatible hacks in the future since
567
-- they have been there a long time.
568
local loader_major = 3
569
570
function core.loaderTooOld()
571
return loader.version == nil or loader.version < loader_major * 1000
572
end
573
574
if core.loaderTooOld() then
575
print("**********************************************************************")
576
print("**********************************************************************")
577
print("***** *****")
578
print("***** BOOT LOADER IS TOO OLD. PLEASE UPGRADE. *****")
579
print("***** *****")
580
print("**********************************************************************")
581
print("**********************************************************************")
582
end
583
584
recordDefaults()
585
hook.register("config.reloaded", core.clearCachedKernels)
586
return core
587
588