blob: 1e66cca1712cd209121958b5edd69dccc4eccd80 [file] [log] [blame]
--[[
Automatically generated boot menu of the installed Linux kernels
Example:
m = require "automenu"
m.run { dir = "/",
default = 1,
timeout = 5,
append = "root=/dev/hda2 ro",
}
TODO:
- add hooks
- demo adding break options from user config
- kernel flavor preference (pae/rt)
]]
local lfs = require "lfs"
local sl = require "syslinux"
local single = false
local verbosity = 2
local function modifiers ()
return (single and " single" or "") .. ({" quiet",""," debug"})[verbosity]
end
local function scan (params)
local sep = string.sub (params.dir, -1) == "/" and "" or "/"
if not params.items then params.items = {} end
for name in lfs.dir (params.dir) do
local path = params.dir .. sep .. name
if lfs.attributes (path, "mode") == "file" then
local from,to,version = string.find (name, "^vmlinuz%-(.*)")
if from then
local initrd = params.dir .. sep .. "initrd.img-" .. version
local initrd_param = ""
if lfs.attributes (initrd, "size") then
initrd_param = "initrd=" .. initrd .. " "
end
table.insert (params.items, {
show = function () return name end,
version = version,
execute = function () sl.boot_linux (path, initrd_param .. params.append .. modifiers ()) end
})
end
end
end
end
local function version_gt (v1, v2)
local negatives = {"rc", "pre"}
local m1, r1 = string.match (v1, "^(%D*)(.*)")
local m2, r2 = string.match (v2, "^(%D*)(.*)")
if m1 ~= m2 then
for _, suffix in ipairs (negatives) do
suffix = "-" .. suffix
if m1 == suffix and m2 ~= suffix then
return false
elseif m1 ~= suffix and m2 == suffix then
return true
end
end
return m1 > m2
end
m1, r1 = string.match (r1, "^(%d*)(.*)")
m2, r2 = string.match (r2, "^(%d*)(.*)")
m1 = tonumber (m1) or 0
m2 = tonumber (m2) or 0
if m1 ~= m2 then
return m1 > m2
end
if r1 == "" and r2 == "" then
return false
end
return version_gt (r1, r2)
end
local function kernel_gt (k1, k2)
return version_gt (k1.version, k2.version)
end
local function print_or_call (x, def)
local t = type (x)
if t == "nil" then
if def then print (def) end
elseif t == "function" then
x ()
else
print (x)
end
end
local function draw (params)
print_or_call (params.title, "\n=== Boot menu ===")
for i, item in ipairs (params.items) do
print ((i == params.default and " > " or " ") .. i .. " " .. item.show ())
end
print ("\nKernel arguments:\n " .. params.append .. modifiers ())
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")
end
local function choose (params)
draw (params)
print ("\nBooting in " .. params.timeout .. " s...")
while true do
local i = sl.get_key (params.timeout * 1000)
if i == sl.KEY.ESC then
break
else
if i == sl.KEY.NONE or i == sl.KEY.ENTER then
i = params.default
elseif i == sl.KEY.DOWN then
params.default = params.default < #params.items and params.default + 1 or #params.items
elseif i == sl.KEY.UP then
params.default = params.default > 1 and params.default - 1 or 1
else
i = i - string.byte "0"
end
if params.items[i] then
params.items[i].execute ()
end
params.timeout = 0
draw (params)
end
end
end
local function run (params)
scan (params)
if not next (params.items) then
print ("No kernels found in directory " .. params.dir)
os.exit (false)
end
table.sort (params.items, kernel_gt)
table.insert (params.items, {
show = function () return "Single user: " .. (single and "true" or "false") end,
execute = function () single = not single end
})
table.insert (params.items, {
show = function () return "Verbosity: " .. ({"quiet","normal","debug"})[verbosity] end,
execute = function () verbosity = verbosity < 3 and verbosity + 1 or 1 end
})
choose (params)
end
return {
scan = scan,
choose = choose,
run = run
}