0
0
mirror of https://github.com/Doodle3D/doodle3d-firmware.git synced 2024-06-02 08:24:33 +02:00

Add support for beta releases to updater command-line tool.

Fix several bugs in updater.
Validate images both on file size and checksum.
This commit is contained in:
Wouter R 2014-02-22 00:09:41 +01:00
parent 96065095ab
commit 264511f809
2 changed files with 81 additions and 29 deletions

View File

@ -368,7 +368,7 @@ local function main()
quit(1) quit(1)
end end
if um.findVersion(newVersion.version, stables) or um.findVersion(newVersion.version, betas) then if um.findVersion(newVersion.version, nil, stables) or um.findVersion(newVersion.version, nil, betas) then
print("Error: firmware version " .. um.formatVersion(newVersion.version) .. " already exists") print("Error: firmware version " .. um.formatVersion(newVersion.version) .. " already exists")
quit(3) quit(3)
end end

View File

@ -280,7 +280,7 @@ end
-- @treturn table|nil A table containing information on what to do, or nil if invalid arguments were specified. -- @treturn table|nil A table containing information on what to do, or nil if invalid arguments were specified.
-- @treturn ?string Descriptive message on error. -- @treturn ?string Descriptive message on error.
local function parseCommandlineArguments(arglist) local function parseCommandlineArguments(arglist)
local result = { verbosity = 0, baseUrl = M.DEFAULT_BASE_URL, action = nil } local result = { verbosity = 0, baseUrl = M.DEFAULT_BASE_URL, includeBetas = false, action = nil }
local nextIsVersion, nextIsUrl = false, false local nextIsVersion, nextIsUrl = false, false
for index,argument in ipairs(arglist) do for index,argument in ipairs(arglist) do
if nextIsVersion then if nextIsVersion then
@ -294,6 +294,7 @@ local function parseCommandlineArguments(arglist)
elseif argument == '-c' then result.useCache = true elseif argument == '-c' then result.useCache = true
elseif argument == '-C' then result.useCache = false elseif argument == '-C' then result.useCache = false
elseif argument == '-u' then nextIsUrl = true elseif argument == '-u' then nextIsUrl = true
elseif argument == '-b' then result.includeBetas = true
elseif argument == '-v' then result.action = 'showCurrentVersion' elseif argument == '-v' then result.action = 'showCurrentVersion'
elseif argument == '-s' then result.action = 'showStatus' elseif argument == '-s' then result.action = 'showStatus'
elseif argument == '-l' then result.action = 'showAvailableVersions' elseif argument == '-l' then result.action = 'showAvailableVersions'
@ -319,16 +320,45 @@ local function parseCommandlineArguments(arglist)
return result return result
end end
--- Determines if the system is OpenWrt or not by checking if `/etc/openwrt_release` exists.
-- @treturn bool True if the OS is OpenWrt.
local function isOpenWrt()
local flag = nil
return function()
if flag == nil then
local relFile = io.open('/etc/openwrt_release', 'r')
flag = not not relFile
if relFile then relFile:close() end
return flag
else
return flag
end
end
end
--- Returns the [MD5](http://en.wikipedia.org/wiki/MD5) hash for a given file. --- Returns the [MD5](http://en.wikipedia.org/wiki/MD5) hash for a given file.
-- --
-- NOTE: this function is not implemented, and a better hash function should probably be chosen anyway. -- NOTE: this function is not implemented, and a better hash function should probably be chosen anyway.
-- @string filepath The path of which to calculate the MD5-sum. -- @string filepath The path of which to calculate the MD5-sum.
-- @treturn nil -- @treturn nil
local function md5sum(filepath) local function md5sum(filepath)
return nil local sfile
-- TODO [osx: md5 -q <file>], [linux: ?]
end
if not isOpenWrt() then
sfile = io.popen('md5 -q "' .. filepath .. '"')
else
sfile = io.popen('md5sum "' .. filepath .. '" 2>/dev/null', 'r')
end
local sum = sfile:read('*all')
sfile:close()
if not sum then return nil,"could not obtain MD5 sum" end
sum = sum:match('[%da-fA-F]+')
return sum
end
@ -343,11 +373,11 @@ local compatlua51 = _VERSION == 'Lua 5.1'
-- @param cmd a shell command -- @param cmd a shell command
-- @return true if successful -- @return true if successful
-- @return actual return code -- @return actual return code
function M.compatexecute (cmd) function M.compatexecute(cmd)
local res1,res2,res3 = os.execute(cmd) local res1,res2,res3 = os.execute(cmd)
if compatlua51 then if compatlua51 then
local cmd, sys = splitExitStatus(res1) local cmd, sys = splitExitStatus(res1)
return (res1 == 0) and true or nil, sys return (res1 == 0) and true,cmd or nil,cmd
else else
return res1, res3 return res1, res3
end end
@ -393,10 +423,11 @@ end
-- If the box has internet access, it will also include the newest version available. -- If the box has internet access, it will also include the newest version available.
-- If an image is currently being downloaded, progress information will also be included. -- If an image is currently being downloaded, progress information will also be included.
-- --
-- @tparam bool[opt] withBetas Consider beta releases when looking for newest version.
-- @treturn bool True if status has been determined fully, false if not. -- @treturn bool True if status has been determined fully, false if not.
-- @treturn table The result table. -- @treturn table The result table.
-- @treturn ?string Descriptive message in case the result table is not complete. -- @treturn ?string Descriptive message in case the result table is not complete.
function M.getStatus(includeBetas) function M.getStatus(withBetas)
if not baseUrl then baseUrl = M.DEFAULT_BASE_URL end if not baseUrl then baseUrl = M.DEFAULT_BASE_URL end
local unknownVersion = { major = 0, minor = 0, patch = 0 } local unknownVersion = { major = 0, minor = 0, patch = 0 }
local result = {} local result = {}
@ -405,7 +436,7 @@ function M.getStatus(includeBetas)
result.stateCode, result.stateText = getState() result.stateCode, result.stateText = getState()
result.stateCode = tonumber(result.stateCode) result.stateCode = tonumber(result.stateCode)
local verTable,msg = M.getAvailableVersions(includeBetas and 'both' or 'stables') local verTable,msg = M.getAvailableVersions(withBetas and 'both' or 'stables')
if not verTable then if not verTable then
D("error: could not obtain available versions (" .. msg .. ")") D("error: could not obtain available versions (" .. msg .. ")")
-- TODO: set an error state in result to signify we probably do not have internet access? -- TODO: set an error state in result to signify we probably do not have internet access?
@ -417,7 +448,7 @@ function M.getStatus(includeBetas)
result.newestReleaseTimestamp = newest and newest.timestamp result.newestReleaseTimestamp = newest and newest.timestamp
-- look up timestamp of current version -- look up timestamp of current version
local cEnt = M.findVersion(result.currentVersion, verTable) local cEnt = M.findVersion(result.currentVersion, nil, verTable)
if cEnt then if cEnt then
result.currentReleaseTimestamp = cEnt.timestamp result.currentReleaseTimestamp = cEnt.timestamp
else else
@ -500,15 +531,17 @@ function M.versionsEqual(versionA, versionB, timestampA, timestampB)
end end
--- Returns information on a version if it can be found in a collection of versions as returned by @{getAvailableVersions}. --- Returns information on a version if it can be found in a collection of versions as returned by @{getAvailableVersions}.
-- FIXME: if no version table is passed in, it will be downloaded but betas will never be included
-- @tparam table version The version to look for. -- @tparam table version The version to look for.
-- @tparam bool[opt] withBetas If verTable is not given, download versions including beta releases
-- @tparam table[opt] verTable A table containing a collection of versions, if not passed in, it will be obtained using @{getAvailableVersions}. -- @tparam table[opt] verTable A table containing a collection of versions, if not passed in, it will be obtained using @{getAvailableVersions}.
-- @param timestamp[opt] Specific timestamp to look for. -- @param timestamp[opt] Specific timestamp to look for.
-- @treturn table|nil Version information table found in the collection, or nil on error or if not found. -- @treturn table|nil Version information table found in the collection, or nil on error or if not found.
-- @treturn string Descriptive message in case of error or if the version could not be found. -- @treturn string Descriptive message in case of error or if the version could not be found.
function M.findVersion(version, verTable, timestamp) function M.findVersion(version, withBetas, verTable, timestamp)
local msg = nil local msg = nil
version = M.parseVersion(version) version = M.parseVersion(version)
if not verTable then verTable,msg = M.getAvailableVersions() end if not verTable then verTable,msg = M.getAvailableVersions(withBetas and 'both' or nil) end
if not verTable then return nil,msg end if not verTable then return nil,msg end
@ -558,12 +591,20 @@ end
-- @string[opt] devType Image device type, see @{constructImageFilename}. -- @string[opt] devType Image device type, see @{constructImageFilename}.
-- @bool[opt] isFactory Image type, see @{constructImageFilename}. -- @bool[opt] isFactory Image type, see @{constructImageFilename}.
-- @treturn bool True if a valid image is present, false otherwise. -- @treturn bool True if a valid image is present, false otherwise.
-- @treturn string|nil Reason for being invalid if first return value is false.
function M.checkValidImage(versionEntry, devType, isFactory) function M.checkValidImage(versionEntry, devType, isFactory)
local filename = M.constructImageFilename(versionEntry.version, devType, isFactory) local filename = M.constructImageFilename(versionEntry.version, devType, isFactory)
--return versionEntry.md5 == md5sum(cachePath .. '/' .. filename)
local size = fileSize(cachePath .. '/' .. filename) local entSize = isFactory and versionEntry.factoryFileSize or versionEntry.sysupgradeFileSize
versionEntry.isValid = versionEntry.sysupgradeFileSize == size local entMd5 = isFactory and versionEntry.factoryMd5 or versionEntry.sysupgradeMD5
return versionEntry.isValid
versionEntry.isValid = entMd5 == md5sum(cachePath .. '/' .. filename)
if not versionEntry.isValid then return false,"incorrect MD5 checksum" end
versionEntry.isValid = entSize == fileSize(cachePath .. '/' .. filename)
if not versionEntry.isValid then return false,"incorrect file size" end
return true
end end
--- Returns the current wifibox version text, extracted from `/etc/wifibox-version`. --- Returns the current wifibox version text, extracted from `/etc/wifibox-version`.
@ -677,6 +718,11 @@ function M.getAvailableVersions(which)
for k,v in pairs(betas) do verTable[k] = v end for k,v in pairs(betas) do verTable[k] = v end
end end
table.sort(verTable, function(a, b)
return M.compareVersions(a.version, b.version, a.timestamp, b.timestamp) < 0
end)
return verTable return verTable
end end
@ -701,24 +747,25 @@ function M.downloadImageFile(versionEntry, devType, isFactory)
if versionEntry.isValid == false then doDownload = true end if versionEntry.isValid == false then doDownload = true end
end end
local rv = 0 local rv1,rv2 = 0,0
if doDownload then if doDownload then
M.setState(M.STATE.DOWNLOADING, "Downloading image (" .. filename .. ")") M.setState(M.STATE.DOWNLOADING, "Downloading image (" .. filename .. ")")
rv = downloadFile(baseUrl .. '/images/' .. filename, cachePath, filename) rv1,rv2 = downloadFile(baseUrl .. '/images/' .. filename, cachePath, filename)
end end
if rv == 0 then if rv1 then
if M.checkValidImage(versionEntry, devType, isFactory) then local valid,msg = M.checkValidImage(versionEntry, devType, isFactory)
if valid then
M.setState(M.STATE.IMAGE_READY, "Image downloaded, ready to install (image name: " .. filename .. ")") M.setState(M.STATE.IMAGE_READY, "Image downloaded, ready to install (image name: " .. filename .. ")")
return true return true
else else
removeFile(cachePath .. '/' .. filename) removeFile(cachePath .. '/' .. filename)
local ws = "Image download failed (invalid image)" local ws = "Image download failed (invalid image: " .. msg .. ")"
M.setState(M.STATE.DOWNLOAD_FAILED, ws) M.setState(M.STATE.DOWNLOAD_FAILED, ws)
return nil,ws return nil,ws
end end
else else
local ws = wgetStatusToString(rv) local ws = wgetStatusToString(rv2)
removeFile(cachePath .. '/' .. filename) removeFile(cachePath .. '/' .. filename)
M.setState(M.STATE.DOWNLOAD_FAILED, "Image download failed (wget error: " .. ws .. ")") M.setState(M.STATE.DOWNLOAD_FAILED, "Image download failed (wget error: " .. ws .. ")")
return nil,ws return nil,ws
@ -735,7 +782,7 @@ end
-- @treturn bool|nil True on success (with the 'exception' as noted above) or nil on error. -- @treturn bool|nil True on success (with the 'exception' as noted above) or nil on error.
-- @treturn ?string|number (optional) Descriptive message or sysupgrade exit status on error. -- @treturn ?string|number (optional) Descriptive message or sysupgrade exit status on error.
function M.flashImageVersion(versionEntry, noRetain, devType, isFactory) function M.flashImageVersion(versionEntry, noRetain, devType, isFactory)
log:info("flashImageVersion") if log then log:info("flashImageVersion") end
local imgName = M.constructImageFilename(versionEntry.version, devType, isFactory) local imgName = M.constructImageFilename(versionEntry.version, devType, isFactory)
local cmd = noRetain and 'sysupgrade -n ' or 'sysupgrade ' local cmd = noRetain and 'sysupgrade -n ' or 'sysupgrade '
cmd = cmd .. cachePath .. '/' .. imgName cmd = cmd .. cachePath .. '/' .. imgName
@ -771,7 +818,7 @@ function M.clear()
D("Removing " .. cachePath .. "/doodle3d-wifibox-*.bin") D("Removing " .. cachePath .. "/doodle3d-wifibox-*.bin")
M.setState(M.STATE.NONE, "") M.setState(M.STATE.NONE, "")
local rv = M.compatexecute('rm -f ' .. cachePath .. '/doodle3d-wifibox-*.bin') local rv = M.compatexecute('rm -f ' .. cachePath .. '/doodle3d-wifibox-*.bin')
return (rv == 0) and true or nil,"could not remove image files" return rv and true or nil,"could not remove image files"
end end
--- Set updater state. --- Set updater state.
@ -817,7 +864,9 @@ local function main()
end end
verbosity = argTable.verbosity verbosity = argTable.verbosity
includeBetas = argTable.includeBetas
if argTable.useCache ~= nil then useCache = argTable.useCache end if argTable.useCache ~= nil then useCache = argTable.useCache end
if argTable.baseUrl ~= nil then baseUrl = argTable.baseUrl end
P(0, "Doodle3D Wifibox firmware updater") P(0, "Doodle3D Wifibox firmware updater")
local cacheCreated,msg = createCacheDirectory() local cacheCreated,msg = createCacheDirectory()
@ -826,6 +875,8 @@ local function main()
os.exit(1) os.exit(1)
end end
P(0, (includeBetas and "Considering" or "Not considering") .. " beta releases.")
if argTable.action == 'showHelp' then if argTable.action == 'showHelp' then
P(1, "\t-h\t\tShow this help message") P(1, "\t-h\t\tShow this help message")
P(1, "\t-q\t\tquiet mode") P(1, "\t-q\t\tquiet mode")
@ -833,6 +884,7 @@ local function main()
P(1, "\t-c\t\tUse cache as much as possible") P(1, "\t-c\t\tUse cache as much as possible")
P(1, "\t-C\t\tDo not use the cache") P(1, "\t-C\t\tDo not use the cache")
P(1, "\t-u <base_url>\tUse specified base URL (default: " .. M.DEFAULT_BASE_URL .. ")") P(1, "\t-u <base_url>\tUse specified base URL (default: " .. M.DEFAULT_BASE_URL .. ")")
P(1, "\t-b\t\tInclude beta releases")
P(1, "\t-v\t\tShow current image version") P(1, "\t-v\t\tShow current image version")
P(1, "\t-s\t\tShow current update status") P(1, "\t-s\t\tShow current update status")
P(1, "\t-l\t\tShow list of available image versions (and which one has been downloaded, if any)") P(1, "\t-l\t\tShow list of available image versions (and which one has been downloaded, if any)")
@ -850,7 +902,7 @@ local function main()
P(1, "version: " .. M.formatVersion(v)) P(1, "version: " .. M.formatVersion(v))
elseif argTable.action == 'showStatus' then elseif argTable.action == 'showStatus' then
local status = M.getStatus() local success,status,msg = M.getStatus(includeBetas)
P(0, "Current update status:") P(0, "Current update status:")
P(1, " currentVersion:\t" .. (M.formatVersion(status.currentVersion) or '?')) P(1, " currentVersion:\t" .. (M.formatVersion(status.currentVersion) or '?'))
P(1, " newestVersion:\t" .. (M.formatVersion(status.newestVersion) or '?')) P(1, " newestVersion:\t" .. (M.formatVersion(status.newestVersion) or '?'))
@ -867,7 +919,7 @@ local function main()
end end
elseif argTable.action == 'showAvailableVersions' then elseif argTable.action == 'showAvailableVersions' then
local verTable,msg = M.getAvailableVersions() local verTable,msg = M.getAvailableVersions(includeBetas and 'both' or 'stables')
if not verTable then if not verTable then
E("error collecting version information (" .. msg .. ")") E("error collecting version information (" .. msg .. ")")
os.exit(2) os.exit(2)
@ -877,7 +929,7 @@ local function main()
for _,ent in ipairs(verTable) do P(1, M.formatVersion(ent.version)) end for _,ent in ipairs(verTable) do P(1, M.formatVersion(ent.version)) end
elseif argTable.action == 'showVersionInfo' then elseif argTable.action == 'showVersionInfo' then
local vEnt,msg = M.findVersion(argTable.version) local vEnt,msg = M.findVersion(argTable.version, includeBetas)
if vEnt then if vEnt then
P(0, "Information on version:") P(0, "Information on version:")
@ -902,7 +954,7 @@ local function main()
end end
elseif argTable.action == 'imageDownload' then elseif argTable.action == 'imageDownload' then
local vEnt,msg = M.findVersion(argTable.version) local vEnt,msg = M.findVersion(argTable.version, includeBetas)
if vEnt == false then if vEnt == false then
P(1, "no such version") P(1, "no such version")
os.exit(4) os.exit(4)
@ -924,7 +976,7 @@ local function main()
elseif argTable.action == 'imageInstall' then elseif argTable.action == 'imageInstall' then
local vEnt, msg = nil, nil local vEnt, msg = nil, nil
vEnt,msg = M.findVersion(argTable.version) vEnt,msg = M.findVersion(argTable.version, includeBetas)
if vEnt == false then if vEnt == false then
P(1, "no such version") P(1, "no such version")
os.exit(4) os.exit(4)