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