Z1maV1

bootloader

May 7th, 2024 (edited)
231
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 10.67 KB | None | 0 0
  1. os.pullEvent = os.pullEventRaw
  2.  
  3. local version = "v.1.1"
  4.  
  5. local bootable_tag = "-- CRAFTLOADER BOOTABLE MEDIA"
  6.  
  7. local nOption = 1
  8.  
  9. local bootCfg = {}
  10.  
  11. local timer;
  12.  
  13. local w, h = term.getSize()
  14.  
  15. local function halt()
  16.     while true do
  17.         sleep(1)
  18.     end
  19. end
  20.  
  21. local function print_centered(y, str)
  22.     term.setCursorPos(math.floor((w - string.len(str)) / 2), y)
  23.     term.clearLine()
  24.     write(str)
  25. end
  26.  
  27. local function split(inputstr, sep)
  28.     if sep == nil then
  29.         sep = "%s"
  30.     end
  31.     local t = {}
  32.     for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
  33.         table.insert(t, str)
  34.     end
  35.     return t
  36. end
  37.  
  38. --[[ This is a copy of libs from /lib folder
  39.     in case it is missing there. I just don't want to
  40.     add dependencies to bootloader
  41. ]]
  42.  
  43. --[[    CONFIG    ]]
  44.  
  45. local function save(tbl, name)
  46.     local file = fs.open(name, "w")
  47.     file.write(textutils.serialize(tbl))
  48.     file.close()    
  49. end
  50.  
  51. local function createIfNotExists(name)
  52.     if not fs.exists(name) then
  53.         local file = fs.open(name, "w")
  54.         file.close()
  55.         return true
  56.     end
  57.     return false
  58. end
  59.  
  60. local function load(name)
  61.     local file = fs.open(name, "r")
  62.     local tbl = textutils.unserialize(file.readAll())
  63.     file.close()
  64.     return tbl
  65. end
  66.  
  67. local function print_err(exception)
  68.     -- save old color formatting
  69.     local bg = term.getBackgroundColor()
  70.     local fg = term.getTextColor()
  71.  
  72.     -- set colors
  73.     term.setBackgroundColor(colors.gray)
  74.     term.setTextColor(colors.red)
  75.  
  76.     -- print error
  77.     print(exception)
  78.  
  79.     -- restore colors
  80.     term.setBackgroundColor(bg)
  81.     term.setTextColor(fg)
  82. end
  83.  
  84. --[[    ARGS PARSER    ]]
  85.  
  86. local parser = {}
  87.  
  88. function parser:new()
  89.     local obj = {
  90.         flags = {},
  91.         options = {},
  92.         parsed = {flags = {}, options = {}, args = {}}
  93.     }
  94.     self.__index = self
  95.     return setmetatable(obj, self)
  96. end
  97.  
  98. function parser:addFlag(canonical, ...)
  99.     local variants = {...}
  100.     self.flags[canonical] = {false, variants}
  101.     for _, variant in ipairs(variants) do
  102.         self.flags[variant] = {false, {canonical}}
  103.     end
  104. end
  105.  
  106. function parser:addOption(canonical, ...)
  107.     local variants = {...}
  108.     self.options[canonical] = {nil, variants}
  109.     for _, variant in ipairs(variants) do
  110.         self.options[variant] = {nil, {canonical}}
  111.     end
  112. end
  113.  
  114. function parser:parse(args)
  115.     local i = 1
  116.     while i <= #args do
  117.         local arg = args[i]
  118.         if self.flags[arg] then
  119.             for _, variant in ipairs(self.flags[arg][2]) do
  120.                 self.parsed.flags[variant] = true
  121.             end
  122.             self.parsed.flags[arg] = true
  123.         elseif self.options[arg] then
  124.             if i + 1 <= #args then
  125.                 local value = args[i + 1]
  126.                 for _, variant in ipairs(self.options[arg][2]) do
  127.                     self.parsed.options[variant] = value
  128.                 end
  129.                 self.parsed.options[arg] = value
  130.                 i = i + 1
  131.             else
  132.                 error("Option " .. arg .. " expects a value")
  133.             end
  134.         else
  135.             table.insert(self.parsed.args, arg)
  136.         end
  137.         i = i + 1
  138.     end
  139. end
  140.  
  141. function parser:getFlags()
  142.     local result = {}
  143.     for flag, _ in pairs(self.flags) do
  144.         if self.parsed.flags[flag] then
  145.             result[flag] = true
  146.         end
  147.     end
  148.     return result
  149. end
  150.  
  151. function parser:getOptions()
  152.     local result = {}
  153.     for option, _ in pairs(self.options) do
  154.         if self.parsed.options[option] then
  155.             result[option] = self.parsed.options[option]
  156.         end
  157.     end
  158.     return result
  159. end
  160.  
  161. function parser:getArgs()
  162.     return self.parsed.args
  163. end
  164.  
  165. --[[
  166.     End of libs copies
  167. ]]
  168.  
  169.  
  170. --[[
  171.  
  172.         CLI
  173.  
  174. ]]
  175.  
  176. function string.starts(str, str1)
  177.     return str:sub(1, #str1) == str1
  178. end
  179.  
  180. function handleCommand(command, arguments)
  181.     args = parser:new()
  182.  
  183.     if command:lower() == "shutdown" then
  184.         os.shutdown()
  185.     elseif command:lower() == "reboot" then
  186.         os.reboot()
  187.     elseif command:lower() == "boot" then                           -- boot command
  188.         args:addOption("--entry", "-e")
  189.         args:addFlag("--help", "-h")
  190.         args:parse(arguments)
  191.  
  192.         local flags = args:getFlags()
  193.         local options = args:getOptions()
  194.         local boot_args = args:getArgs()
  195.  
  196.         if flags["--help"] then
  197.             print("usage: boot --entry | -e path/to/entry")
  198.             print("Boots from specified path if entry exists")
  199.             return
  200.         end
  201.  
  202.         if options["--entry"] then
  203.             if not fs.exists(options["--entry"]) then
  204.                 print_err("Error: Path does not exists")
  205.                 return
  206.             end
  207.  
  208.             local path = options["--entry"]
  209.  
  210.             local handle = fs.open(path, "r")
  211.  
  212.             if not handle then
  213.                 print_err("Error: Cannot open the Path")
  214.                 return
  215.             end
  216.  
  217.             local tag = handle.read(#bootable_tag)
  218.  
  219.             if not tag == bootable_tag then
  220.                 print_err("Error: Path is not valid bootable media!")
  221.                 return
  222.             end
  223.  
  224.             term.clear()
  225.             term.setCursorPos(0,0)
  226.  
  227.             shell.setDir(fs.getDir(path))
  228.             shell.run(path, table.concat(boot_args, " "))
  229.         else
  230.             print("usage: boot --entry | -e path/to/entry")
  231.         end
  232.     elseif command:lower() == "chkmedia" then
  233.         args:addOption("--media", "-m")
  234.         args:parse(arguments)
  235.        
  236.         local options = args:getOptions()
  237.  
  238.         if not fs.exists(options["--media"]) then
  239.             print_err("Error: Path does not exists")
  240.             return
  241.         end
  242.  
  243.         local handle = fs.open(options["--media"], "r")
  244.  
  245.         if not handle then
  246.             print_err("Error: Cannot open Path")
  247.             return
  248.         end
  249.  
  250.         local tag = handle.read(#bootable_tag)
  251.  
  252.         print(tag == bootable_tag)
  253.     elseif command:lower() == "help" then
  254.         print("Allowed commands: shutdown reboot boot halt help chkmedia")
  255.     elseif command:lower() == "halt" then
  256.         halt()
  257.     else
  258.         print("Allowed commands: shutdown reboot boot halt help chkmedia")
  259.     end
  260. end
  261.  
  262.  
  263. function runCLI()
  264.     while true do
  265.         -- read input
  266.         write("> ")
  267.         local input = read()
  268.         local sp = split(input, ' ')
  269.  
  270.         local arguments = table.remove(sp, 1)
  271.  
  272.         handleCommand(sp[1], arguments)
  273.     end
  274. end
  275.  
  276.  
  277. --[[
  278.  
  279.         BOOTLOADER
  280.  
  281. ]]
  282.  
  283. local function drawEntrySelector()
  284.     term.clear()
  285.     term.setCursorPos(0,0)
  286.  
  287.     print_centered(1, "CBL "..version .. "\n")
  288.     for i = 1, w do
  289.         write("=")
  290.     end
  291.     term.setCursorPos(1,3)
  292.    
  293.     for i, v in ipairs(bootCfg.options) do
  294.         if nOption == i then
  295.             term.setBackgroundColor(colors.lightGray)
  296.             term.setTextColor(colors.black)
  297.             term.clearLine()
  298.             print(" > " .. v.name)
  299.             term.setBackgroundColor(colors.black)
  300.             term.setTextColor(colors.white)
  301.         else
  302.             print("   " .. v.name)
  303.         end
  304.     end
  305.     for i = 1, w do
  306.         write("=")
  307.     end
  308.     write("\n")
  309.     print("Use arrows to select and enter to boot")
  310. end
  311.  
  312. local function handleTimer()
  313.     while true do
  314.         local event = os.pullEvent("timer")
  315.         if event == "timer" then
  316.             break
  317.         end
  318.     end
  319. end
  320.  
  321. local function handleInput()
  322.     while true do
  323.         local event, key = os.pullEvent("key")
  324.         if event == "key" then
  325.             if keys.getName(key) == "up" then
  326.                 os.cancelTimer(timer)
  327.                 if nOption > 1 then
  328.                     nOption = nOption - 1
  329.                 end
  330.             elseif keys.getName(key) == "down" then
  331.                 os.cancelTimer(timer)
  332.                 if nOption < #bootCfg.options then
  333.                     nOption = nOption + 1
  334.                 end
  335.             elseif keys.getName(key) == "enter" then
  336.                 os.cancelTimer(timer)
  337.                 break
  338.             end
  339.  
  340.             term.clear()
  341.             drawEntrySelector()
  342.         end
  343.     end
  344. end
  345.  
  346.  
  347.  
  348. if createIfNotExists("/etc/craftbl/boot.cfg") then
  349.     bootCfg = {
  350.         timeout = 3,
  351.         default_option = 1,
  352.         options = {
  353.             [1] = {
  354.                 name = "DiamondOS",
  355.                 path = "/boot/boot.lua",
  356.                 args = {},
  357.             },
  358.             [2] = {
  359.                 name = "CraftBootloader CLI",
  360.                 path = "craft://cli",
  361.                 args = {},
  362.             },
  363.         },
  364.     }
  365.  
  366.     save(bootCfg, "/etc/craftbl/boot.cfg")
  367. end
  368.  
  369. -- preload
  370.  
  371. bootCfg = load("/etc/craftbl/boot.cfg")
  372.  
  373. if bootCfg == nil then
  374.     print_err("No boot configuration found!")
  375.     runCLI()
  376. elseif bootCfg.options == nil then
  377.     print_err("No boot options found!")
  378.     runCLI()
  379. elseif bootCfg.default_option == nil then
  380.     bootCfg.default_option = 1
  381. end
  382.  
  383. if #bootCfg.options > 1 then
  384. -- menu (if more than one entry)
  385.  
  386.     nOption = bootCfg.default_option
  387.  
  388.     drawEntrySelector()
  389.  
  390.     print("Botting in " .. bootCfg.timeout .. " sec")
  391.  
  392.     timer = os.startTimer(bootCfg.timeout)
  393.  
  394.     parallel.waitForAny(handleInput, handleTimer)
  395.  
  396.     term.clear()
  397.     term.setCursorPos(1,1)
  398.  
  399.     if bootCfg.options[nOption].path == "craft://cli" then
  400.         os.cancelTimer(timer)
  401.         runCLI()
  402.     end
  403.  
  404.     local handle = fs.open(bootCfg.options[nOption].path, "r")                -- look for boot pattern on first line ("-- CRAFTBOOT BOOTABLE MEDIA")
  405.     if not handle then
  406.         print_err("Unable to read boot path")
  407.         os.cancelTimer(timer)
  408.         runCLI()
  409.     end
  410.     local entry = handle.read(#bootable_tag)
  411.     if entry ~= bootable_tag then
  412.         print_err(bootCfg.options[nOption].path .. " is not bootable media!")
  413.         os.cancelTimer(timer)
  414.         runCLI()
  415.     end
  416.  
  417.     shell.setDir(fs.getDir(bootCfg.options[nOption].path))
  418.     shell.run(bootCfg.options[nOption].path, table.concat(bootCfg.options[nOption].args, " "))
  419. else
  420.     term.clear()
  421.     term.setCursorPos(1,1)
  422.  
  423.     if bootCfg.options[1].path == "craft://cli" then
  424.         runCLI()
  425.     end
  426.  
  427.     local handle = fs.open(bootCfg.options[1].path, "r")                -- look for boot pattern on first line ("-- CRAFTBOOT BOOTABLE MEDIA")
  428.     local entry = handle.read(#bootable_tag)
  429.     if entry ~= bootable_tag then
  430.         print_err(bootCfg.options[1] .. " is not bootable media!")
  431.         runCLI()
  432.     end
  433.  
  434.     shell.setDir(fs.getDir(bootCfg.options[1].path))
  435.     shell.run(bootCfg.options[1].path, table.concat(bootCfg.options[1].args, " "))
  436. end
Advertisement
Add Comment
Please, Sign In to add comment