]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - stand/lua/config.lua
lualoader: Remove debug function
[FreeBSD/FreeBSD.git] / stand / lua / config.lua
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
32 local config = {}
33
34 local modules = {}
35
36 local carousel_choices = {}
37
38 local pattern_table = {
39         {
40                 str = "^%s*(#.*)",
41                 process = function(_, _)  end,
42         },
43         --  module_load="value"
44         {
45                 str = "^%s*([%w_]+)_load%s*=%s*\"([%w%s%p]-)\"%s*(.*)",
46                 process = function(k, v)
47                         if modules[k] == nil then
48                                 modules[k] = {}
49                         end
50                         modules[k].load = v:upper()
51                 end,
52         },
53         --  module_name="value"
54         {
55                 str = "^%s*([%w_]+)_name%s*=%s*\"([%w%s%p]-)\"%s*(.*)",
56                 process = function(k, v)
57                         config.setKey(k, "name", v)
58                 end,
59         },
60         --  module_type="value"
61         {
62                 str = "^%s*([%w_]+)_type%s*=%s*\"([%w%s%p]-)\"%s*(.*)",
63                 process = function(k, v)
64                         config.setKey(k, "type", v)
65                 end,
66         },
67         --  module_flags="value"
68         {
69                 str = "^%s*([%w_]+)_flags%s*=%s*\"([%w%s%p]-)\"%s*(.*)",
70                 process = function(k, v)
71                         config.setKey(k, "flags", v)
72                 end,
73         },
74         --  module_before="value"
75         {
76                 str = "^%s*([%w_]+)_before%s*=%s*\"([%w%s%p]-)\"%s*(.*)",
77                 process = function(k, v)
78                         config.setKey(k, "before", v)
79                 end,
80         },
81         --  module_after="value"
82         {
83                 str = "^%s*([%w_]+)_after%s*=%s*\"([%w%s%p]-)\"%s*(.*)",
84                 process = function(k, v)
85                         config.setKey(k, "after", v)
86                 end,
87         },
88         --  module_error="value"
89         {
90                 str = "^%s*([%w_]+)_error%s*=%s*\"([%w%s%p]-)\"%s*(.*)",
91                 process = function(k, v)
92                         config.setKey(k, "error", v)
93                 end,
94         },
95         --  exec="command"
96         {
97                 str = "^%s*exec%s*=%s*\"([%w%s%p]-)\"%s*(.*)",
98                 process = function(k, _)
99                         if loader.perform(k) ~= 0 then
100                                 print("Failed to exec '" .. k .. "'")
101                         end
102                 end,
103         },
104         --  env_var="value"
105         {
106                 str = "^%s*([%w%p]+)%s*=%s*\"([%w%s%p]-)\"%s*(.*)",
107                 process = function(k, v)
108                         if config.setenv(k, v) ~= 0 then
109                                 print("Failed to set '" .. k ..
110                                     "' with value: " .. v .. "")
111                         end
112                 end,
113         },
114         --  env_var=num
115         {
116                 str = "^%s*([%w%p]+)%s*=%s*(%d+)%s*(.*)",
117                 process = function(k, v)
118                         if config.setenv(k, v) ~= 0 then
119                                 print("Failed to set '" .. k ..
120                                     "' with value: " .. v .. "")
121                         end
122                 end,
123         },
124 }
125
126 local function readFile(name, silent)
127         local f = io.open(name)
128         if f == nil then
129                 if not silent then
130                         print("Failed to open config: '" .. name .. "'")
131                 end
132                 return nil
133         end
134
135         local text, _ = io.read(f)
136         -- We might have read in the whole file, this won't be needed any more.
137         io.close(f)
138
139         if text == nil then
140                 if not silent then
141                         print("Failed to read config: '" .. name .. "'")
142                 end
143                 return nil
144         end
145         return text
146 end
147
148 local function checkNextboot()
149         local nextboot_file = loader.getenv("nextboot_file")
150         if nextboot_file == nil then
151                 return
152         end
153
154         local text = readFile(nextboot_file, true)
155         if text == nil then
156                 return
157         end
158
159         if text:match("^nextboot_enable=\"NO\"") ~= nil then
160                 -- We're done; nextboot is not enabled
161                 return
162         end
163
164         if not config.parse(text) then
165                 print("Failed to parse nextboot configuration: '" ..
166                     nextboot_file .. "'")
167         end
168
169         -- Attempt to rewrite the first line and only the first line of the
170         -- nextboot_file. We overwrite it with nextboot_enable="NO", then
171         -- check for that on load.
172         -- It's worth noting that this won't work on every filesystem, so we
173         -- won't do anything notable if we have any errors in this process.
174         local nfile = io.open(nextboot_file, 'w')
175         if nfile ~= nil then
176                 -- We need the trailing space here to account for the extra
177                 -- character taken up by the string nextboot_enable="YES"
178                 -- Or new end quotation mark lands on the S, and we want to
179                 -- rewrite the entirety of the first line.
180                 io.write(nfile, "nextboot_enable=\"NO\" ")
181                 io.close(nfile)
182         end
183 end
184
185 -- Module exports
186 -- Which variables we changed
187 config.env_changed = {}
188 -- Values to restore env to (nil to unset)
189 config.env_restore = {}
190
191 -- The first item in every carousel is always the default item.
192 function config.getCarouselIndex(id)
193         local val = carousel_choices[id]
194         if val == nil then
195                 return 1
196         end
197         return val
198 end
199
200 function config.setCarouselIndex(id, idx)
201         carousel_choices[id] = idx
202 end
203
204 function config.restoreEnv()
205         -- Examine changed environment variables
206         for k, v in pairs(config.env_changed) do
207                 local restore_value = config.env_restore[k]
208                 if restore_value == nil then
209                         -- This one doesn't need restored for some reason
210                         goto continue
211                 end
212                 local current_value = loader.getenv(k)
213                 if current_value ~= v then
214                         -- This was overwritten by some action taken on the menu
215                         -- most likely; we'll leave it be.
216                         goto continue
217                 end
218                 restore_value = restore_value.value
219                 if restore_value ~= nil then
220                         loader.setenv(k, restore_value)
221                 else
222                         loader.unsetenv(k)
223                 end
224                 ::continue::
225         end
226
227         config.env_changed = {}
228         config.env_restore = {}
229 end
230
231 function config.setenv(key, value)
232         -- Track the original value for this if we haven't already
233         if config.env_restore[key] == nil then
234                 config.env_restore[key] = {value = loader.getenv(key)}
235         end
236
237         config.env_changed[key] = value
238
239         return loader.setenv(key, value)
240 end
241
242 -- name here is one of 'name', 'type', flags', 'before', 'after', or 'error.'
243 -- These are set from lines in loader.conf(5): ${key}_${name}="${value}" where
244 -- ${key} is a module name.
245 function config.setKey(key, name, value)
246         if modules[key] == nil then
247                 modules[key] = {}
248         end
249         modules[key][name] = value
250 end
251
252 function config.isValidComment(line)
253         if line ~= nil then
254                 local s = line:match("^%s*#.*")
255                 if s == nil then
256                         s = line:match("^%s*$")
257                 end
258                 if s == nil then
259                         return false
260                 end
261         end
262         return true
263 end
264
265 function config.loadmod(mod, silent)
266         local status = true
267         for k, v in pairs(mod) do
268                 if v.load == "YES" then
269                         local str = "load "
270                         if v.flags ~= nil then
271                                 str = str .. v.flags .. " "
272                         end
273                         if v.type ~= nil then
274                                 str = str .. "-t " .. v.type .. " "
275                         end
276                         if v.name ~= nil then
277                                 str = str .. v.name
278                         else
279                                 str = str .. k
280                         end
281
282                         if v.before ~= nil then
283                                 if loader.perform(v.before) ~= 0 then
284                                         if not silent then
285                                                 print("Failed to execute '" ..
286                                                     v.before ..
287                                                     "' before loading '" .. k ..
288                                                     "'")
289                                         end
290                                         status = false
291                                 end
292                         end
293
294                         if loader.perform(str) ~= 0 then
295                                 if not silent then
296                                         print("Failed to execute '" .. str ..
297                                             "'")
298                                 end
299                                 if v.error ~= nil then
300                                         loader.perform(v.error)
301                                 end
302                                 status = false
303                         end
304
305                         if v.after ~= nil then
306                                 if loader.perform(v.after) ~= 0 then
307                                         if not silent then
308                                                 print("Failed to execute '" ..
309                                                     v.after ..
310                                                     "' after loading '" .. k ..
311                                                     "'")
312                                         end
313                                         status = false
314                                 end
315                         end
316
317 --              else
318 --                      if not silent then
319 --                              print("Skipping module '". . k .. "'")
320 --                      end
321                 end
322         end
323
324         return status
325 end
326
327 -- Returns true if we processed the file successfully, false if we did not.
328 -- If 'silent' is true, being unable to read the file is not considered a
329 -- failure.
330 function config.processFile(name, silent)
331         if silent == nil then
332                 silent = false
333         end
334
335         local text = readFile(name, silent)
336         if text == nil then
337                 return silent
338         end
339
340         return config.parse(text)
341 end
342
343 -- silent runs will not return false if we fail to open the file
344 function config.parse(text)
345         local n = 1
346         local status = true
347
348         for line in text:gmatch("([^\n]+)") do
349                 if line:match("^%s*$") == nil then
350                         local found = false
351
352                         for _, val in ipairs(pattern_table) do
353                                 local k, v, c = line:match(val.str)
354                                 if k ~= nil then
355                                         found = true
356
357                                         if config.isValidComment(c) then
358                                                 val.process(k, v)
359                                         else
360                                                 print("Malformed line (" .. n ..
361                                                     "):\n\t'" .. line .. "'")
362                                                 status = false
363                                         end
364
365                                         break
366                                 end
367                         end
368
369                         if not found then
370                                 print("Malformed line (" .. n .. "):\n\t'" ..
371                                     line .. "'")
372                                 status = false
373                         end
374                 end
375                 n = n + 1
376         end
377
378         return status
379 end
380
381 -- other_kernel is optionally the name of a kernel to load, if not the default
382 -- or autoloaded default from the module_path
383 function config.loadKernel(other_kernel)
384         local flags = loader.getenv("kernel_options") or ""
385         local kernel = other_kernel or loader.getenv("kernel")
386
387         local function tryLoad(names)
388                 for name in names:gmatch("([^;]+)%s*;?") do
389                         local r = loader.perform("load " .. flags ..
390                             " " .. name)
391                         if r == 0 then
392                                 return name
393                         end
394                 end
395                 return nil
396         end
397
398         local function loadBootfile()
399                 local bootfile = loader.getenv("bootfile")
400
401                 -- append default kernel name
402                 if bootfile == nil then
403                         bootfile = "kernel"
404                 else
405                         bootfile = bootfile .. ";kernel"
406                 end
407
408                 return tryLoad(bootfile)
409         end
410
411         -- kernel not set, try load from default module_path
412         if kernel == nil then
413                 local res = loadBootfile()
414
415                 if res ~= nil then
416                         -- Default kernel is loaded
417                         config.kernel_loaded = nil
418                         return true
419                 else
420                         print("No kernel set, failed to load from module_path")
421                         return false
422                 end
423         else
424                 -- Use our cached module_path, so we don't end up with multiple
425                 -- automatically added kernel paths to our final module_path
426                 local module_path = config.module_path
427                 local res
428
429                 if other_kernel ~= nil then
430                         kernel = other_kernel
431                 end
432                 -- first try load kernel with module_path = /boot/${kernel}
433                 -- then try load with module_path=${kernel}
434                 local paths = {"/boot/" .. kernel, kernel}
435
436                 for _, v in pairs(paths) do
437                         loader.setenv("module_path", v)
438                         res = loadBootfile()
439
440                         -- succeeded, add path to module_path
441                         if res ~= nil then
442                                 config.kernel_loaded = kernel
443                                 if module_path ~= nil then
444                                         loader.setenv("module_path", v .. ";" ..
445                                             module_path)
446                                 end
447                                 return true
448                         end
449                 end
450
451                 -- failed to load with ${kernel} as a directory
452                 -- try as a file
453                 res = tryLoad(kernel)
454                 if res ~= nil then
455                         config.kernel_loaded = kernel
456                         return true
457                 else
458                         print("Failed to load kernel '" .. kernel .. "'")
459                         return false
460                 end
461         end
462 end
463
464 function config.selectKernel(kernel)
465         config.kernel_selected = kernel
466 end
467
468 function config.load(file)
469         if not file then
470                 file = "/boot/defaults/loader.conf"
471         end
472
473         if not config.processFile(file) then
474                 print("Failed to parse configuration: '" .. file .. "'")
475         end
476
477         local f = loader.getenv("loader_conf_files")
478         if f ~= nil then
479                 for name in f:gmatch("([%w%p]+)%s*") do
480                         -- These may or may not exist, and that's ok. Do a
481                         -- silent parse so that we complain on parse errors but
482                         -- not for them simply not existing.
483                         if not config.processFile(name, true) then
484                                 print("Failed to parse configuration: '" ..
485                                     name .. "'")
486                         end
487                 end
488         end
489
490         checkNextboot()
491
492         -- Cache the provided module_path at load time for later use
493         config.module_path = loader.getenv("module_path")
494 end
495
496 -- Reload configuration
497 function config.reload(file)
498         modules = {}
499         config.restoreEnv()
500         config.load(file)
501 end
502
503 function config.loadelf()
504         local kernel = config.kernel_selected or config.kernel_loaded
505         local loaded
506
507         print("Loading kernel...")
508         loaded = config.loadKernel(kernel)
509
510         if not loaded then
511                 print("Failed to load any kernel")
512                 return
513         end
514
515         print("Loading configured modules...")
516         if not config.loadmod(modules) then
517                 print("Could not load one or more modules!")
518         end
519 end
520
521 return config