1--[[
2Automatically generated boot menu of the installed Linux kernels
3
4Example:
5
6m = require "automenu"
7m.run { dir = "/",
8        default = 1,
9        timeout = 5,
10        append = "root=/dev/hda2 ro",
11}
12
13TODO:
14- add hooks
15- demo adding break options from user config
16- kernel flavor preference (pae/rt)
17]]
18
19local lfs = require "lfs"
20local sl = require "syslinux"
21
22local single = false
23local verbosity = 2
24
25local function modifiers ()
26   return (single and " single" or "") .. ({" quiet",""," debug"})[verbosity]
27end
28
29local 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
50end
51
52local 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)
78end
79
80local function kernel_gt (k1, k2)
81   return version_gt (k1.version, k2.version)
82end
83
84local 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
93end
94
95local 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")
102end
103
104local 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
128end
129
130local 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)
146end
147
148return {
149   scan = scan,
150   choose = choose,
151   run = run
152}
153