Merge pull request #128 from freifunk-gluon/autoupdater

Autoupdater improvements
This commit is contained in:
Matthias Schiffer 2016-01-12 15:21:33 +01:00
commit 50c763b895
5 changed files with 83 additions and 14 deletions

View File

@ -0,0 +1,6 @@
Executable files in abort.d will be executed if downloading the upgrade
image has failed and should revert the actions from download.d.
We can't really do anything when the download has succeeded, but the
flashing has failed, as the autoupdater process will have been replaced by
sysupgrade by then.

View File

@ -0,0 +1,4 @@
#!/bin/sh
sync
sysctl -w vm.drop_caches=3

View File

@ -0,0 +1,2 @@
Executable files in download.d will be executed after the update manifest
has been verified, but before the actual image is downloaded.

View File

@ -1,13 +1,28 @@
local io = io
local math = math
local nixio = require 'nixio'
local fs = require 'nixio.fs'
local util = require 'nixio.util'
module 'autoupdater.util'
module('autoupdater.util', package.seeall)
-- Executes a command in the background, without parsing the command through a shell (in contrast to os.execute)
function exec(...)
local pid, errno, error = nixio.fork()
if pid == 0 then
nixio.execp(...)
os.exit(127)
elseif pid > 0 then
local wpid, status, code = nixio.waitpid(pid)
return wpid and status == 'exited' and code
else
return pid, errno, error
end
end
-- Executes a command in the background, returning its PID and a pipe connected to the command's standard input
function popen(command)
function popen(...)
local inr, inw = nixio.pipe()
local pid = nixio.fork()
@ -21,7 +36,41 @@ function popen(command)
inr:close()
inw:close()
nixio.exec('/bin/sh', '-c', command)
nixio.execp(...)
os.exit(127)
end
end
-- Executes all executable files in a directory
function run_dir(dir)
local function is_ok(entry)
if entry:sub(1, 1) == '.' then
return false
end
local file = dir .. '/' .. entry
if fs.stat(file, 'type') ~= 'reg' then
return false
end
if not fs.access(file, 'x') then
return false
end
return true
end
local files = util.consume(fs.dir(dir))
if not files then
return
end
table.sort(files)
for _, entry in ipairs(files) do
if is_ok(entry) then
exec(dir .. '/' .. entry)
end
end
end

View File

@ -80,24 +80,26 @@ end
-- Verifies a file given as a list of lines with a list of signatures using ecdsaverify
local function verify_lines(lines, sigs)
local command = string.format('ecdsaverify -n %i', branch.good_signatures)
local command = {'ecdsaverify', '-n', tostring(branch.good_signatures)}
-- Build command line from sigs and branch.pubkey
for _, sig in ipairs(sigs) do
if sig:match('^' .. string.rep('%x', 128) .. '$') then
command = command .. ' -s ' .. sig
table.insert(command, '-s')
table.insert(command, sig)
end
end
for _, key in ipairs(branch.pubkey) do
if key:match('^' .. string.rep('%x', 64) .. '$') then
command = command .. ' -p ' .. key
table.insert(command, '-p')
table.insert(command, key)
end
end
-- Call ecdsautils
local pid, f = autoupdater_util.popen(command)
local pid, f = autoupdater_util.popen(unpack(command))
for _, line in ipairs(lines) do
f:write(line)
@ -126,7 +128,7 @@ local function read_manifest(mirror)
-- Read all lines from the manifest
-- The upper part is saves to lines, the lower part to sigs
for line in io.popen(string.format("wget -T 120 -O- '%s/%s.manifest'", mirror, branch.name), 'r'):lines() do
for line in io.popen(string.format("exec wget -T 120 -O- '%s/%s.manifest'", mirror, branch.name), 'r'):lines() do
if not sep then
if line == '---' then
sep = true
@ -189,7 +191,7 @@ end
-- Downloads the firmware image from a mirror to a given output file
local function fetch_firmware(mirror, filename, output)
if os.execute(string.format("wget -T 120 -O '%s' '%s/%s'", output, mirror, filename)) ~= 0 then
if autoupdater_util.exec('wget', '-T', '120', '-O', output, mirror .. '/' .. filename) ~= 0 then
io.stderr:write('Error downloading the image from ' .. mirror .. '\n')
return false
end
@ -239,6 +241,10 @@ end
-- Tries to perform an update from a given mirror
local function autoupdate(mirror)
local download_d_dir = '/usr/lib/autoupdater/download.d'
local abort_d_dir = '/usr/lib/autoupdater/abort.d'
local manifest = read_manifest(mirror)
if not manifest then
return false
@ -257,21 +263,22 @@ local function autoupdate(mirror)
return true
end
os.execute('sync; sysctl -w vm.drop_caches=3')
autoupdater_util.run_dir(download_d_dir)
collectgarbage()
local image = os.tmpname()
if not fetch_firmware(mirror, manifest.filename, image) then
autoupdater_util.run_dir(abort_d_dir)
return false
end
local popen = io.popen(string.format("sha512sum '%s'", image))
local popen = io.popen(string.format("exec sha512sum '%s'", image))
local checksum = popen:read('*l'):match('^%x+')
popen:close()
if checksum ~= manifest.checksum then
io.stderr:write('Invalid image checksum!\n')
os.remove(image)
autoupdater_util.run_dir(abort_d_dir)
return false
end
@ -291,6 +298,7 @@ local function autoupdate(mirror)
-- We output the error message through stdout as stderr isn't available anymore
io.write('Failed to call sysupgrade?\n')
os.remove(image)
autoupdater_util.run_dir(abort_d_dir)
os.exit(1)
end