Home | History | Annotate | Download | only in test
      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 }
    153