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)
function exec(timeout, ...)
local pid, errno, error = nixio.fork()
if pid == 0 then
nixio.execp(...)
os.exit(127)
elseif pid > 0 then
if timeout then
local starttime = os.time()
while true do
if os.difftime(os.time(), starttime) > timeout then
nixio.kill(pid, nixio.const.SIGTERM)
end
local pid, errno, error = nixio.fork()
if pid == 0 then
nixio.execp(...)
os.exit(127)
elseif pid > 0 then
if timeout then
local starttime = os.time()
while true do
if os.difftime(os.time(), starttime) > timeout then
nixio.kill(pid, nixio.const.SIGTERM)
end
local wpid, status, code = nixio.waitpid(pid, 'nohang')
if wpid then
return wpid and status == 'exited' and code
end
nixio.nanosleep(1)
end
else
local wpid, status, code = nixio.waitpid(pid)
return wpid and status == 'exited' and code
end
else
return pid, errno, error
end
local wpid, status, code = nixio.waitpid(pid, 'nohang')
if wpid then
return wpid and status == 'exited' and code
end
nixio.nanosleep(1)
end
else
local wpid, status, code = nixio.waitpid(pid)
return wpid and status == 'exited' and code
end
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(write, ...)
local inr, inw = nixio.pipe()
local pid = nixio.fork()
local inr, inw = nixio.pipe()
local pid = nixio.fork()
if pid > 0 then
if write then
inr:close()
return pid, inw
else
inw:close()
return pid, inr
end
elseif pid == 0 then
if write then
nixio.dup(inr, nixio.stdin)
else
nixio.dup(inw, nixio.stdout)
end
if pid > 0 then
if write then
inr:close()
return pid, inw
else
inw:close()
return pid, inr
end
elseif pid == 0 then
if write then
nixio.dup(inr, nixio.stdin)
else
nixio.dup(inw, nixio.stdout)
end
inr:close()
inw:close()
inr:close()
inw:close()
nixio.execp(...)
os.exit(127)
end
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 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
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
return true
end
local files = util.consume(fs.dir(dir))
if not files then
return
end
local files = util.consume(fs.dir(dir))
if not files then
return
end
table.sort(files)
table.sort(files)
for _, entry in ipairs(files) do
if is_ok(entry) then
exec(nil, dir .. '/' .. entry)
end
end
for _, entry in ipairs(files) do
if is_ok(entry) then
exec(nil, dir .. '/' .. entry)
end
end
end
-- Seeds Lua's random generator from /dev/urandom
function randomseed()
local f = io.open('/dev/urandom', 'r')
local b1, b2, b3, b4 = f:read(4):byte(1, 4)
f:close()
local f = io.open('/dev/urandom', 'r')
local b1, b2, b3, b4 = f:read(4):byte(1, 4)
f:close()
-- 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))
-- 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))
end
-- Takes a date and time in RFC3339 format and returns a Unix timestamp
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)$')
if not year then
return nil
end
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
return nil
end
local a = math.floor((14 - month)/12)
local y = year - a
local m = month + 12*a - 3
local a = math.floor((14 - month)/12)
local y = year - a
local m = month + 12*a - 3
-- 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 time = hour*3600 + minute*60 + second
local tz = tzh*3600 + tzm*60
-- 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 time = hour*3600 + minute*60 + second
local tz = tzh*3600 + tzm*60
if tzs == '-' then
tz = -tz
end
if tzs == '-' then
tz = -tz
end
return days * 86400 + time - tz
return days * 86400 + time - tz
end

View File

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

View File

@ -11,8 +11,8 @@ local autoupdater_version = require('autoupdater.version')
if not platform_info.get_image_name() then
io.stderr:write("The autoupdater doesn't support this hardware model.\n")
os.exit(1)
io.stderr:write("The autoupdater doesn't support this hardware model.\n")
os.exit(1)
end
@ -38,30 +38,30 @@ local fallback = false
local mirrors = {}
local function parse_args()
local i = 1
while arg[i] do
if arg[i] == '-f' then
force = true
elseif arg[i] == '--fallback' then
fallback = true
elseif arg[i] == '-b' then
i = i+1
local i = 1
while arg[i] do
if arg[i] == '-f' then
force = true
elseif arg[i] == '--fallback' then
fallback = true
elseif arg[i] == '-b' then
i = i+1
if not arg[i] then
io.stderr:write("Error parsing command line: expected branch name\n")
os.exit(1)
end
if not arg[i] then
io.stderr:write("Error parsing command line: expected branch name\n")
os.exit(1)
end
branch_name = arg[i]
elseif arg[i]:sub(0, 1) == '-' then
io.stderr:write("Error parsing command line: unexpected argument '" .. arg[i] .. "'\n")
os.exit(1)
else
table.insert(mirrors, arg[i])
end
branch_name = arg[i]
elseif arg[i]:sub(0, 1) == '-' then
io.stderr:write("Error parsing command line: unexpected argument '" .. arg[i] .. "'\n")
os.exit(1)
else
table.insert(mirrors, arg[i])
end
i = i+1
end
i = i+1
end
end
@ -70,274 +70,274 @@ parse_args()
local branch = uci:get_all('autoupdater', branch_name)
if not branch then
io.stderr:write("Can't find configuration for branch '" .. branch_name .. "'\n")
os.exit(1)
io.stderr:write("Can't find configuration for branch '" .. branch_name .. "'\n")
os.exit(1)
end
if settings.enabled ~= '1' and not force then
io.stderr:write('autoupdater is disabled.\n')
os.exit(0)
io.stderr:write('autoupdater is disabled.\n')
os.exit(0)
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 = {'ecdsaverify', '-n', tostring(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
table.insert(command, '-s')
table.insert(command, sig)
end
end
-- Build command line from sigs and branch.pubkey
for _, sig in ipairs(sigs) do
if sig:match('^' .. string.rep('%x', 128) .. '$') then
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
table.insert(command, '-p')
table.insert(command, key)
end
end
for _, key in ipairs(branch.pubkey) do
if key:match('^' .. string.rep('%x', 64) .. '$') then
table.insert(command, '-p')
table.insert(command, key)
end
end
-- Call ecdsautils
local pid, f = autoupdater_util.popen(true, unpack(command))
-- Call ecdsautils
local pid, f = autoupdater_util.popen(true, unpack(command))
for _, line in ipairs(lines) do
f:write(line)
f:write('\n')
end
for _, line in ipairs(lines) do
f:write(line)
f:write('\n')
end
f:close()
f:close()
local wpid, status, code = nixio.waitpid(pid)
return wpid and status == 'exited' and code == 0
local wpid, status, code = nixio.waitpid(pid)
return wpid and status == 'exited' and code == 0
end
-- 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
local function read_manifest(mirror)
local sep = false
local sep = false
local lines = {}
local sigs = {}
local lines = {}
local sigs = {}
local branch_ok = false
local branch_ok = false
local ret = {}
local ret = {}
-- Remove potential trailing slash
mirror = mirror:gsub('/$', '')
-- Remove potential trailing slash
mirror = mirror:gsub('/$', '')
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 starttime = os.time()
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
-- The upper part is saved to lines, the lower part to sigs
while true do
-- If the manifest download takes more than 5 minutes, we don't really
-- have a chance to download a whole image
local timeout = starttime+300 - os.time()
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")
nixio.kill(pid, nixio.const.SIGTERM)
manifest_loader:close()
return nil
end
-- Read all lines from the manifest
-- The upper part is saved to lines, the lower part to sigs
while true do
-- If the manifest download takes more than 5 minutes, we don't really
-- have a chance to download a whole image
local timeout = starttime+300 - os.time()
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")
nixio.kill(pid, nixio.const.SIGTERM)
manifest_loader:close()
return nil
end
local r = manifest_loader:read(1024)
if not r or r == '' then
break
end
data = data .. r
local r = manifest_loader:read(1024)
if not r or r == '' then
break
end
data = data .. r
while data:match('\n') do
local line, rest = data:match('^([^\n]*)\n(.*)$')
data = rest
while data:match('\n') do
local line, rest = data:match('^([^\n]*)\n(.*)$')
data = rest
if not sep then
if line == '---' then
sep = true
else
table.insert(lines, line)
if not sep then
if line == '---' then
sep = true
else
table.insert(lines, line)
if line == ('BRANCH=' .. branch.name) then
branch_ok = true
end
if line == ('BRANCH=' .. branch.name) then
branch_ok = true
end
local date = line:match('^DATE=(.+)$')
local priority = line:match('^PRIORITY=([%d%.]+)$')
local model, version, checksum, filename = line:match('^([^ ]+) ([^ ]+) ([^ ]+) ([^ ]+)$')
local date = line:match('^DATE=(.+)$')
local priority = line:match('^PRIORITY=([%d%.]+)$')
local model, version, checksum, filename = line:match('^([^ ]+) ([^ ]+) ([^ ]+) ([^ ]+)$')
if date then
ret.date = autoupdater_util.parse_date(date)
elseif priority then
ret.priority = tonumber(priority)
elseif model == platform_info.get_image_name() then
ret.version = version
ret.checksum = checksum
ret.filename = filename
end
end
else
table.insert(sigs, line)
end
end
end
manifest_loader:close()
if date then
ret.date = autoupdater_util.parse_date(date)
elseif priority then
ret.priority = tonumber(priority)
elseif model == platform_info.get_image_name() then
ret.version = version
ret.checksum = checksum
ret.filename = filename
end
end
else
table.insert(sigs, line)
end
end
end
manifest_loader:close()
-- Do some very basic checks before checking the signatures
-- (as the signature verification is computationally expensive)
if not sep then
io.stderr:write('There seems to have gone something wrong downloading the manifest from ' .. mirror .. '\n')
return nil
end
-- Do some very basic checks before checking the signatures
-- (as the signature verification is computationally expensive)
if not sep then
io.stderr:write('There seems to have gone something wrong downloading the manifest from ' .. mirror .. '\n')
return nil
end
if not ret.date or not ret.priority then
io.stderr:write('The manifest downloaded from ' .. mirror .. ' is invalid (DATE or PRIORITY missing)\n')
return nil
end
if not ret.date or not ret.priority then
io.stderr:write('The manifest downloaded from ' .. mirror .. ' is invalid (DATE or PRIORITY missing)\n')
return nil
end
if not branch_ok then
io.stderr:write('Wrong branch. We are on ', branch.name, '.\n')
return nil
end
if not branch_ok then
io.stderr:write('Wrong branch. We are on ', branch.name, '.\n')
return nil
end
if not ret.version then
io.stderr:write('No matching firmware found (model ' .. platform_info.get_image_name() .. ')\n')
return nil
end
if not ret.version then
io.stderr:write('No matching firmware found (model ' .. platform_info.get_image_name() .. ')\n')
return nil
end
if not verify_lines(lines, sigs) then
io.stderr:write('Not enough valid signatures!\n')
return nil
end
if not verify_lines(lines, sigs) then
io.stderr:write('Not enough valid signatures!\n')
return nil
end
return ret
return ret
end
-- Downloads the firmware image from a mirror to a given output file
local function fetch_firmware(mirror, filename, output)
-- 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
io.stderr:write('Error downloading the image from ' .. mirror .. '\n')
return false
end
-- 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
io.stderr:write('Error downloading the image from ' .. mirror .. '\n')
return false
end
return true
return true
end
-- Returns the computed update probability
local function get_probability(date, priority)
local seconds = priority * 86400
local diff = os.difftime(os.time(), date)
local seconds = priority * 86400
local diff = os.difftime(os.time(), date)
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.
-- 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
io.stderr:write('Warning: clock seems to be incorrect.\n')
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.
-- 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
io.stderr:write('Warning: clock seems to be incorrect.\n')
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
return 0
else
-- Will give 1 when priority == 0, and lower probabilities the higher the priority value is
-- (similar to the old static probability system)
return 0.75^priority
end
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
return 0
else
-- Will give 1 when priority == 0, and lower probabilities the higher the priority value is
-- (similar to the old static probability system)
return 0.75^priority
end
elseif fallback then
if diff >= seconds + 86400 then
return 1
else
return 0
end
elseif fallback then
if diff >= seconds + 86400 then
return 1
else
return 0
end
elseif diff >= seconds then
return 1
elseif diff >= seconds then
return 1
else
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
-- (we all love continuously differentiable functions, right?)
return (-2)*x^3 + 3*x^2
end
else
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
-- (we all love continuously differentiable functions, right?)
return (-2)*x^3 + 3*x^2
end
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 upgrade_d_dir = '/usr/lib/autoupdater/upgrade.d'
local download_d_dir = '/usr/lib/autoupdater/download.d'
local abort_d_dir = '/usr/lib/autoupdater/abort.d'
local upgrade_d_dir = '/usr/lib/autoupdater/upgrade.d'
local manifest = read_manifest(mirror)
if not manifest then
return false
end
local manifest = read_manifest(mirror)
if not manifest then
return false
end
if not autoupdater_version.newer_than(manifest.version, old_version) then
io.stderr:write('No new firmware available.\n')
return true
end
if not autoupdater_version.newer_than(manifest.version, old_version) then
io.stderr:write('No new firmware available.\n')
return true
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
io.stderr:write('No autoupdate this time. Use -f to override.\n')
return true
end
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')
return true
end
autoupdater_util.run_dir(download_d_dir)
collectgarbage()
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 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("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
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
autoupdater_util.run_dir(upgrade_d_dir)
autoupdater_util.run_dir(upgrade_d_dir)
io.stderr:write('Upgrading firmware...\n')
local null = nixio.open('/dev/null', 'w+')
if null then
nixio.dup(null, nixio.stdin)
nixio.dup(null, nixio.stderr)
if null:fileno() > 2 then
null:close()
end
end
io.stderr:write('Upgrading firmware...\n')
local null = nixio.open('/dev/null', 'w+')
if null then
nixio.dup(null, nixio.stdin)
nixio.dup(null, nixio.stderr)
if null:fileno() > 2 then
null:close()
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
-- 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)
-- 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
io.write('Failed to call sysupgrade?\n')
os.remove(image)
autoupdater_util.run_dir(abort_d_dir)
os.exit(1)
end
@ -345,24 +345,24 @@ local lockfile = '/var/lock/autoupdater.lock'
local lockfd = nixio.open(lockfile, 'w', 'rw-------')
if not lockfd:lock('tlock') then
io.stderr:write(string.format(
"Unable to lock file %s. Make sure there is no other instance of the autoupdater running.\n",
lockfile, err
))
os.exit(1)
io.stderr:write(string.format(
"Unable to lock file %s. Make sure there is no other instance of the autoupdater running.\n",
lockfile, err
))
os.exit(1)
end
if #mirrors == 0 then
while #branch.mirror > 0 do
table.insert(mirrors, table.remove(branch.mirror, math.random(#branch.mirror)))
end
while #branch.mirror > 0 do
table.insert(mirrors, table.remove(branch.mirror, math.random(#branch.mirror)))
end
end
for k, mirror in ipairs(mirrors) do
if autoupdate(mirror) then
os.exit(0)
end
if autoupdate(mirror) then
os.exit(0)
end
end
io.stderr:write('No usable mirror found.\n')