1 | --[[ |
---|
2 | Automatically generated boot menu of the installed Linux kernels |
---|
3 | |
---|
4 | Example: |
---|
5 | |
---|
6 | m = require "automenu" |
---|
7 | m.run { dir = "/", |
---|
8 | default = 1, |
---|
9 | timeout = 5, |
---|
10 | append = "root=/dev/hda2 ro", |
---|
11 | } |
---|
12 | |
---|
13 | TODO: |
---|
14 | - add hooks |
---|
15 | - demo adding break options from user config |
---|
16 | - kernel flavor preference (pae/rt) |
---|
17 | ]] |
---|
18 | |
---|
19 | local lfs = require "lfs" |
---|
20 | local sl = require "syslinux" |
---|
21 | |
---|
22 | local single = false |
---|
23 | local verbosity = 2 |
---|
24 | |
---|
25 | local function modifiers () |
---|
26 | return (single and " single" or "") .. ({" quiet",""," debug"})[verbosity] |
---|
27 | end |
---|
28 | |
---|
29 | local function scan (params) |
---|
30 | local sep = string.sub (params.dir, -1) == "/" and "" or "/" |
---|
31 | if not params.items then params.items = {} end |
---|
32 | for name in lfs.dir (params.dir) do |
---|
33 | local path = params.dir .. sep .. name |
---|
34 | if lfs.attributes (path, "mode") == "file" then |
---|
35 | local from,to,version = string.find (name, "^vmlinuz%-(.*)") |
---|
36 | if from then |
---|
37 | local initrd = params.dir .. sep .. "initrd.img-" .. version |
---|
38 | local initrd_param = "" |
---|
39 | if lfs.attributes (initrd, "size") then |
---|
40 | initrd_param = "initrd=" .. initrd .. " " |
---|
41 | end |
---|
42 | table.insert (params.items, { |
---|
43 | show = function () return name end, |
---|
44 | version = version, |
---|
45 | execute = function () sl.boot_linux (path, initrd_param .. params.append .. modifiers ()) end |
---|
46 | }) |
---|
47 | end |
---|
48 | end |
---|
49 | end |
---|
50 | end |
---|
51 | |
---|
52 | local function version_gt (v1, v2) |
---|
53 | local negatives = {"rc", "pre"} |
---|
54 | local m1, r1 = string.match (v1, "^(%D*)(.*)") |
---|
55 | local m2, r2 = string.match (v2, "^(%D*)(.*)") |
---|
56 | if m1 ~= m2 then |
---|
57 | for _, suffix in ipairs (negatives) do |
---|
58 | suffix = "-" .. suffix |
---|
59 | if m1 == suffix and m2 ~= suffix then |
---|
60 | return false |
---|
61 | elseif m1 ~= suffix and m2 == suffix then |
---|
62 | return true |
---|
63 | end |
---|
64 | end |
---|
65 | return m1 > m2 |
---|
66 | end |
---|
67 | m1, r1 = string.match (r1, "^(%d*)(.*)") |
---|
68 | m2, r2 = string.match (r2, "^(%d*)(.*)") |
---|
69 | m1 = tonumber (m1) or 0 |
---|
70 | m2 = tonumber (m2) or 0 |
---|
71 | if m1 ~= m2 then |
---|
72 | return m1 > m2 |
---|
73 | end |
---|
74 | if r1 == "" and r2 == "" then |
---|
75 | return false |
---|
76 | end |
---|
77 | return version_gt (r1, r2) |
---|
78 | end |
---|
79 | |
---|
80 | local function kernel_gt (k1, k2) |
---|
81 | return version_gt (k1.version, k2.version) |
---|
82 | end |
---|
83 | |
---|
84 | local function print_or_call (x, def) |
---|
85 | local t = type (x) |
---|
86 | if t == "nil" then |
---|
87 | if def then print (def) end |
---|
88 | elseif t == "function" then |
---|
89 | x () |
---|
90 | else |
---|
91 | print (x) |
---|
92 | end |
---|
93 | end |
---|
94 | |
---|
95 | local function draw (params) |
---|
96 | print_or_call (params.title, "\n=== Boot menu ===") |
---|
97 | for i, item in ipairs (params.items) do |
---|
98 | print ((i == params.default and " > " or " ") .. i .. " " .. item.show ()) |
---|
99 | end |
---|
100 | print ("\nKernel arguments:\n " .. params.append .. modifiers ()) |
---|
101 | print ("\nHit a number to select from the menu,\n ENTER to accept default,\n ESC to exit\n or any other key to print menu again") |
---|
102 | end |
---|
103 | |
---|
104 | local function choose (params) |
---|
105 | draw (params) |
---|
106 | print ("\nBooting in " .. params.timeout .. " s...") |
---|
107 | while true do |
---|
108 | local i = sl.get_key (params.timeout * 1000) |
---|
109 | if i == sl.KEY.ESC then |
---|
110 | break |
---|
111 | else |
---|
112 | if i == sl.KEY.NONE or i == sl.KEY.ENTER then |
---|
113 | i = params.default |
---|
114 | elseif i == sl.KEY.DOWN then |
---|
115 | params.default = params.default < #params.items and params.default + 1 or #params.items |
---|
116 | elseif i == sl.KEY.UP then |
---|
117 | params.default = params.default > 1 and params.default - 1 or 1 |
---|
118 | else |
---|
119 | i = i - string.byte "0" |
---|
120 | end |
---|
121 | if params.items[i] then |
---|
122 | params.items[i].execute () |
---|
123 | end |
---|
124 | params.timeout = 0 |
---|
125 | draw (params) |
---|
126 | end |
---|
127 | end |
---|
128 | end |
---|
129 | |
---|
130 | local function run (params) |
---|
131 | scan (params) |
---|
132 | if not next (params.items) then |
---|
133 | print ("No kernels found in directory " .. params.dir) |
---|
134 | os.exit (false) |
---|
135 | end |
---|
136 | table.sort (params.items, kernel_gt) |
---|
137 | table.insert (params.items, { |
---|
138 | show = function () return "Single user: " .. (single and "true" or "false") end, |
---|
139 | execute = function () single = not single end |
---|
140 | }) |
---|
141 | table.insert (params.items, { |
---|
142 | show = function () return "Verbosity: " .. ({"quiet","normal","debug"})[verbosity] end, |
---|
143 | execute = function () verbosity = verbosity < 3 and verbosity + 1 or 1 end |
---|
144 | }) |
---|
145 | choose (params) |
---|
146 | end |
---|
147 | |
---|
148 | return { |
---|
149 | scan = scan, |
---|
150 | choose = choose, |
---|
151 | run = run |
---|
152 | } |
---|