[e16e8f2] | 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 | } |
---|