diff --git a/admin/autoupdater/files/usr/lib/autoupdater/abort.d/README b/admin/autoupdater/files/usr/lib/autoupdater/abort.d/README new file mode 100644 index 0000000..014518a --- /dev/null +++ b/admin/autoupdater/files/usr/lib/autoupdater/abort.d/README @@ -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. diff --git a/admin/autoupdater/files/usr/lib/autoupdater/download.d/95drop_caches b/admin/autoupdater/files/usr/lib/autoupdater/download.d/95drop_caches new file mode 100755 index 0000000..814159f --- /dev/null +++ b/admin/autoupdater/files/usr/lib/autoupdater/download.d/95drop_caches @@ -0,0 +1,4 @@ +#!/bin/sh + +sync +sysctl -w vm.drop_caches=3 diff --git a/admin/autoupdater/files/usr/lib/autoupdater/download.d/README b/admin/autoupdater/files/usr/lib/autoupdater/download.d/README new file mode 100644 index 0000000..c1c53e5 --- /dev/null +++ b/admin/autoupdater/files/usr/lib/autoupdater/download.d/README @@ -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. diff --git a/admin/autoupdater/files/usr/lib/lua/autoupdater/util.lua b/admin/autoupdater/files/usr/lib/lua/autoupdater/util.lua index 34640ca..d5dd67e 100644 --- a/admin/autoupdater/files/usr/lib/lua/autoupdater/util.lua +++ b/admin/autoupdater/files/usr/lib/lua/autoupdater/util.lua @@ -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 diff --git a/admin/autoupdater/files/usr/sbin/autoupdater b/admin/autoupdater/files/usr/sbin/autoupdater index 83aea72..681d35c 100755 --- a/admin/autoupdater/files/usr/sbin/autoupdater +++ b/admin/autoupdater/files/usr/sbin/autoupdater @@ -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