autoupdater: convert spaces to tabs

This commit is contained in:
Matthias Schiffer 2017-02-24 23:04:05 +01:00
parent 463f872844
commit ac55ad56b5
No known key found for this signature in database
GPG Key ID: 16EF3F64CB201D9C
3 changed files with 361 additions and 361 deletions

View File

@ -8,126 +8,126 @@ module('autoupdater.util', package.seeall)
-- Executes a command in the background, without parsing the command through a shell (in contrast to os.execute) -- Executes a command in the background, without parsing the command through a shell (in contrast to os.execute)
function exec(timeout, ...) function exec(timeout, ...)
local pid, errno, error = nixio.fork() local pid, errno, error = nixio.fork()
if pid == 0 then if pid == 0 then
nixio.execp(...) nixio.execp(...)
os.exit(127) os.exit(127)
elseif pid > 0 then elseif pid > 0 then
if timeout then if timeout then
local starttime = os.time() local starttime = os.time()
while true do while true do
if os.difftime(os.time(), starttime) > timeout then if os.difftime(os.time(), starttime) > timeout then
nixio.kill(pid, nixio.const.SIGTERM) nixio.kill(pid, nixio.const.SIGTERM)
end end
local wpid, status, code = nixio.waitpid(pid, 'nohang') local wpid, status, code = nixio.waitpid(pid, 'nohang')
if wpid then if wpid then
return wpid and status == 'exited' and code return wpid and status == 'exited' and code
end end
nixio.nanosleep(1) nixio.nanosleep(1)
end end
else else
local wpid, status, code = nixio.waitpid(pid) local wpid, status, code = nixio.waitpid(pid)
return wpid and status == 'exited' and code return wpid and status == 'exited' and code
end end
else else
return pid, errno, error return pid, errno, error
end end
end end
-- Executes a command in the background, returning its PID and a pipe connected to the command's standard input -- Executes a command in the background, returning its PID and a pipe connected to the command's standard input
function popen(write, ...) function popen(write, ...)
local inr, inw = nixio.pipe() local inr, inw = nixio.pipe()
local pid = nixio.fork() local pid = nixio.fork()
if pid > 0 then if pid > 0 then
if write then if write then
inr:close() inr:close()
return pid, inw return pid, inw
else else
inw:close() inw:close()
return pid, inr return pid, inr
end end
elseif pid == 0 then elseif pid == 0 then
if write then if write then
nixio.dup(inr, nixio.stdin) nixio.dup(inr, nixio.stdin)
else else
nixio.dup(inw, nixio.stdout) nixio.dup(inw, nixio.stdout)
end end
inr:close() inr:close()
inw:close() inw:close()
nixio.execp(...) nixio.execp(...)
os.exit(127) os.exit(127)
end end
end end
-- Executes all executable files in a directory -- Executes all executable files in a directory
function run_dir(dir) function run_dir(dir)
local function is_ok(entry) local function is_ok(entry)
if entry:sub(1, 1) == '.' then if entry:sub(1, 1) == '.' then
return false return false
end end
local file = dir .. '/' .. entry local file = dir .. '/' .. entry
if fs.stat(file, 'type') ~= 'reg' then if fs.stat(file, 'type') ~= 'reg' then
return false return false
end end
if not fs.access(file, 'x') then if not fs.access(file, 'x') then
return false return false
end end
return true return true
end end
local files = util.consume(fs.dir(dir)) local files = util.consume(fs.dir(dir))
if not files then if not files then
return return
end end
table.sort(files) table.sort(files)
for _, entry in ipairs(files) do for _, entry in ipairs(files) do
if is_ok(entry) then if is_ok(entry) then
exec(nil, dir .. '/' .. entry) exec(nil, dir .. '/' .. entry)
end end
end end
end end
-- Seeds Lua's random generator from /dev/urandom -- Seeds Lua's random generator from /dev/urandom
function randomseed() function randomseed()
local f = io.open('/dev/urandom', 'r') local f = io.open('/dev/urandom', 'r')
local b1, b2, b3, b4 = f:read(4):byte(1, 4) local b1, b2, b3, b4 = f:read(4):byte(1, 4)
f:close() f:close()
-- The and is necessary as Lua on OpenWrt doesn't like integers over 2^31-1 -- The and is necessary as Lua on OpenWrt doesn't like integers over 2^31-1
math.randomseed(nixio.bit.band(b1*0x1000000 + b2*0x10000 + b3*0x100 + b4, 0x7fffffff)) math.randomseed(nixio.bit.band(b1*0x1000000 + b2*0x10000 + b3*0x100 + b4, 0x7fffffff))
end end
-- Takes a date and time in RFC3339 format and returns a Unix timestamp -- Takes a date and time in RFC3339 format and returns a Unix timestamp
function parse_date(date) function parse_date(date)
local year, month, day, hour, minute, second, tzs, tzh, tzm = date:match('^(%d%d%d%d)%-(%d%d)%-(%d%d) (%d%d):(%d%d):(%d%d)([%+%-])(%d%d):(%d%d)$') local year, month, day, hour, minute, second, tzs, tzh, tzm = date:match('^(%d%d%d%d)%-(%d%d)%-(%d%d) (%d%d):(%d%d):(%d%d)([%+%-])(%d%d):(%d%d)$')
if not year then if not year then
return nil return nil
end end
local a = math.floor((14 - month)/12) local a = math.floor((14 - month)/12)
local y = year - a local y = year - a
local m = month + 12*a - 3 local m = month + 12*a - 3
-- Based on a well-known formula for Julian dates -- Based on a well-known formula for Julian dates
local days = day + math.floor((153*m + 2)/5) + 365*y + math.floor(y/4) - math.floor(y/100) + math.floor(y/400) - 719469 local days = day + math.floor((153*m + 2)/5) + 365*y + math.floor(y/4) - math.floor(y/100) + math.floor(y/400) - 719469
local time = hour*3600 + minute*60 + second local time = hour*3600 + minute*60 + second
local tz = tzh*3600 + tzm*60 local tz = tzh*3600 + tzm*60
if tzs == '-' then if tzs == '-' then
tz = -tz tz = -tz
end end
return days * 86400 + time - tz return days * 86400 + time - tz
end end

View File

@ -3,77 +3,77 @@ module 'autoupdater.version'
-- version comparison is based on dpkg code -- version comparison is based on dpkg code
local function isdigit(s, i) local function isdigit(s, i)
local c = s:sub(i, i) local c = s:sub(i, i)
return c and c:match('^%d$') return c and c:match('^%d$')
end end
local function char_value(s, i) local function char_value(s, i)
return s:byte(i, i) or 0 return s:byte(i, i) or 0
end end
local function char_order(s, i) local function char_order(s, i)
local c = s:sub(i, i) local c = s:sub(i, i)
if c == '' or c:match('^%d$') then if c == '' or c:match('^%d$') then
return 0 return 0
elseif c:match('^%a$') then elseif c:match('^%a$') then
return c:byte() return c:byte()
elseif c == '~' then elseif c == '~' then
return -1 return -1
else else
return c:byte() + 256 return c:byte() + 256
end end
end end
-- returns true when a is a higher version number than b -- returns true when a is a higher version number than b
function newer_than(a, b) function newer_than(a, b)
local apos = 1 local apos = 1
local bpos = 1 local bpos = 1
while apos <= a:len() or bpos <= b:len() do while apos <= a:len() or bpos <= b:len() do
local first_diff = 0 local first_diff = 0
while (apos <= a:len() and not isdigit(a, apos)) or (bpos <= b:len() and not isdigit(b, bpos)) do while (apos <= a:len() and not isdigit(a, apos)) or (bpos <= b:len() and not isdigit(b, bpos)) do
local ac = char_order(a, apos) local ac = char_order(a, apos)
local bc = char_order(b, bpos) local bc = char_order(b, bpos)
if ac ~= bc then if ac ~= bc then
return ac > bc return ac > bc
end end
apos = apos + 1 apos = apos + 1
bpos = bpos + 1 bpos = bpos + 1
end end
while a:sub(apos, apos) == '0' do while a:sub(apos, apos) == '0' do
apos = apos + 1 apos = apos + 1
end end
while b:sub(bpos, bpos) == '0' do while b:sub(bpos, bpos) == '0' do
bpos = bpos + 1 bpos = bpos + 1
end end
while isdigit(a, apos) and isdigit(b, bpos) do while isdigit(a, apos) and isdigit(b, bpos) do
if first_diff == 0 then if first_diff == 0 then
first_diff = char_value(a, apos) - char_value(b, bpos) first_diff = char_value(a, apos) - char_value(b, bpos)
end end
apos = apos + 1 apos = apos + 1
bpos = bpos + 1 bpos = bpos + 1
end end
if isdigit(a, apos) then if isdigit(a, apos) then
return true return true
end end
if isdigit(b, bpos) then if isdigit(b, bpos) then
return false return false
end end
if first_diff ~= 0 then if first_diff ~= 0 then
return first_diff > 0 return first_diff > 0
end end
end end
return false return false
end end

View File

@ -11,8 +11,8 @@ local autoupdater_version = require('autoupdater.version')
if not platform_info.get_image_name() then if not platform_info.get_image_name() then
io.stderr:write("The autoupdater doesn't support this hardware model.\n") io.stderr:write("The autoupdater doesn't support this hardware model.\n")
os.exit(1) os.exit(1)
end end
@ -38,30 +38,30 @@ local fallback = false
local mirrors = {} local mirrors = {}
local function parse_args() local function parse_args()
local i = 1 local i = 1
while arg[i] do while arg[i] do
if arg[i] == '-f' then if arg[i] == '-f' then
force = true force = true
elseif arg[i] == '--fallback' then elseif arg[i] == '--fallback' then
fallback = true fallback = true
elseif arg[i] == '-b' then elseif arg[i] == '-b' then
i = i+1 i = i+1
if not arg[i] then if not arg[i] then
io.stderr:write("Error parsing command line: expected branch name\n") io.stderr:write("Error parsing command line: expected branch name\n")
os.exit(1) os.exit(1)
end end
branch_name = arg[i] branch_name = arg[i]
elseif arg[i]:sub(0, 1) == '-' then elseif arg[i]:sub(0, 1) == '-' then
io.stderr:write("Error parsing command line: unexpected argument '" .. arg[i] .. "'\n") io.stderr:write("Error parsing command line: unexpected argument '" .. arg[i] .. "'\n")
os.exit(1) os.exit(1)
else else
table.insert(mirrors, arg[i]) table.insert(mirrors, arg[i])
end end
i = i+1 i = i+1
end end
end end
@ -70,274 +70,274 @@ parse_args()
local branch = uci:get_all('autoupdater', branch_name) local branch = uci:get_all('autoupdater', branch_name)
if not branch then if not branch then
io.stderr:write("Can't find configuration for branch '" .. branch_name .. "'\n") io.stderr:write("Can't find configuration for branch '" .. branch_name .. "'\n")
os.exit(1) os.exit(1)
end end
if settings.enabled ~= '1' and not force then if settings.enabled ~= '1' and not force then
io.stderr:write('autoupdater is disabled.\n') io.stderr:write('autoupdater is disabled.\n')
os.exit(0) os.exit(0)
end end
-- Verifies a file given as a list of lines with a list of signatures using ecdsaverify -- Verifies a file given as a list of lines with a list of signatures using ecdsaverify
local function verify_lines(lines, sigs) local function verify_lines(lines, sigs)
local command = {'ecdsaverify', '-n', tostring(branch.good_signatures)} local command = {'ecdsaverify', '-n', tostring(branch.good_signatures)}
-- Build command line from sigs and branch.pubkey -- Build command line from sigs and branch.pubkey
for _, sig in ipairs(sigs) do for _, sig in ipairs(sigs) do
if sig:match('^' .. string.rep('%x', 128) .. '$') then if sig:match('^' .. string.rep('%x', 128) .. '$') then
table.insert(command, '-s') table.insert(command, '-s')
table.insert(command, sig) table.insert(command, sig)
end end
end end
for _, key in ipairs(branch.pubkey) do for _, key in ipairs(branch.pubkey) do
if key:match('^' .. string.rep('%x', 64) .. '$') then if key:match('^' .. string.rep('%x', 64) .. '$') then
table.insert(command, '-p') table.insert(command, '-p')
table.insert(command, key) table.insert(command, key)
end end
end end
-- Call ecdsautils -- Call ecdsautils
local pid, f = autoupdater_util.popen(true, unpack(command)) local pid, f = autoupdater_util.popen(true, unpack(command))
for _, line in ipairs(lines) do for _, line in ipairs(lines) do
f:write(line) f:write(line)
f:write('\n') f:write('\n')
end end
f:close() f:close()
local wpid, status, code = nixio.waitpid(pid) local wpid, status, code = nixio.waitpid(pid)
return wpid and status == 'exited' and code == 0 return wpid and status == 'exited' and code == 0
end end
-- Downloads, parses and verifies the update manifest from a mirror -- Downloads, parses and verifies the update manifest from a mirror
-- Returns a table with the fields version, checksum and filename if everything is ok, nil otherwise -- Returns a table with the fields version, checksum and filename if everything is ok, nil otherwise
local function read_manifest(mirror) local function read_manifest(mirror)
local sep = false local sep = false
local lines = {} local lines = {}
local sigs = {} local sigs = {}
local branch_ok = false local branch_ok = false
local ret = {} local ret = {}
-- Remove potential trailing slash -- Remove potential trailing slash
mirror = mirror:gsub('/$', '') mirror = mirror:gsub('/$', '')
local starttime = os.time() local starttime = os.time()
local pid, manifest_loader = autoupdater_util.popen(false, 'wget', '-T', '120', '-O-', string.format('%s/%s.manifest', mirror, branch.name)) local pid, manifest_loader = autoupdater_util.popen(false, 'wget', '-T', '120', '-O-', string.format('%s/%s.manifest', mirror, branch.name))
local data = '' local data = ''
-- Read all lines from the manifest -- Read all lines from the manifest
-- The upper part is saved to lines, the lower part to sigs -- The upper part is saved to lines, the lower part to sigs
while true do while true do
-- If the manifest download takes more than 5 minutes, we don't really -- If the manifest download takes more than 5 minutes, we don't really
-- have a chance to download a whole image -- have a chance to download a whole image
local timeout = starttime+300 - os.time() local timeout = starttime+300 - os.time()
if timeout < 0 or not nixio.poll({{fd = manifest_loader, events = nixio.poll_flags('in')}}, timeout * 1000) then if timeout < 0 or not nixio.poll({{fd = manifest_loader, events = nixio.poll_flags('in')}}, timeout * 1000) then
io.stderr:write("Timeout while reading manifest.\n") io.stderr:write("Timeout while reading manifest.\n")
nixio.kill(pid, nixio.const.SIGTERM) nixio.kill(pid, nixio.const.SIGTERM)
manifest_loader:close() manifest_loader:close()
return nil return nil
end end
local r = manifest_loader:read(1024) local r = manifest_loader:read(1024)
if not r or r == '' then if not r or r == '' then
break break
end end
data = data .. r data = data .. r
while data:match('\n') do while data:match('\n') do
local line, rest = data:match('^([^\n]*)\n(.*)$') local line, rest = data:match('^([^\n]*)\n(.*)$')
data = rest data = rest
if not sep then if not sep then
if line == '---' then if line == '---' then
sep = true sep = true
else else
table.insert(lines, line) table.insert(lines, line)
if line == ('BRANCH=' .. branch.name) then if line == ('BRANCH=' .. branch.name) then
branch_ok = true branch_ok = true
end end
local date = line:match('^DATE=(.+)$') local date = line:match('^DATE=(.+)$')
local priority = line:match('^PRIORITY=([%d%.]+)$') local priority = line:match('^PRIORITY=([%d%.]+)$')
local model, version, checksum, filename = line:match('^([^ ]+) ([^ ]+) ([^ ]+) ([^ ]+)$') local model, version, checksum, filename = line:match('^([^ ]+) ([^ ]+) ([^ ]+) ([^ ]+)$')
if date then if date then
ret.date = autoupdater_util.parse_date(date) ret.date = autoupdater_util.parse_date(date)
elseif priority then elseif priority then
ret.priority = tonumber(priority) ret.priority = tonumber(priority)
elseif model == platform_info.get_image_name() then elseif model == platform_info.get_image_name() then
ret.version = version ret.version = version
ret.checksum = checksum ret.checksum = checksum
ret.filename = filename ret.filename = filename
end end
end end
else else
table.insert(sigs, line) table.insert(sigs, line)
end end
end end
end end
manifest_loader:close() manifest_loader:close()
-- Do some very basic checks before checking the signatures -- Do some very basic checks before checking the signatures
-- (as the signature verification is computationally expensive) -- (as the signature verification is computationally expensive)
if not sep then if not sep then
io.stderr:write('There seems to have gone something wrong downloading the manifest from ' .. mirror .. '\n') io.stderr:write('There seems to have gone something wrong downloading the manifest from ' .. mirror .. '\n')
return nil return nil
end end
if not ret.date or not ret.priority then if not ret.date or not ret.priority then
io.stderr:write('The manifest downloaded from ' .. mirror .. ' is invalid (DATE or PRIORITY missing)\n') io.stderr:write('The manifest downloaded from ' .. mirror .. ' is invalid (DATE or PRIORITY missing)\n')
return nil return nil
end end
if not branch_ok then if not branch_ok then
io.stderr:write('Wrong branch. We are on ', branch.name, '.\n') io.stderr:write('Wrong branch. We are on ', branch.name, '.\n')
return nil return nil
end end
if not ret.version then if not ret.version then
io.stderr:write('No matching firmware found (model ' .. platform_info.get_image_name() .. ')\n') io.stderr:write('No matching firmware found (model ' .. platform_info.get_image_name() .. ')\n')
return nil return nil
end end
if not verify_lines(lines, sigs) then if not verify_lines(lines, sigs) then
io.stderr:write('Not enough valid signatures!\n') io.stderr:write('Not enough valid signatures!\n')
return nil return nil
end end
return ret return ret
end end
-- Downloads the firmware image from a mirror to a given output file -- Downloads the firmware image from a mirror to a given output file
local function fetch_firmware(mirror, filename, output) local function fetch_firmware(mirror, filename, output)
-- Let's give the image download 30 minutes, hopefully more than enough -- Let's give the image download 30 minutes, hopefully more than enough
if autoupdater_util.exec(1800, 'wget', '-T', '120', '-O', output, mirror .. '/' .. filename) ~= 0 then if autoupdater_util.exec(1800, 'wget', '-T', '120', '-O', output, mirror .. '/' .. filename) ~= 0 then
io.stderr:write('Error downloading the image from ' .. mirror .. '\n') io.stderr:write('Error downloading the image from ' .. mirror .. '\n')
return false return false
end end
return true return true
end end
-- Returns the computed update probability -- Returns the computed update probability
local function get_probability(date, priority) local function get_probability(date, priority)
local seconds = priority * 86400 local seconds = priority * 86400
local diff = os.difftime(os.time(), date) local diff = os.difftime(os.time(), date)
if diff < 0 then if diff < 0 then
-- When the difference is negative, there are two possibilities: The manifest contains a wrong date, or our own clock is wrong. -- When the difference is negative, there are two possibilities: The manifest contains a wrong date, or our own clock is wrong.
-- As there isn't anything useful to do for an incorrect manifest, we'll assume the latter case and update anyways as we -- As there isn't anything useful to do for an incorrect manifest, we'll assume the latter case and update anyways as we
-- can't do anything better -- can't do anything better
io.stderr:write('Warning: clock seems to be incorrect.\n') io.stderr:write('Warning: clock seems to be incorrect.\n')
if tonumber(fs.readfile('/proc/uptime'):match('^([^ ]+) ')) < 600 then if tonumber(fs.readfile('/proc/uptime'):match('^([^ ]+) ')) < 600 then
-- If the uptime is very low, it's possible we just didn't get the time over NTP yet, so we'll just wait until the next time the updater runs -- If the uptime is very low, it's possible we just didn't get the time over NTP yet, so we'll just wait until the next time the updater runs
return 0 return 0
else else
-- Will give 1 when priority == 0, and lower probabilities the higher the priority value is -- Will give 1 when priority == 0, and lower probabilities the higher the priority value is
-- (similar to the old static probability system) -- (similar to the old static probability system)
return 0.75^priority return 0.75^priority
end end
elseif fallback then elseif fallback then
if diff >= seconds + 86400 then if diff >= seconds + 86400 then
return 1 return 1
else else
return 0 return 0
end end
elseif diff >= seconds then elseif diff >= seconds then
return 1 return 1
else else
local x = diff/seconds local x = diff/seconds
-- This is the most simple polynomial with value 0 at 0, 1 at 1, and whose first derivative is 0 at both 0 and 1 -- This is the most simple polynomial with value 0 at 0, 1 at 1, and whose first derivative is 0 at both 0 and 1
-- (we all love continuously differentiable functions, right?) -- (we all love continuously differentiable functions, right?)
return (-2)*x^3 + 3*x^2 return (-2)*x^3 + 3*x^2
end end
end end
-- Tries to perform an update from a given mirror -- Tries to perform an update from a given mirror
local function autoupdate(mirror) local function autoupdate(mirror)
local download_d_dir = '/usr/lib/autoupdater/download.d' local download_d_dir = '/usr/lib/autoupdater/download.d'
local abort_d_dir = '/usr/lib/autoupdater/abort.d' local abort_d_dir = '/usr/lib/autoupdater/abort.d'
local upgrade_d_dir = '/usr/lib/autoupdater/upgrade.d' local upgrade_d_dir = '/usr/lib/autoupdater/upgrade.d'
local manifest = read_manifest(mirror) local manifest = read_manifest(mirror)
if not manifest then if not manifest then
return false return false
end end
if not autoupdater_version.newer_than(manifest.version, old_version) then if not autoupdater_version.newer_than(manifest.version, old_version) then
io.stderr:write('No new firmware available.\n') io.stderr:write('No new firmware available.\n')
return true return true
end end
io.stderr:write('New version available.\n') io.stderr:write('New version available.\n')
if not force and math.random() >= get_probability(manifest.date, manifest.priority) then if not force and math.random() >= get_probability(manifest.date, manifest.priority) then
io.stderr:write('No autoupdate this time. Use -f to override.\n') io.stderr:write('No autoupdate this time. Use -f to override.\n')
return true return true
end end
autoupdater_util.run_dir(download_d_dir) autoupdater_util.run_dir(download_d_dir)
collectgarbage() collectgarbage()
local image = os.tmpname() local image = os.tmpname()
if not fetch_firmware(mirror, manifest.filename, image) then if not fetch_firmware(mirror, manifest.filename, image) then
autoupdater_util.run_dir(abort_d_dir) autoupdater_util.run_dir(abort_d_dir)
return false return false
end end
local popen = io.popen(string.format("exec sha512sum '%s'", image)) local popen = io.popen(string.format("exec sha512sum '%s'", image))
local checksum = popen:read('*l'):match('^%x+') local checksum = popen:read('*l'):match('^%x+')
popen:close() popen:close()
if checksum ~= manifest.checksum then if checksum ~= manifest.checksum then
io.stderr:write('Invalid image checksum!\n') io.stderr:write('Invalid image checksum!\n')
os.remove(image) os.remove(image)
autoupdater_util.run_dir(abort_d_dir) autoupdater_util.run_dir(abort_d_dir)
return false return false
end end
autoupdater_util.run_dir(upgrade_d_dir) autoupdater_util.run_dir(upgrade_d_dir)
io.stderr:write('Upgrading firmware...\n') io.stderr:write('Upgrading firmware...\n')
local null = nixio.open('/dev/null', 'w+') local null = nixio.open('/dev/null', 'w+')
if null then if null then
nixio.dup(null, nixio.stdin) nixio.dup(null, nixio.stdin)
nixio.dup(null, nixio.stderr) nixio.dup(null, nixio.stderr)
if null:fileno() > 2 then if null:fileno() > 2 then
null:close() null:close()
end end
end end
nixio.exec('/sbin/sysupgrade', image) nixio.exec('/sbin/sysupgrade', image)
-- This should never be reached as nixio.exec replaces the autoupdater process unless /sbin/sysupgrade can't be executed -- This should never be reached as nixio.exec replaces the autoupdater process unless /sbin/sysupgrade can't be executed
-- We output the error message through stdout as stderr isn't available anymore -- We output the error message through stdout as stderr isn't available anymore
io.write('Failed to call sysupgrade?\n') io.write('Failed to call sysupgrade?\n')
os.remove(image) os.remove(image)
autoupdater_util.run_dir(abort_d_dir) autoupdater_util.run_dir(abort_d_dir)
os.exit(1) os.exit(1)
end end
@ -345,24 +345,24 @@ local lockfile = '/var/lock/autoupdater.lock'
local lockfd = nixio.open(lockfile, 'w', 'rw-------') local lockfd = nixio.open(lockfile, 'w', 'rw-------')
if not lockfd:lock('tlock') then if not lockfd:lock('tlock') then
io.stderr:write(string.format( io.stderr:write(string.format(
"Unable to lock file %s. Make sure there is no other instance of the autoupdater running.\n", "Unable to lock file %s. Make sure there is no other instance of the autoupdater running.\n",
lockfile, err lockfile, err
)) ))
os.exit(1) os.exit(1)
end end
if #mirrors == 0 then if #mirrors == 0 then
while #branch.mirror > 0 do while #branch.mirror > 0 do
table.insert(mirrors, table.remove(branch.mirror, math.random(#branch.mirror))) table.insert(mirrors, table.remove(branch.mirror, math.random(#branch.mirror)))
end end
end end
for k, mirror in ipairs(mirrors) do for k, mirror in ipairs(mirrors) do
if autoupdate(mirror) then if autoupdate(mirror) then
os.exit(0) os.exit(0)
end end
end end
io.stderr:write('No usable mirror found.\n') io.stderr:write('No usable mirror found.\n')