diff options
author | reillyg@chromium.org <reillyg@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-07-25 08:35:56 +0000 |
---|---|---|
committer | reillyg@chromium.org <reillyg@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-07-25 08:35:56 +0000 |
commit | ac37a8cc221f9b68682e0c872fd35a8ce2177837 (patch) | |
tree | fb14b611df80a1b8d72eb085dcac2c476657afe6 /tools/usb_gadget | |
parent | a9987cae11bccd09827d0b3cb91bd4f70091d59d (diff) | |
download | chromium_src-ac37a8cc221f9b68682e0c872fd35a8ce2177837.zip chromium_src-ac37a8cc221f9b68682e0c872fd35a8ce2177837.tar.gz chromium_src-ac37a8cc221f9b68682e0c872fd35a8ce2177837.tar.bz2 |
[usb_gadget p09] Software updates over HTTP.
Add a /update resource which accepts a new software package (assuming
the hash is correct) and performs an in-place upgrade. To avoid leaving
the device in an unusable state the old version remains running until
the new version starts to accept HTTP connections. At this point the
Tornado I/O loop is running correctly and even if USB functionality
is broken the device should accept another software update to fix it.
BUG=396682
R=rockot@chromium.org,rpaquay@chromium.org,kalman@chromium.org
Review URL: https://codereview.chromium.org/412243002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@285531 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'tools/usb_gadget')
-rw-r--r-- | tools/usb_gadget/__main__.py | 4 | ||||
-rwxr-xr-x | tools/usb_gadget/package.py | 29 | ||||
-rw-r--r-- | tools/usb_gadget/server.py | 72 |
3 files changed, 104 insertions, 1 deletions
diff --git a/tools/usb_gadget/__main__.py b/tools/usb_gadget/__main__.py index 20998c5..4ca6ad9 100644 --- a/tools/usb_gadget/__main__.py +++ b/tools/usb_gadget/__main__.py @@ -25,6 +25,9 @@ def ParseArgs(): parser.add_argument( '--hardware', default='beaglebone-black', help='Hardware configuration.') + parser.add_argument( + '--start-claimed', + help='Start with the device claimed by this client.') return parser.parse_args() @@ -34,6 +37,7 @@ def main(): server.interface = args.interface server.port = args.port server.hardware = args.hardware + server.claimed_by = args.start_claimed addrs = netifaces.ifaddresses(server.interface) ip_address = addrs[netifaces.AF_INET][0]['addr'] diff --git a/tools/usb_gadget/package.py b/tools/usb_gadget/package.py index d1a2f2d..069bb14 100755 --- a/tools/usb_gadget/package.py +++ b/tools/usb_gadget/package.py @@ -3,13 +3,14 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -"""Utility to package the USB gadget framework. +"""Utility to package and upload the USB gadget framework. """ import argparse import hashlib import os import StringIO +import urllib2 import zipfile @@ -37,6 +38,27 @@ def MakeZip(directory=None, files=None): return content, md5 +def EncodeBody(filename, buf): + return '\r\n'.join([ + '--foo', + 'Content-Disposition: form-data; name="file"; filename="{}"' + .format(filename), + 'Content-Type: application/octet-stream', + '', + buf, + '--foo--', + '' + ]) + + +def UploadZip(content, md5, host): + filename = 'usb_gadget-{}.zip'.format(md5) + req = urllib2.Request(url='http://{}/update'.format(host), + data=EncodeBody(filename, content)) + req.add_header('Content-Type', 'multipart/form-data; boundary=foo') + urllib2.urlopen(req) + + def main(): parser = argparse.ArgumentParser( description='Package (and upload) the USB gadget framework.') @@ -50,6 +72,9 @@ def main(): '--hash-file', type=str, metavar='FILE', help='save package hash as FILE') parser.add_argument( + '--upload', type=str, metavar='HOST[:PORT]', + help='upload package to target system') + parser.add_argument( 'files', metavar='FILE', type=str, nargs='*', help='source files') @@ -62,6 +87,8 @@ def main(): if args.hash_file: with open(args.hash_file, 'w') as hash_file: hash_file.write(md5) + if args.upload: + UploadZip(content, md5, args.upload) if __name__ == '__main__': diff --git a/tools/usb_gadget/server.py b/tools/usb_gadget/server.py index 9f23726..e5aa8b8 100644 --- a/tools/usb_gadget/server.py +++ b/tools/usb_gadget/server.py @@ -5,10 +5,16 @@ """WSGI application to manage a USB gadget. """ +import datetime +import hashlib import re +import subprocess import sys +import time +import urllib2 from tornado import httpserver +from tornado import ioloop from tornado import web import default_gadget @@ -20,6 +26,9 @@ chip = None claimed_by = None default = default_gadget.DefaultGadget() gadget = None +hardware = None +interface = None +port = None def SwitchGadget(new_gadget): @@ -45,6 +54,68 @@ class VersionHandler(web.RequestHandler): self.write(version) +class UpdateHandler(web.RequestHandler): + + def post(self): + fileinfo = self.request.files['file'][0] + + match = VERSION_PATTERN.match(fileinfo['filename']) + if match is None: + self.write('Filename must contain MD5 hash.') + self.set_status(400) + return + + content = fileinfo['body'] + md5sum = hashlib.md5(content).hexdigest() + if md5sum != match.group(1): + self.write('File hash does not match.') + self.set_status(400) + return + + filename = 'usb_gadget-{}.zip'.format(md5sum) + with open(filename, 'wb') as f: + f.write(content) + + args = ['/usr/bin/python', filename, + '--interface', interface, + '--port', str(port), + '--hardware', hardware] + if claimed_by is not None: + args.extend(['--start-claimed', claimed_by]) + + print 'Reloading with version {}...'.format(md5sum) + + global http_server + if chip.IsConfigured(): + chip.Destroy() + http_server.stop() + + child = subprocess.Popen(args, close_fds=True) + + while True: + child.poll() + if child.returncode is not None: + self.write('New package exited with error {}.' + .format(child.returncode)) + self.set_status(500) + + http_server = httpserver.HTTPServer(app) + http_server.listen(port) + SwitchGadget(gadget) + return + + try: + f = urllib2.urlopen('http://{}/version'.format(address)) + if f.getcode() == 200: + # Update complete, wait 1 second to make sure buffers are flushed. + io_loop = ioloop.IOLoop.instance() + io_loop.add_timeout(datetime.timedelta(seconds=1), io_loop.stop) + return + except urllib2.URLError: + pass + time.sleep(0.1) + + class ClaimHandler(web.RequestHandler): def post(self): @@ -88,6 +159,7 @@ class ReconnectHandler(web.RequestHandler): app = web.Application([ (r'/version', VersionHandler), + (r'/update', UpdateHandler), (r'/claim', ClaimHandler), (r'/unclaim', UnclaimHandler), (r'/unconfigure', UnconfigureHandler), |