Andrew Lassalle | 165843c | 2019-11-05 13:30:34 -0800 | [diff] [blame^] | 1 | #!/usr/bin/env python |
Alex Deymo | 6751bbe | 2017-03-21 11:20:02 -0700 | [diff] [blame] | 2 | # |
| 3 | # Copyright (C) 2017 The Android Open Source Project |
| 4 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | # you may not use this file except in compliance with the License. |
| 7 | # You may obtain a copy of the License at |
| 8 | # |
| 9 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | # |
| 11 | # Unless required by applicable law or agreed to in writing, software |
| 12 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. |
| 16 | # |
| 17 | |
| 18 | """Send an A/B update to an Android device over adb.""" |
| 19 | |
Andrew Lassalle | 165843c | 2019-11-05 13:30:34 -0800 | [diff] [blame^] | 20 | from __future__ import absolute_import |
| 21 | |
Alex Deymo | 6751bbe | 2017-03-21 11:20:02 -0700 | [diff] [blame] | 22 | import argparse |
Sen Jiang | 3b15b59 | 2017-09-26 18:21:04 -0700 | [diff] [blame] | 23 | import hashlib |
Alex Deymo | 6751bbe | 2017-03-21 11:20:02 -0700 | [diff] [blame] | 24 | import logging |
| 25 | import os |
| 26 | import socket |
| 27 | import subprocess |
| 28 | import sys |
| 29 | import threading |
Sen Jiang | 144f9f8 | 2017-09-26 15:49:45 -0700 | [diff] [blame] | 30 | import xml.etree.ElementTree |
Alex Deymo | 6751bbe | 2017-03-21 11:20:02 -0700 | [diff] [blame] | 31 | import zipfile |
| 32 | |
Andrew Lassalle | 165843c | 2019-11-05 13:30:34 -0800 | [diff] [blame^] | 33 | from six.moves import BaseHTTPServer |
| 34 | |
Sen Jiang | a1784b7 | 2017-08-09 17:42:36 -0700 | [diff] [blame] | 35 | import update_payload.payload |
| 36 | |
Alex Deymo | 6751bbe | 2017-03-21 11:20:02 -0700 | [diff] [blame] | 37 | |
| 38 | # The path used to store the OTA package when applying the package from a file. |
| 39 | OTA_PACKAGE_PATH = '/data/ota_package' |
| 40 | |
Sen Jiang | a1784b7 | 2017-08-09 17:42:36 -0700 | [diff] [blame] | 41 | # The path to the payload public key on the device. |
| 42 | PAYLOAD_KEY_PATH = '/etc/update_engine/update-payload-key.pub.pem' |
| 43 | |
| 44 | # The port on the device that update_engine should connect to. |
| 45 | DEVICE_PORT = 1234 |
Alex Deymo | 6751bbe | 2017-03-21 11:20:02 -0700 | [diff] [blame] | 46 | |
Andrew Lassalle | 165843c | 2019-11-05 13:30:34 -0800 | [diff] [blame^] | 47 | |
Alex Deymo | 6751bbe | 2017-03-21 11:20:02 -0700 | [diff] [blame] | 48 | def CopyFileObjLength(fsrc, fdst, buffer_size=128 * 1024, copy_length=None): |
| 49 | """Copy from a file object to another. |
| 50 | |
| 51 | This function is similar to shutil.copyfileobj except that it allows to copy |
| 52 | less than the full source file. |
| 53 | |
| 54 | Args: |
| 55 | fsrc: source file object where to read from. |
| 56 | fdst: destination file object where to write to. |
| 57 | buffer_size: size of the copy buffer in memory. |
| 58 | copy_length: maximum number of bytes to copy, or None to copy everything. |
| 59 | |
| 60 | Returns: |
| 61 | the number of bytes copied. |
| 62 | """ |
| 63 | copied = 0 |
| 64 | while True: |
| 65 | chunk_size = buffer_size |
| 66 | if copy_length is not None: |
| 67 | chunk_size = min(chunk_size, copy_length - copied) |
| 68 | if not chunk_size: |
| 69 | break |
| 70 | buf = fsrc.read(chunk_size) |
| 71 | if not buf: |
| 72 | break |
| 73 | fdst.write(buf) |
| 74 | copied += len(buf) |
| 75 | return copied |
| 76 | |
| 77 | |
| 78 | class AndroidOTAPackage(object): |
| 79 | """Android update payload using the .zip format. |
| 80 | |
| 81 | Android OTA packages traditionally used a .zip file to store the payload. When |
| 82 | applying A/B updates over the network, a payload binary is stored RAW inside |
| 83 | this .zip file which is used by update_engine to apply the payload. To do |
| 84 | this, an offset and size inside the .zip file are provided. |
| 85 | """ |
| 86 | |
| 87 | # Android OTA package file paths. |
| 88 | OTA_PAYLOAD_BIN = 'payload.bin' |
| 89 | OTA_PAYLOAD_PROPERTIES_TXT = 'payload_properties.txt' |
| 90 | |
| 91 | def __init__(self, otafilename): |
| 92 | self.otafilename = otafilename |
| 93 | |
| 94 | otazip = zipfile.ZipFile(otafilename, 'r') |
| 95 | payload_info = otazip.getinfo(self.OTA_PAYLOAD_BIN) |
Shashikant Baviskar | b1a9e08 | 2018-04-12 12:37:21 +0900 | [diff] [blame] | 96 | self.offset = payload_info.header_offset |
| 97 | self.offset += zipfile.sizeFileHeader |
| 98 | self.offset += len(payload_info.extra) + len(payload_info.filename) |
Alex Deymo | 6751bbe | 2017-03-21 11:20:02 -0700 | [diff] [blame] | 99 | self.size = payload_info.file_size |
| 100 | self.properties = otazip.read(self.OTA_PAYLOAD_PROPERTIES_TXT) |
| 101 | |
| 102 | |
| 103 | class UpdateHandler(BaseHTTPServer.BaseHTTPRequestHandler): |
| 104 | """A HTTPServer that supports single-range requests. |
| 105 | |
| 106 | Attributes: |
| 107 | serving_payload: path to the only payload file we are serving. |
Sen Jiang | 3b15b59 | 2017-09-26 18:21:04 -0700 | [diff] [blame] | 108 | serving_range: the start offset and size tuple of the payload. |
Alex Deymo | 6751bbe | 2017-03-21 11:20:02 -0700 | [diff] [blame] | 109 | """ |
| 110 | |
| 111 | @staticmethod |
Sen Jiang | 1048559 | 2017-08-15 18:20:24 -0700 | [diff] [blame] | 112 | def _parse_range(range_str, file_size): |
Alex Deymo | 6751bbe | 2017-03-21 11:20:02 -0700 | [diff] [blame] | 113 | """Parse an HTTP range string. |
| 114 | |
| 115 | Args: |
| 116 | range_str: HTTP Range header in the request, not including "Header:". |
| 117 | file_size: total size of the serving file. |
| 118 | |
| 119 | Returns: |
| 120 | A tuple (start_range, end_range) with the range of bytes requested. |
| 121 | """ |
| 122 | start_range = 0 |
| 123 | end_range = file_size |
| 124 | |
| 125 | if range_str: |
| 126 | range_str = range_str.split('=', 1)[1] |
| 127 | s, e = range_str.split('-', 1) |
| 128 | if s: |
| 129 | start_range = int(s) |
| 130 | if e: |
| 131 | end_range = int(e) + 1 |
| 132 | elif e: |
| 133 | if int(e) < file_size: |
| 134 | start_range = file_size - int(e) |
| 135 | return start_range, end_range |
| 136 | |
Alex Deymo | 6751bbe | 2017-03-21 11:20:02 -0700 | [diff] [blame] | 137 | def do_GET(self): # pylint: disable=invalid-name |
| 138 | """Reply with the requested payload file.""" |
| 139 | if self.path != '/payload': |
| 140 | self.send_error(404, 'Unknown request') |
| 141 | return |
| 142 | |
| 143 | if not self.serving_payload: |
| 144 | self.send_error(500, 'No serving payload set') |
| 145 | return |
| 146 | |
| 147 | try: |
| 148 | f = open(self.serving_payload, 'rb') |
| 149 | except IOError: |
| 150 | self.send_error(404, 'File not found') |
| 151 | return |
| 152 | # Handle the range request. |
| 153 | if 'Range' in self.headers: |
| 154 | self.send_response(206) |
| 155 | else: |
| 156 | self.send_response(200) |
| 157 | |
Sen Jiang | 3b15b59 | 2017-09-26 18:21:04 -0700 | [diff] [blame] | 158 | serving_start, serving_size = self.serving_range |
Sen Jiang | 1048559 | 2017-08-15 18:20:24 -0700 | [diff] [blame] | 159 | start_range, end_range = self._parse_range(self.headers.get('range'), |
Sen Jiang | 3b15b59 | 2017-09-26 18:21:04 -0700 | [diff] [blame] | 160 | serving_size) |
Alex Deymo | 6751bbe | 2017-03-21 11:20:02 -0700 | [diff] [blame] | 161 | logging.info('Serving request for %s from %s [%d, %d) length: %d', |
Sen Jiang | 3b15b59 | 2017-09-26 18:21:04 -0700 | [diff] [blame] | 162 | self.path, self.serving_payload, serving_start + start_range, |
| 163 | serving_start + end_range, end_range - start_range) |
Alex Deymo | 6751bbe | 2017-03-21 11:20:02 -0700 | [diff] [blame] | 164 | |
| 165 | self.send_header('Accept-Ranges', 'bytes') |
| 166 | self.send_header('Content-Range', |
| 167 | 'bytes ' + str(start_range) + '-' + str(end_range - 1) + |
| 168 | '/' + str(end_range - start_range)) |
| 169 | self.send_header('Content-Length', end_range - start_range) |
| 170 | |
Sen Jiang | 3b15b59 | 2017-09-26 18:21:04 -0700 | [diff] [blame] | 171 | stat = os.fstat(f.fileno()) |
Alex Deymo | 6751bbe | 2017-03-21 11:20:02 -0700 | [diff] [blame] | 172 | self.send_header('Last-Modified', self.date_time_string(stat.st_mtime)) |
| 173 | self.send_header('Content-type', 'application/octet-stream') |
| 174 | self.end_headers() |
| 175 | |
Sen Jiang | 3b15b59 | 2017-09-26 18:21:04 -0700 | [diff] [blame] | 176 | f.seek(serving_start + start_range) |
Alex Deymo | 6751bbe | 2017-03-21 11:20:02 -0700 | [diff] [blame] | 177 | CopyFileObjLength(f, self.wfile, copy_length=end_range - start_range) |
| 178 | |
Sen Jiang | a1784b7 | 2017-08-09 17:42:36 -0700 | [diff] [blame] | 179 | def do_POST(self): # pylint: disable=invalid-name |
| 180 | """Reply with the omaha response xml.""" |
| 181 | if self.path != '/update': |
| 182 | self.send_error(404, 'Unknown request') |
| 183 | return |
| 184 | |
| 185 | if not self.serving_payload: |
| 186 | self.send_error(500, 'No serving payload set') |
| 187 | return |
| 188 | |
| 189 | try: |
| 190 | f = open(self.serving_payload, 'rb') |
| 191 | except IOError: |
| 192 | self.send_error(404, 'File not found') |
| 193 | return |
| 194 | |
Sen Jiang | 144f9f8 | 2017-09-26 15:49:45 -0700 | [diff] [blame] | 195 | content_length = int(self.headers.getheader('Content-Length')) |
| 196 | request_xml = self.rfile.read(content_length) |
| 197 | xml_root = xml.etree.ElementTree.fromstring(request_xml) |
| 198 | appid = None |
| 199 | for app in xml_root.iter('app'): |
| 200 | if 'appid' in app.attrib: |
| 201 | appid = app.attrib['appid'] |
| 202 | break |
| 203 | if not appid: |
| 204 | self.send_error(400, 'No appid in Omaha request') |
| 205 | return |
| 206 | |
Sen Jiang | a1784b7 | 2017-08-09 17:42:36 -0700 | [diff] [blame] | 207 | self.send_response(200) |
| 208 | self.send_header("Content-type", "text/xml") |
| 209 | self.end_headers() |
| 210 | |
Sen Jiang | 3b15b59 | 2017-09-26 18:21:04 -0700 | [diff] [blame] | 211 | serving_start, serving_size = self.serving_range |
| 212 | sha256 = hashlib.sha256() |
| 213 | f.seek(serving_start) |
| 214 | bytes_to_hash = serving_size |
| 215 | while bytes_to_hash: |
| 216 | buf = f.read(min(bytes_to_hash, 1024 * 1024)) |
| 217 | if not buf: |
| 218 | self.send_error(500, 'Payload too small') |
| 219 | return |
| 220 | sha256.update(buf) |
| 221 | bytes_to_hash -= len(buf) |
| 222 | |
| 223 | payload = update_payload.Payload(f, payload_file_offset=serving_start) |
Sen Jiang | a1784b7 | 2017-08-09 17:42:36 -0700 | [diff] [blame] | 224 | payload.Init() |
| 225 | |
Sen Jiang | 144f9f8 | 2017-09-26 15:49:45 -0700 | [diff] [blame] | 226 | response_xml = ''' |
Sen Jiang | a1784b7 | 2017-08-09 17:42:36 -0700 | [diff] [blame] | 227 | <?xml version="1.0" encoding="UTF-8"?> |
| 228 | <response protocol="3.0"> |
Sen Jiang | 144f9f8 | 2017-09-26 15:49:45 -0700 | [diff] [blame] | 229 | <app appid="{appid}"> |
Sen Jiang | a1784b7 | 2017-08-09 17:42:36 -0700 | [diff] [blame] | 230 | <updatecheck status="ok"> |
| 231 | <urls> |
Sen Jiang | 144f9f8 | 2017-09-26 15:49:45 -0700 | [diff] [blame] | 232 | <url codebase="http://127.0.0.1:{port}/"/> |
Sen Jiang | a1784b7 | 2017-08-09 17:42:36 -0700 | [diff] [blame] | 233 | </urls> |
| 234 | <manifest version="0.0.0.1"> |
| 235 | <actions> |
| 236 | <action event="install" run="payload"/> |
Sen Jiang | 144f9f8 | 2017-09-26 15:49:45 -0700 | [diff] [blame] | 237 | <action event="postinstall" MetadataSize="{metadata_size}"/> |
Sen Jiang | a1784b7 | 2017-08-09 17:42:36 -0700 | [diff] [blame] | 238 | </actions> |
| 239 | <packages> |
Sen Jiang | 144f9f8 | 2017-09-26 15:49:45 -0700 | [diff] [blame] | 240 | <package hash_sha256="{payload_hash}" name="payload" size="{payload_size}"/> |
Sen Jiang | a1784b7 | 2017-08-09 17:42:36 -0700 | [diff] [blame] | 241 | </packages> |
| 242 | </manifest> |
| 243 | </updatecheck> |
| 244 | </app> |
| 245 | </response> |
Sen Jiang | 144f9f8 | 2017-09-26 15:49:45 -0700 | [diff] [blame] | 246 | '''.format(appid=appid, port=DEVICE_PORT, |
Sen Jiang | 3b15b59 | 2017-09-26 18:21:04 -0700 | [diff] [blame] | 247 | metadata_size=payload.metadata_size, |
| 248 | payload_hash=sha256.hexdigest(), |
| 249 | payload_size=serving_size) |
Sen Jiang | 144f9f8 | 2017-09-26 15:49:45 -0700 | [diff] [blame] | 250 | self.wfile.write(response_xml.strip()) |
Sen Jiang | a1784b7 | 2017-08-09 17:42:36 -0700 | [diff] [blame] | 251 | return |
| 252 | |
| 253 | |
Alex Deymo | 6751bbe | 2017-03-21 11:20:02 -0700 | [diff] [blame] | 254 | class ServerThread(threading.Thread): |
| 255 | """A thread for serving HTTP requests.""" |
| 256 | |
Sen Jiang | 3b15b59 | 2017-09-26 18:21:04 -0700 | [diff] [blame] | 257 | def __init__(self, ota_filename, serving_range): |
Alex Deymo | 6751bbe | 2017-03-21 11:20:02 -0700 | [diff] [blame] | 258 | threading.Thread.__init__(self) |
Sen Jiang | 3b15b59 | 2017-09-26 18:21:04 -0700 | [diff] [blame] | 259 | # serving_payload and serving_range are class attributes and the |
| 260 | # UpdateHandler class is instantiated with every request. |
Alex Deymo | 6751bbe | 2017-03-21 11:20:02 -0700 | [diff] [blame] | 261 | UpdateHandler.serving_payload = ota_filename |
Sen Jiang | 3b15b59 | 2017-09-26 18:21:04 -0700 | [diff] [blame] | 262 | UpdateHandler.serving_range = serving_range |
Alex Deymo | 6751bbe | 2017-03-21 11:20:02 -0700 | [diff] [blame] | 263 | self._httpd = BaseHTTPServer.HTTPServer(('127.0.0.1', 0), UpdateHandler) |
| 264 | self.port = self._httpd.server_port |
| 265 | |
| 266 | def run(self): |
| 267 | try: |
| 268 | self._httpd.serve_forever() |
| 269 | except (KeyboardInterrupt, socket.error): |
| 270 | pass |
| 271 | logging.info('Server Terminated') |
| 272 | |
| 273 | def StopServer(self): |
| 274 | self._httpd.socket.close() |
| 275 | |
| 276 | |
Sen Jiang | 3b15b59 | 2017-09-26 18:21:04 -0700 | [diff] [blame] | 277 | def StartServer(ota_filename, serving_range): |
| 278 | t = ServerThread(ota_filename, serving_range) |
Alex Deymo | 6751bbe | 2017-03-21 11:20:02 -0700 | [diff] [blame] | 279 | t.start() |
| 280 | return t |
| 281 | |
| 282 | |
Sen Jiang | 6fbfd7d | 2017-10-31 16:16:56 -0700 | [diff] [blame] | 283 | def AndroidUpdateCommand(ota_filename, payload_url, extra_headers): |
Alex Deymo | 6751bbe | 2017-03-21 11:20:02 -0700 | [diff] [blame] | 284 | """Return the command to run to start the update in the Android device.""" |
| 285 | ota = AndroidOTAPackage(ota_filename) |
| 286 | headers = ota.properties |
| 287 | headers += 'USER_AGENT=Dalvik (something, something)\n' |
Alex Deymo | 6751bbe | 2017-03-21 11:20:02 -0700 | [diff] [blame] | 288 | headers += 'NETWORK_ID=0\n' |
Sen Jiang | 6fbfd7d | 2017-10-31 16:16:56 -0700 | [diff] [blame] | 289 | headers += extra_headers |
Alex Deymo | 6751bbe | 2017-03-21 11:20:02 -0700 | [diff] [blame] | 290 | |
| 291 | return ['update_engine_client', '--update', '--follow', |
| 292 | '--payload=%s' % payload_url, '--offset=%d' % ota.offset, |
| 293 | '--size=%d' % ota.size, '--headers="%s"' % headers] |
| 294 | |
| 295 | |
Sen Jiang | a1784b7 | 2017-08-09 17:42:36 -0700 | [diff] [blame] | 296 | def OmahaUpdateCommand(omaha_url): |
| 297 | """Return the command to run to start the update in a device using Omaha.""" |
| 298 | return ['update_engine_client', '--update', '--follow', |
| 299 | '--omaha_url=%s' % omaha_url] |
| 300 | |
| 301 | |
Alex Deymo | 6751bbe | 2017-03-21 11:20:02 -0700 | [diff] [blame] | 302 | class AdbHost(object): |
| 303 | """Represents a device connected via ADB.""" |
| 304 | |
| 305 | def __init__(self, device_serial=None): |
| 306 | """Construct an instance. |
| 307 | |
| 308 | Args: |
| 309 | device_serial: options string serial number of attached device. |
| 310 | """ |
| 311 | self._device_serial = device_serial |
| 312 | self._command_prefix = ['adb'] |
| 313 | if self._device_serial: |
| 314 | self._command_prefix += ['-s', self._device_serial] |
| 315 | |
| 316 | def adb(self, command): |
| 317 | """Run an ADB command like "adb push". |
| 318 | |
| 319 | Args: |
| 320 | command: list of strings containing command and arguments to run |
| 321 | |
| 322 | Returns: |
| 323 | the program's return code. |
| 324 | |
| 325 | Raises: |
| 326 | subprocess.CalledProcessError on command exit != 0. |
| 327 | """ |
| 328 | command = self._command_prefix + command |
| 329 | logging.info('Running: %s', ' '.join(str(x) for x in command)) |
| 330 | p = subprocess.Popen(command, universal_newlines=True) |
| 331 | p.wait() |
| 332 | return p.returncode |
| 333 | |
Sen Jiang | a1784b7 | 2017-08-09 17:42:36 -0700 | [diff] [blame] | 334 | def adb_output(self, command): |
| 335 | """Run an ADB command like "adb push" and return the output. |
| 336 | |
| 337 | Args: |
| 338 | command: list of strings containing command and arguments to run |
| 339 | |
| 340 | Returns: |
| 341 | the program's output as a string. |
| 342 | |
| 343 | Raises: |
| 344 | subprocess.CalledProcessError on command exit != 0. |
| 345 | """ |
| 346 | command = self._command_prefix + command |
| 347 | logging.info('Running: %s', ' '.join(str(x) for x in command)) |
| 348 | return subprocess.check_output(command, universal_newlines=True) |
| 349 | |
Alex Deymo | 6751bbe | 2017-03-21 11:20:02 -0700 | [diff] [blame] | 350 | |
| 351 | def main(): |
| 352 | parser = argparse.ArgumentParser(description='Android A/B OTA helper.') |
Sen Jiang | 3b15b59 | 2017-09-26 18:21:04 -0700 | [diff] [blame] | 353 | parser.add_argument('otafile', metavar='PAYLOAD', type=str, |
| 354 | help='the OTA package file (a .zip file) or raw payload \ |
| 355 | if device uses Omaha.') |
Alex Deymo | 6751bbe | 2017-03-21 11:20:02 -0700 | [diff] [blame] | 356 | parser.add_argument('--file', action='store_true', |
| 357 | help='Push the file to the device before updating.') |
| 358 | parser.add_argument('--no-push', action='store_true', |
| 359 | help='Skip the "push" command when using --file') |
| 360 | parser.add_argument('-s', type=str, default='', metavar='DEVICE', |
| 361 | help='The specific device to use.') |
| 362 | parser.add_argument('--no-verbose', action='store_true', |
| 363 | help='Less verbose output') |
Sen Jiang | a1784b7 | 2017-08-09 17:42:36 -0700 | [diff] [blame] | 364 | parser.add_argument('--public-key', type=str, default='', |
| 365 | help='Override the public key used to verify payload.') |
Sen Jiang | 6fbfd7d | 2017-10-31 16:16:56 -0700 | [diff] [blame] | 366 | parser.add_argument('--extra-headers', type=str, default='', |
| 367 | help='Extra headers to pass to the device.') |
Alex Deymo | 6751bbe | 2017-03-21 11:20:02 -0700 | [diff] [blame] | 368 | args = parser.parse_args() |
| 369 | logging.basicConfig( |
| 370 | level=logging.WARNING if args.no_verbose else logging.INFO) |
| 371 | |
| 372 | dut = AdbHost(args.s) |
| 373 | |
| 374 | server_thread = None |
| 375 | # List of commands to execute on exit. |
| 376 | finalize_cmds = [] |
| 377 | # Commands to execute when canceling an update. |
| 378 | cancel_cmd = ['shell', 'su', '0', 'update_engine_client', '--cancel'] |
| 379 | # List of commands to perform the update. |
| 380 | cmds = [] |
| 381 | |
Sen Jiang | a1784b7 | 2017-08-09 17:42:36 -0700 | [diff] [blame] | 382 | help_cmd = ['shell', 'su', '0', 'update_engine_client', '--help'] |
| 383 | use_omaha = 'omaha' in dut.adb_output(help_cmd) |
| 384 | |
Alex Deymo | 6751bbe | 2017-03-21 11:20:02 -0700 | [diff] [blame] | 385 | if args.file: |
| 386 | # Update via pushing a file to /data. |
| 387 | device_ota_file = os.path.join(OTA_PACKAGE_PATH, 'debug.zip') |
| 388 | payload_url = 'file://' + device_ota_file |
| 389 | if not args.no_push: |
Tao Bao | abb45a5 | 2017-10-25 11:13:03 -0700 | [diff] [blame] | 390 | data_local_tmp_file = '/data/local/tmp/debug.zip' |
| 391 | cmds.append(['push', args.otafile, data_local_tmp_file]) |
| 392 | cmds.append(['shell', 'su', '0', 'mv', data_local_tmp_file, |
| 393 | device_ota_file]) |
| 394 | cmds.append(['shell', 'su', '0', 'chcon', |
| 395 | 'u:object_r:ota_package_file:s0', device_ota_file]) |
Alex Deymo | 6751bbe | 2017-03-21 11:20:02 -0700 | [diff] [blame] | 396 | cmds.append(['shell', 'su', '0', 'chown', 'system:cache', device_ota_file]) |
| 397 | cmds.append(['shell', 'su', '0', 'chmod', '0660', device_ota_file]) |
| 398 | else: |
| 399 | # Update via sending the payload over the network with an "adb reverse" |
| 400 | # command. |
Sen Jiang | a1784b7 | 2017-08-09 17:42:36 -0700 | [diff] [blame] | 401 | payload_url = 'http://127.0.0.1:%d/payload' % DEVICE_PORT |
Sen Jiang | 3b15b59 | 2017-09-26 18:21:04 -0700 | [diff] [blame] | 402 | if use_omaha and zipfile.is_zipfile(args.otafile): |
| 403 | ota = AndroidOTAPackage(args.otafile) |
| 404 | serving_range = (ota.offset, ota.size) |
| 405 | else: |
| 406 | serving_range = (0, os.stat(args.otafile).st_size) |
| 407 | server_thread = StartServer(args.otafile, serving_range) |
Alex Deymo | 6751bbe | 2017-03-21 11:20:02 -0700 | [diff] [blame] | 408 | cmds.append( |
Sen Jiang | a1784b7 | 2017-08-09 17:42:36 -0700 | [diff] [blame] | 409 | ['reverse', 'tcp:%d' % DEVICE_PORT, 'tcp:%d' % server_thread.port]) |
| 410 | finalize_cmds.append(['reverse', '--remove', 'tcp:%d' % DEVICE_PORT]) |
| 411 | |
| 412 | if args.public_key: |
| 413 | payload_key_dir = os.path.dirname(PAYLOAD_KEY_PATH) |
| 414 | cmds.append( |
| 415 | ['shell', 'su', '0', 'mount', '-t', 'tmpfs', 'tmpfs', payload_key_dir]) |
| 416 | # Allow adb push to payload_key_dir |
| 417 | cmds.append(['shell', 'su', '0', 'chcon', 'u:object_r:shell_data_file:s0', |
| 418 | payload_key_dir]) |
| 419 | cmds.append(['push', args.public_key, PAYLOAD_KEY_PATH]) |
| 420 | # Allow update_engine to read it. |
| 421 | cmds.append(['shell', 'su', '0', 'chcon', '-R', 'u:object_r:system_file:s0', |
| 422 | payload_key_dir]) |
| 423 | finalize_cmds.append(['shell', 'su', '0', 'umount', payload_key_dir]) |
Alex Deymo | 6751bbe | 2017-03-21 11:20:02 -0700 | [diff] [blame] | 424 | |
| 425 | try: |
| 426 | # The main update command using the configured payload_url. |
Sen Jiang | a1784b7 | 2017-08-09 17:42:36 -0700 | [diff] [blame] | 427 | if use_omaha: |
| 428 | update_cmd = \ |
| 429 | OmahaUpdateCommand('http://127.0.0.1:%d/update' % DEVICE_PORT) |
| 430 | else: |
Sen Jiang | 6fbfd7d | 2017-10-31 16:16:56 -0700 | [diff] [blame] | 431 | update_cmd = \ |
| 432 | AndroidUpdateCommand(args.otafile, payload_url, args.extra_headers) |
Alex Deymo | 6751bbe | 2017-03-21 11:20:02 -0700 | [diff] [blame] | 433 | cmds.append(['shell', 'su', '0'] + update_cmd) |
| 434 | |
| 435 | for cmd in cmds: |
| 436 | dut.adb(cmd) |
| 437 | except KeyboardInterrupt: |
| 438 | dut.adb(cancel_cmd) |
| 439 | finally: |
| 440 | if server_thread: |
| 441 | server_thread.StopServer() |
| 442 | for cmd in finalize_cmds: |
| 443 | dut.adb(cmd) |
| 444 | |
| 445 | return 0 |
| 446 | |
Andrew Lassalle | 165843c | 2019-11-05 13:30:34 -0800 | [diff] [blame^] | 447 | |
Alex Deymo | 6751bbe | 2017-03-21 11:20:02 -0700 | [diff] [blame] | 448 | if __name__ == '__main__': |
| 449 | sys.exit(main()) |