From 3d98695abc314ef7a5aa01729b303ae06da03176 Mon Sep 17 00:00:00 2001 From: Matthias Schiffer Date: Sun, 10 Jan 2016 13:50:01 +0100 Subject: [PATCH 1/2] autoupdater: avoid unnessesary shell processes Use functions that don't run commands though a shell where easily possible, add 'exec' to remaining io.popen calls. --- .../files/usr/lib/lua/autoupdater/util.lua | 20 +++++++++++++++++-- admin/autoupdater/files/usr/sbin/autoupdater | 19 ++++++++++-------- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/admin/autoupdater/files/usr/lib/lua/autoupdater/util.lua b/admin/autoupdater/files/usr/lib/lua/autoupdater/util.lua index 34640ca..08c03db 100644 --- a/admin/autoupdater/files/usr/lib/lua/autoupdater/util.lua +++ b/admin/autoupdater/files/usr/lib/lua/autoupdater/util.lua @@ -6,8 +6,23 @@ local nixio = require 'nixio' module 'autoupdater.util' +-- 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,8 @@ function popen(command) inr:close() inw:close() - nixio.exec('/bin/sh', '-c', command) + nixio.execp(...) + os.exit(127) end end diff --git a/admin/autoupdater/files/usr/sbin/autoupdater b/admin/autoupdater/files/usr/sbin/autoupdater index 83aea72..73510b1 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 @@ -258,7 +260,8 @@ local function autoupdate(mirror) end - os.execute('sync; sysctl -w vm.drop_caches=3') + autoupdater_util.exec('sync') + autoupdater_util.exec('sysctl', '-w', 'vm.drop_caches=3') collectgarbage() local image = os.tmpname() @@ -266,7 +269,7 @@ local function autoupdate(mirror) 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 From b7ce1a2002f254544deaed8e4d71ceb1ad731c75 Mon Sep 17 00:00:00 2001 From: Matthias Schiffer Date: Sun, 10 Jan 2016 13:32:35 +0100 Subject: [PATCH 2/2] autoupdater: add download.d and abort.d directories All executables in download.d are executed before after the update manifest has been verified, but before the image is downloaded. This can be used to stop non-essential services to free RAM. abort.d is run when the download has failed and should revert the actions of download.d. --- .../files/usr/lib/autoupdater/abort.d/README | 6 +++ .../lib/autoupdater/download.d/95drop_caches | 4 ++ .../usr/lib/autoupdater/download.d/README | 2 + .../files/usr/lib/lua/autoupdater/util.lua | 39 +++++++++++++++++-- admin/autoupdater/files/usr/sbin/autoupdater | 11 ++++-- 5 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 admin/autoupdater/files/usr/lib/autoupdater/abort.d/README create mode 100755 admin/autoupdater/files/usr/lib/autoupdater/download.d/95drop_caches create mode 100644 admin/autoupdater/files/usr/lib/autoupdater/download.d/README 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 08c03db..d5dd67e 100644 --- a/admin/autoupdater/files/usr/lib/lua/autoupdater/util.lua +++ b/admin/autoupdater/files/usr/lib/lua/autoupdater/util.lua @@ -1,9 +1,9 @@ -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) @@ -42,6 +42,39 @@ function popen(...) 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 + + -- Seeds Lua's random generator from /dev/urandom function randomseed() local f = io.open('/dev/urandom', 'r') diff --git a/admin/autoupdater/files/usr/sbin/autoupdater b/admin/autoupdater/files/usr/sbin/autoupdater index 73510b1..681d35c 100755 --- a/admin/autoupdater/files/usr/sbin/autoupdater +++ b/admin/autoupdater/files/usr/sbin/autoupdater @@ -241,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 @@ -259,13 +263,12 @@ local function autoupdate(mirror) return true end - - autoupdater_util.exec('sync') - autoupdater_util.exec('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 @@ -275,6 +278,7 @@ local function autoupdate(mirror) 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 @@ -294,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