0
0
mirror of https://github.com/Doodle3D/doodle3d-firmware.git synced 2024-07-01 05:31:23 +02:00

Basic implementation of update module + api finished (but not completely debugged yet).

This commit is contained in:
Wouter R 2013-10-21 12:36:54 +02:00
parent b6c983f446
commit b6528e4752
2 changed files with 155 additions and 68 deletions

View File

@ -13,7 +13,8 @@ local M = {
function M.status(request, response) function M.status(request, response)
updater.setLogger(log) updater.setLogger(log)
local status,msg = updater.getStatus(nil, false) updater.setUseCache(false)
local status,msg = updater.getStatus()
if not status then if not status then
response:setFail(msg) response:setFail(msg)
return return
@ -50,7 +51,7 @@ function M.download_POST(request, response)
end end
updater.setLogger(log) updater.setLogger(log)
local rv,msg local vEnt, rv, msg
if argClearImages then if argClearImages then
rv,msg = updater.clear() rv,msg = updater.clear()
@ -61,9 +62,6 @@ function M.download_POST(request, response)
end end
if argClearGcode then if argClearGcode then
-- TODO
--[[ from api_printer.lua:
log:debug("clearing all gcode for " .. printer:getId())
response:addData('gcode_clear',true) response:addData('gcode_clear',true)
local rv,msg = printer:clearGcode() local rv,msg = printer:clearGcode()
@ -71,10 +69,18 @@ if not rv then
response:setError(msg) response:setError(msg)
return return
end end
]]--
end end
rv,msg = updater.downloadImageFile(nil, argVersion) vEnt,msg = updater.findVersion(argVersion)
if vEnt == nil then
response:setFail("error searching version index (" .. msg .. ")")
return
else if vEnt == false then
response:setFail("no such version")
return
end
rv,msg = updater.downloadImageFile(vEnt)
if not rv then if not rv then
response:setFail(msg) response:setFail(msg)
return return
@ -85,10 +91,19 @@ end
-- if successful, this call won't return since the device will flash its memory and reboot -- if successful, this call won't return since the device will flash its memory and reboot
function M.install_POST(request, response) function M.install_POST(request, response)
local argVersion = request:get("version")
updater.setLogger(log) updater.setLogger(log)
-- install
-- cross fingers if not argVersion then
response:setSuccess() response:setError("missing version argument")
return
end
local rv,msg = updater.flashImageVersion(argVersion)
if not rv then response:setFail("installation failed (" .. msg .. ")")
else response:setSuccess()
end
end end
function M.clear_POST(request, response) function M.clear_POST(request, response)

View File

@ -1,10 +1,6 @@
#!/usr/bin/env lua #!/usr/bin/env lua
-- TODO/NOTES: -- TODO/NOTES:
-- M.checkValidImage(verEnt) -> doet exists+fileSize/MD5 check
-- after download: (can use checkValidImage for this)
-- - remove file on fail
-- - check size or md5 and remove file on mismatch [osx: md5 -q <file>]
-- add to status: validImage: none|<version> (can use checkValidImage for this) -- add to status: validImage: none|<version> (can use checkValidImage for this)
-- any more TODO's across this file? -- any more TODO's across this file?
-- max 1 image tegelijk (moet api doen), en rekening houden met printbuffer (printen blokkeren?) -- max 1 image tegelijk (moet api doen), en rekening houden met printbuffer (printen blokkeren?)
@ -12,14 +8,12 @@
-- MAYBE/LATER: -- MAYBE/LATER:
-- add API calls to retrieve a list of all versions with their info (i.e., the result of getAvailableVersions) -- add API calls to retrieve a list of all versions with their info (i.e., the result of getAvailableVersions)
-- wget: add provision (in verbose mode?) to use -v instead of -q and disable output redirection -- wget: add provision (in verbose mode?) to use -v instead of -q and disable output redirection
-- wget: configurable timeout?
-- max cache lifetime for index file?
-- document index file format (Version first, then in any order: Files: sysup; factory, FileSize: sysup; factory, MD5: sysup; factory, ChangelogStart:, ..., ChangelogEnd:) -- document index file format (Version first, then in any order: Files: sysup; factory, FileSize: sysup; factory, MD5: sysup; factory, ChangelogStart:, ..., ChangelogEnd:)
-- remove /etc/wifibox-version on macbook... -- remove /etc/wifibox-version on macbook...
-- copy improved fileSize back to utils (add unit tests!) -- copy improved fileSize back to utils (add unit tests!)
-- create new utils usable by updater as well as api? (remove dependencies on uci and logger etc) -- create new utils usable by updater as well as api? (remove dependencies on uci and logger etc)
-- note: take care not to print any text in module functions, as this breaks http responses -- note: take care not to print any text in module functions, as this breaks http responses
-- change representation of sysupgrade/factory info in versionInfo? (and also in image index?) <- create api call to get all info on all versions? -- change representation of sysupgrade/factory info in versionInfo? (and also in image index?)
local M = {} local M = {}
@ -38,9 +32,10 @@ M.STATE_FILE = 'update-state'
M.WGET_OPTIONS = "-q -t 1 -T 30" M.WGET_OPTIONS = "-q -t 1 -T 30"
--M.WGET_OPTIONS = "-v -t 1 -T 30" --M.WGET_OPTIONS = "-v -t 1 -T 30"
local verbosity = 0 local verbosity = 0 -- set by parseCommandlineArguments()
local log = nil -- wifibox API can use M.setLogger to enable this module to use its logger local log = nil -- wifibox API can use M.setLogger to enable this module to use its logger
local useCache = true -- default, can be overwritten using M.setUseCache()
local baseUrl = M.DEFAULT_BASE_URL -- default, can be overwritten by M.setBaseUrl()
@ -67,11 +62,20 @@ local function E(msg)
end end
end end
-- dontShift is optional -- splits the return status from os.execute (see: http://stackoverflow.com/questions/16158436/how-to-shift-and-mask-bits-from-integer-in-lua)
-- Note: os.execute() return value is shifted one byte to the left, this function local function splitExitStatus(exitStatus)
-- takes that fact into account, unless dontShift is true. local cmdStatus = math.floor(exitStatus / 256)
local function wgetStatusToString(exitStatus, dontShift) local systemStatus = exitStatus - cmdStatus * 256
if not dontShift then exitStatus = exitStatus / 256 end return cmdStatus, systemStatus
end
local function wgetStatusToString(exitStatus)
local wgetStatus,systemStatus = splitExitStatus(exitStatus)
if systemStatus ~= 0 then
return "interrupted:" .. systemStatus
end
-- adapted from man(1) wget on OSX -- adapted from man(1) wget on OSX
local statusTexts = { local statusTexts = {
['0'] = 'Ok', ['0'] = 'Ok',
@ -84,7 +88,7 @@ local function wgetStatusToString(exitStatus, dontShift)
['7'] = 'Protocol error', ['7'] = 'Protocol error',
['8'] = 'Server issued an error response' ['8'] = 'Server issued an error response'
} }
local result = statusTexts[tostring(exitStatus)] local result = statusTexts[tostring(wgetStatus)]
if result then return exitStatus .. ": " .. result if result then return exitStatus .. ": " .. result
else return exitStatus else return exitStatus
@ -176,9 +180,15 @@ local function fileSize(file)
return size return size
end end
-- returns return value of command -- returns return value of command
local function runCommand(command, dryRun) D("about to run: '" .. command .. "'"); return (not dryRun) and os.execute(command) or 0 end local function runCommand(command, dryRun)
D("about to run: '" .. command .. "'")
return (not dryRun) and os.execute(command) or 0
end
local function removeFile(filePath)
return runCommand('rm ' .. filePath)
end
-- returns return value of wget (or nil if saveDir is nil or empty), filename is optional -- returns return value of wget (or nil if saveDir is nil or empty), filename is optional
-- NOTE: leaving out filename will cause issues with files not being overwritten but suffixed with '.1', '.2',etc instead -- NOTE: leaving out filename will cause issues with files not being overwritten but suffixed with '.1', '.2',etc instead
@ -243,17 +253,24 @@ function M.setLogger(logger)
log = logger log = logger
end end
-- baseUrl and useCache are optional function M.setUseCache(use)
function M.getStatus(baseUrl, useCache) useCache = use
end
function M.setBaseUrl(url)
baseUrl = url
end
function M.getStatus()
if not baseUrl then baseUrl = M.DEFAULT_BASE_URL end if not baseUrl then baseUrl = M.DEFAULT_BASE_URL end
local result = {} local result = {}
local verTable,msg = M.getAvailableVersions(baseUrl, useCache) local verTable,msg = M.getAvailableVersions()
if not verTable then return nil,msg end if not verTable then return nil,msg end
local newest = verTable[#verTable] local newest = verTable[#verTable]
result.currentVersion = M.getCurrentVersion() result.currentVersion = M.getCurrentVersion()
result.newestVersion = newest.version result.newestVersion = newest and newest.version or { major = 0, minor = 0, patch = 0 }
result.stateCode, result.stateText = getState() result.stateCode, result.stateText = getState()
result.stateCode = tonumber(result.stateCode) result.stateCode = tonumber(result.stateCode)
@ -296,11 +313,17 @@ function M.compareVersions(versionA, versionB)
return diff > 0 and 1 or (diff < 0 and -1 or 0) return diff > 0 and 1 or (diff < 0 and -1 or 0)
end end
function M.findVersion(verTable, version) -- verTable is optional, getAvailableVersions will be used to obtain it if nil
function M.findVersion(version, verTable)
local msg = nil
if not verTable then verTable,msg = M.getAvailableVersions() end
if not verTable then return nil,msg end
for _,ent in pairs(verTable) do for _,ent in pairs(verTable) do
if M.compareVersions(ent.version, version) == 0 then return ent end if M.compareVersions(ent.version, version) == 0 then return ent end
end end
return nil return nil,"no such version"
end end
-- version may be a table or a string, devtype and isFactory are optional -- version may be a table or a string, devtype and isFactory are optional
@ -311,6 +334,19 @@ function M.constructImageFilename(version, devType, isFactory)
return 'doodle3d-wifibox-' .. M.formatVersion(v) .. '-' .. dt .. '-' .. sf .. '.bin' return 'doodle3d-wifibox-' .. M.formatVersion(v) .. '-' .. dt .. '-' .. sf .. '.bin'
end end
--TODO: move up to locals
local function md5sum(filepath)
-- TODO [osx: md5 -q <file>], [linux: ?]
end
function M.checkValidImage(versionEntry, devType, isFactory)
local filename = M.constructImageFilename(versionEntry.version, devType, isFactory)
--return versionEntry.md5 == md5sum(M.CACHE_PATH .. '/' .. filename)
local size = fileSize(M.CACHE_PATH .. '/' .. filename)
versionEntry.isValid = versionEntry.sysupgradeFileSize == size
return versionEntry.isValid
end
-- returns a plain text version -- returns a plain text version
function M.getCurrentVersionText() function M.getCurrentVersionText()
local res,msg,nr = readFile('/etc/wifibox-version', true) local res,msg,nr = readFile('/etc/wifibox-version', true)
@ -323,9 +359,8 @@ function M.getCurrentVersion()
return vt and M.parseVersion(vt) or nil,msg return vt and M.parseVersion(vt) or nil,msg
end end
-- requires url of image index file; returns an indexed (and sorted) table containing version tables -- returns an indexed (and sorted) table containing version tables
-- baseUrl and useCache are optional function M.getAvailableVersions()
function M.getAvailableVersions(baseUrl, useCache)
if not baseUrl then baseUrl = M.DEFAULT_BASE_URL end if not baseUrl then baseUrl = M.DEFAULT_BASE_URL end
local indexFilename = M.CACHE_PATH .. '/' .. M.IMAGE_INDEX_FILE local indexFilename = M.CACHE_PATH .. '/' .. M.IMAGE_INDEX_FILE
@ -394,17 +429,22 @@ function M.getAvailableVersions(baseUrl, useCache)
return result return result
end end
-- forceDownload, devtype and isFactory are optional -- devtype and isFactory are optional
-- returns true or nil+msg or nil + return value from wget -- returns true or nil+msg or nil + return value from wget
function M.downloadImageFile(baseUrl, version, forceDownload, devType, isFactory) function M.downloadImageFile(versionEntry, devType, isFactory)
if not baseUrl then baseUrl = M.DEFAULT_BASE_URL end if not baseUrl then baseUrl = M.DEFAULT_BASE_URL end
local filename = M.constructImageFilename(version, devType, isFactory) local filename = M.constructImageFilename(versionEntry.version, devType, isFactory)
local doDownload = (type(forceDownload) == 'boolean') and forceDownload or (not exists(M.CACHE_PATH .. '/' .. filename)) local doDownload = not useCache
local ccRv,ccMsg = createCacheDirectory() local ccRv,ccMsg = createCacheDirectory()
if not ccRv then return nil,ccMsg end if not ccRv then return nil,ccMsg end
--TODO: call M.checkValidImage, set doDownload to true if not valid if versionEntry.isValid == false then
doDownload = true
elseif versionEntry.isValid == nil then
M.checkValidImage(versionEntry, devType, isFactory)
if versionEntry.isValid == false then doDownload = true end
end
local rv = 0 local rv = 0
if doDownload then if doDownload then
@ -413,32 +453,47 @@ function M.downloadImageFile(baseUrl, version, forceDownload, devType, isFactory
end end
if rv == 0 then if rv == 0 then
--TODO: check if the downloaded file is complete and matches checksum if M.checkValidImage(versionEntry, devType, isFactory) then
setState(M.STATE.IMAGE_READY, "Image downloaded, ready to install (image name: " .. filename .. ")") setState(M.STATE.IMAGE_READY, "Image downloaded, ready to install (image name: " .. filename .. ")")
return true return true
else
removeFile(M.CACHE_PATH .. '/' .. filename)
local ws = "Image download failed (invalid image)"
setState(M.STATE_DOWNLOAD_FAILED, ws)
return nil,ws
end
else else
local ws = wgetStatusToString(rv) local ws = wgetStatusToString(rv)
setState(M.STATE.DOWNLOAD_FAILED, "Image download failed (" .. ws .. ")") removeFile(M.CACHE_PATH .. '/' .. filename)
setState(M.STATE.DOWNLOAD_FAILED, "Image download failed (wget error: " .. ws .. ")")
return nil,ws return nil,ws
end end
end end
-- this function will not return -- this function will not return if everything goes to plan
-- noRetain, devType and isFactory are optional -- noRetain, devType and isFactory are optional
-- returns true or nil + wget return value -- returns true or nil + wget return value
function M.flashImageVersion(version, noRetain, devType, isFactory) function M.flashImageVersion(versionEntry, noRetain, devType, isFactory)
local imgName = M.constructImageFilename(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 .. M.CACHE_PATH .. '/' .. imgName cmd = cmd .. M.CACHE_PATH .. '/' .. imgName
local ccRv,ccMsg = createCacheDirectory() local ccRv,ccMsg = createCacheDirectory()
if not ccRv then return nil,ccMsg end if not ccRv then return nil,ccMsg end
setState(M.STATE, "Installing new image (" .. imgName .. ")") -- yes this is rather pointless if not M.checkValidImage(versionEntry) then
local rv = runCommand(cmd, true) -- if everything goes to plan, this will not return return nil,"no valid image for requested version present"
end
if rv == 0 then setState(M.STATE.INSTALLED, "Image installed") setState(M.STATE.INSTALLING, "Installing new image (" .. imgName .. ")") -- yes this is rather pointless
else setState(M.STATE.INSTALL_FAILED, "Image installation failed (sysupgrade returned " .. rv .. ")") local rv = runCommand(cmd) -- if everything goes to plan, this will not return
if rv == 0 then
setState(M.STATE.INSTALLED, "Image installed")
else
-- NOTE: if cmdrv == 127, this means the command was not found
local cmdrv,sysrv = splitExitStatus(rv)
setState(M.STATE.INSTALL_FAILED, "Image installation failed (sysupgrade returned " .. cmdrv .. ", execution status: " .. sysrv .. ")")
end end
return (rv == 0) and true or nil,rv return (rv == 0) and true or nil,rv
@ -464,7 +519,6 @@ end
---------- ----------
local function main() local function main()
local useCache = true
local argTable,msg = parseCommandlineArguments(arg) local argTable,msg = parseCommandlineArguments(arg)
if not argTable then if not argTable then
@ -506,7 +560,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(argTable.baseUrl, useCache) local status = M.getStatus()
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 '?'))
@ -523,7 +577,7 @@ local function main()
end end
elseif argTable.action == 'showAvailableVersions' then elseif argTable.action == 'showAvailableVersions' then
local verTable,msg = M.getAvailableVersions(argTable.baseUrl, useCache) local verTable,msg = M.getAvailableVersions()
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)
@ -533,13 +587,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 verTable,msg = M.getAvailableVersions(argTable.baseUrl, useCache) local vEnt,msg = M.findVersion(argTable.version)
if not verTable then
E("error parsing image index file (" .. msg .. ")")
os.exit(2)
end
local vEnt,msg = M.findVersion(verTable, argTable.version)
if vEnt then if vEnt then
P(0, "Information on version:") P(0, "Information on version:")
@ -555,16 +603,29 @@ local function main()
else else
P(1, " changelog:\t\t-") P(1, " changelog:\t\t-")
end end
else elseif vEnt == false then
P(1, "not found") P(1, "no such version")
os.exit(4)
elseif vEnt == nil then
E("error searching version index (" .. msg .. ")")
os.exit(2)
end end
elseif argTable.action == 'imageDownload' then elseif argTable.action == 'imageDownload' then
--TODO: first check if version exists local vEnt,msg = M.findVersion(argTable.version)
local rv,msg = M.downloadImageFile(argTable.baseUrl, argTable.version, not useCache) --TEMP if vEnt == false then
P(1, "no such version")
os.exit(4)
elseif vEnt == nil then
E("error searching version index (" .. msg .. ")")
os.exit(2)
end
local rv,msg = M.downloadImageFile(vEnt)
if not rv then E("could not download file (" .. msg .. ")") if not rv then E("could not download file (" .. msg .. ")")
else P(1, "success") else P(1, "success")
end end
elseif argTable.action == 'clear' then elseif argTable.action == 'clear' then
local rv,msg = M.clear() local rv,msg = M.clear()
if not rv then P(1, "error (" .. msg .. ")") if not rv then P(1, "error (" .. msg .. ")")
@ -572,8 +633,19 @@ local function main()
end end
elseif argTable.action == 'imageInstall' then elseif argTable.action == 'imageInstall' then
local rv = M.flashImageVersion(argTable.version) local vEnt, msg = nil, nil
E("error: flash function returned, the device should have been flashed and rebooted instead") vEnt,msg = M.findVersion(argTable.version)
if vEnt == false then
P(1, "no such version")
os.exit(4)
elseif vEnt == nil then
E("error searching version index (" .. msg .. ")")
os.exit(2)
end
local rv
rv,msg = M.flashImageVersion(vEnt)
E("error: failed to flash image to device (" .. msg .. ")")
os.exit(3) os.exit(3)
else else