#!/usr/bin/lua -- Created by jiaqiang.ye@lnxall.com -- LNXALL firmware automatic upgrade -- 2022/04/11 local cjson = require 'cjson' local posix = require 'posix' local invoker = require 'invoker' -- global variables local g_looping = true local g_firmfile = nil -- firmware download path local g_dlpid = nil -- PID of download program local function fup_handler(signo) g_looping = false local tmpval = g_dlpid -- important: kill downloading process if tmpval then g_dlpid = nil invoker.kill(tmpval, posix.SIGTERM) end tmpval = g_firmfile if tmpval then g_firmfile = nil posix.unlink(tmpval) end end local function os_execute(dl_cmd) local dl_pid = invoker.invoke(invoker.NOWAIT, dl_cmd) if not dl_pid then return false end g_dlpid = dl_pid -- store downloading PID to `g_dlpid local exited, eval = nil, nil local uptim = invoker.uptime() while g_looping do exited, eval = invoker.waitpid(dl_pid, true) if exited then break end local upt = invoker.uptime() if (upt - uptim) >= 30 then uptim = upt io.stdout:write("Firmware downloadinging in progress...\n") io.stdout:flush() end -- check g_looping before sleeping via `waitsec if not g_looping then break end invoker.waitsec(2) end g_dlpid = nil -- set global download PID to nil if not exited then invoker.kill(dl_pid, posix.SIGTERM) invoker.waitpid(dl_pid, false) return false end exited, eval = invoker.exited(eval) if exited and eval == 0 then return true end return false end local function download_firmware(url, md5sum) local dlfirm = string.match(url, "/([^/]+)$") if not dlfirm or #dlfirm == 0 or dlfirm == url then io.stderr:write("Error, invalid firmware URL: " .. url .. "\n") io.stderr:flush() return false end -- remove firmware if already exists: dlfirm = string.format("/tmp/%s", dlfirm) if posix.access(dlfirm) then posix.unlink(dlfirm) end g_firmfile = dlfirm -- set global variable to the path of downloaded firmware io.stdout:write(string.format("Downloading with curl: %s...\n", url)) io.stdout:flush() -- download with curl utility local dlcmd = { 'curl', '--insecure', '--silent', '--retry', '8', '--output', dlfirm, url } local rval = os_execute(dlcmd) if not rval then posix.unlink(dlfirm) if not g_looping then return false end io.stderr:write("Error, failed to download with curl, retry with wget...\n") io.stderr:flush() dlcmd = { 'wget', '-q', '-c', '-t', '8', '--no-check-certificate', '-O', dlfirm, url } rval = os_execute(dlcmd) end if not rval then posix.unlink(dlfirm) if not g_looping then return false end io.stderr:write("Error, failed to download firmware!\n") io.stderr:flush() return false end io.stdout:write("Firmware downloaded successfully!\n") io.stdout:flush() local md5hdl = io.popen(string.format("exec md5sum '%s'", dlfirm)) if not md5hdl then posix.unlink(dlfirm) io.stderr:write("Error, failed to check firmware md5sum!\n") io.stderr:flush() return false end local md5dat = md5hdl:read("*a") md5hdl:close() md5hdl = nil if md5dat then md5dat = string.match(md5dat, "^(%x+)") end if not md5dat or md5dat ~= md5sum then posix.unlink(dlfirm) io.stderr:write("Error, invalid firmware md5sum value!\n") io.stderr:flush() return false end g_firmfile = nil -- set global variable g_firmfile to nil return dlfirm end local function main_func(cfgfile) local cfgs = io.open(cfgfile, "rb") if not cfgs then io.stdout:write(string.format("Firmware_Upgrade file [%s] not found, upgrade skipped\n", cfgfile)) io.stdout:flush() return false end local cfgd = cfgs:read("*a") cfgs:close(); cfgs = nil -- close upgrade config file if not cfgd or string.len(cfgd) == 0 then io.stdout:write("Firmware_Upgrade is empty\n") io.stdout:flush() return false end local okay = nil okay, cfgs = pcall(cjson.decode, cfgd) if not okay or type(cfgs) ~= "table" then io.stderr:write("Error: Firmware_Upgrade config is corrupted!\n") io.stderr:flush() return false end if cfgs["upgrade"] == "1" then io.stderr:write("INFO: Firmware already upgraded!\n") io.stderr:flush() return true end local furl = cfgs["firmware_url"] if type(furl) ~= "string" or #furl == 0 then io.stderr:write("INFO: invalid firmware download URL!\n") io.stderr:flush() return false end local md5val = cfgs["md5"] if type(md5val) ~= "string" or #md5val ~= 32 then io.stderr:write("Error, invalid firmware md5sum given!\n") io.stderr:flush() return false end md5val = md5val:lower() local firmfile = download_firmware(furl, md5val) if not firmfile then return false end cfgs["upgrade"] = "1" cfgd = cjson.encode(cfgs) if not cfgd then posix.unlink(firmfile) io.stderr:write("Error, failed to encode firmware upgrade json data!\n") io.stderr:flush() return false end local cfgh = io.open(cfgfile, "wb") if not cfgh then posix.unlink(firmfile) io.stderr:write("Error, failed to open firmware upgrade config file!\n") io.stderr:flush() return false end cfgh:write(cfgd) cfgh:close(); cfgh = nil io.stdout:write(string.format("Invoking sysupgrade for '%s'...\n", firmfile)) io.stdout:flush() os.execute(string.format("/sbin/sysupgrade '%s' /dev/null 2>&1 &", firmfile)) return true end -- register signal handler for SIGINT/SIGTERM posix.signal(posix.SIGINT, fup_handler) posix.signal(posix.SIGTERM, fup_handler) main_func("/app/config/firmware_upgrade.json") if g_looping then io.stdout:write("firmware_upgrade service will now exit!\n") io.stdout:flush() invoker.waitsec(60) end os.exit(1)