1*5a923131SAndroid Build Coastguard Worker#!/usr/bin/env python3 2*5a923131SAndroid Build Coastguard Worker# 3*5a923131SAndroid Build Coastguard Worker# Copyright (C) 2017 The Android Open Source Project 4*5a923131SAndroid Build Coastguard Worker# 5*5a923131SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 6*5a923131SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 7*5a923131SAndroid Build Coastguard Worker# You may obtain a copy of the License at 8*5a923131SAndroid Build Coastguard Worker# 9*5a923131SAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 10*5a923131SAndroid Build Coastguard Worker# 11*5a923131SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 12*5a923131SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 13*5a923131SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14*5a923131SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 15*5a923131SAndroid Build Coastguard Worker# limitations under the License. 16*5a923131SAndroid Build Coastguard Worker# 17*5a923131SAndroid Build Coastguard Worker 18*5a923131SAndroid Build Coastguard Worker"""Send an A/B update to an Android device over adb.""" 19*5a923131SAndroid Build Coastguard Worker 20*5a923131SAndroid Build Coastguard Workerfrom __future__ import print_function 21*5a923131SAndroid Build Coastguard Workerfrom __future__ import absolute_import 22*5a923131SAndroid Build Coastguard Worker 23*5a923131SAndroid Build Coastguard Workerimport argparse 24*5a923131SAndroid Build Coastguard Workerimport binascii 25*5a923131SAndroid Build Coastguard Workerimport logging 26*5a923131SAndroid Build Coastguard Workerimport os 27*5a923131SAndroid Build Coastguard Workerimport re 28*5a923131SAndroid Build Coastguard Workerimport socket 29*5a923131SAndroid Build Coastguard Workerimport subprocess 30*5a923131SAndroid Build Coastguard Workerimport sys 31*5a923131SAndroid Build Coastguard Workerimport struct 32*5a923131SAndroid Build Coastguard Workerimport tempfile 33*5a923131SAndroid Build Coastguard Workerimport time 34*5a923131SAndroid Build Coastguard Workerimport threading 35*5a923131SAndroid Build Coastguard Workerimport zipfile 36*5a923131SAndroid Build Coastguard Workerimport shutil 37*5a923131SAndroid Build Coastguard Worker 38*5a923131SAndroid Build Coastguard Workerfrom six.moves import BaseHTTPServer 39*5a923131SAndroid Build Coastguard Worker 40*5a923131SAndroid Build Coastguard Worker 41*5a923131SAndroid Build Coastguard Worker# The path used to store the OTA package when applying the package from a file. 42*5a923131SAndroid Build Coastguard WorkerOTA_PACKAGE_PATH = '/data/ota_package' 43*5a923131SAndroid Build Coastguard Worker 44*5a923131SAndroid Build Coastguard Worker# The path to the payload public key on the device. 45*5a923131SAndroid Build Coastguard WorkerPAYLOAD_KEY_PATH = '/etc/update_engine/update-payload-key.pub.pem' 46*5a923131SAndroid Build Coastguard Worker 47*5a923131SAndroid Build Coastguard Worker# The port on the device that update_engine should connect to. 48*5a923131SAndroid Build Coastguard WorkerDEVICE_PORT = 1234 49*5a923131SAndroid Build Coastguard Worker 50*5a923131SAndroid Build Coastguard Worker 51*5a923131SAndroid Build Coastguard Workerdef CopyFileObjLength(fsrc, fdst, buffer_size=128 * 1024, copy_length=None, speed_limit=None): 52*5a923131SAndroid Build Coastguard Worker """Copy from a file object to another. 53*5a923131SAndroid Build Coastguard Worker 54*5a923131SAndroid Build Coastguard Worker This function is similar to shutil.copyfileobj except that it allows to copy 55*5a923131SAndroid Build Coastguard Worker less than the full source file. 56*5a923131SAndroid Build Coastguard Worker 57*5a923131SAndroid Build Coastguard Worker Args: 58*5a923131SAndroid Build Coastguard Worker fsrc: source file object where to read from. 59*5a923131SAndroid Build Coastguard Worker fdst: destination file object where to write to. 60*5a923131SAndroid Build Coastguard Worker buffer_size: size of the copy buffer in memory. 61*5a923131SAndroid Build Coastguard Worker copy_length: maximum number of bytes to copy, or None to copy everything. 62*5a923131SAndroid Build Coastguard Worker speed_limit: upper limit for copying speed, in bytes per second. 63*5a923131SAndroid Build Coastguard Worker 64*5a923131SAndroid Build Coastguard Worker Returns: 65*5a923131SAndroid Build Coastguard Worker the number of bytes copied. 66*5a923131SAndroid Build Coastguard Worker """ 67*5a923131SAndroid Build Coastguard Worker # If buffer size significantly bigger than speed limit 68*5a923131SAndroid Build Coastguard Worker # traffic would seem extremely spiky to the client. 69*5a923131SAndroid Build Coastguard Worker if speed_limit: 70*5a923131SAndroid Build Coastguard Worker print(f"Applying speed limit: {speed_limit}") 71*5a923131SAndroid Build Coastguard Worker buffer_size = min(speed_limit//32, buffer_size) 72*5a923131SAndroid Build Coastguard Worker 73*5a923131SAndroid Build Coastguard Worker start_time = time.time() 74*5a923131SAndroid Build Coastguard Worker copied = 0 75*5a923131SAndroid Build Coastguard Worker while True: 76*5a923131SAndroid Build Coastguard Worker chunk_size = buffer_size 77*5a923131SAndroid Build Coastguard Worker if copy_length is not None: 78*5a923131SAndroid Build Coastguard Worker chunk_size = min(chunk_size, copy_length - copied) 79*5a923131SAndroid Build Coastguard Worker if not chunk_size: 80*5a923131SAndroid Build Coastguard Worker break 81*5a923131SAndroid Build Coastguard Worker buf = fsrc.read(chunk_size) 82*5a923131SAndroid Build Coastguard Worker if not buf: 83*5a923131SAndroid Build Coastguard Worker break 84*5a923131SAndroid Build Coastguard Worker if speed_limit: 85*5a923131SAndroid Build Coastguard Worker expected_duration = copied/speed_limit 86*5a923131SAndroid Build Coastguard Worker actual_duration = time.time() - start_time 87*5a923131SAndroid Build Coastguard Worker if actual_duration < expected_duration: 88*5a923131SAndroid Build Coastguard Worker time.sleep(expected_duration-actual_duration) 89*5a923131SAndroid Build Coastguard Worker fdst.write(buf) 90*5a923131SAndroid Build Coastguard Worker copied += len(buf) 91*5a923131SAndroid Build Coastguard Worker return copied 92*5a923131SAndroid Build Coastguard Worker 93*5a923131SAndroid Build Coastguard Worker 94*5a923131SAndroid Build Coastguard Workerclass AndroidOTAPackage(object): 95*5a923131SAndroid Build Coastguard Worker """Android update payload using the .zip format. 96*5a923131SAndroid Build Coastguard Worker 97*5a923131SAndroid Build Coastguard Worker Android OTA packages traditionally used a .zip file to store the payload. When 98*5a923131SAndroid Build Coastguard Worker applying A/B updates over the network, a payload binary is stored RAW inside 99*5a923131SAndroid Build Coastguard Worker this .zip file which is used by update_engine to apply the payload. To do 100*5a923131SAndroid Build Coastguard Worker this, an offset and size inside the .zip file are provided. 101*5a923131SAndroid Build Coastguard Worker """ 102*5a923131SAndroid Build Coastguard Worker 103*5a923131SAndroid Build Coastguard Worker # Android OTA package file paths. 104*5a923131SAndroid Build Coastguard Worker OTA_PAYLOAD_BIN = 'payload.bin' 105*5a923131SAndroid Build Coastguard Worker OTA_PAYLOAD_PROPERTIES_TXT = 'payload_properties.txt' 106*5a923131SAndroid Build Coastguard Worker SECONDARY_OTA_PAYLOAD_BIN = 'secondary/payload.bin' 107*5a923131SAndroid Build Coastguard Worker SECONDARY_OTA_PAYLOAD_PROPERTIES_TXT = 'secondary/payload_properties.txt' 108*5a923131SAndroid Build Coastguard Worker PAYLOAD_MAGIC_HEADER = b'CrAU' 109*5a923131SAndroid Build Coastguard Worker 110*5a923131SAndroid Build Coastguard Worker def __init__(self, otafilename, secondary_payload=False): 111*5a923131SAndroid Build Coastguard Worker self.otafilename = otafilename 112*5a923131SAndroid Build Coastguard Worker 113*5a923131SAndroid Build Coastguard Worker otazip = zipfile.ZipFile(otafilename, 'r') 114*5a923131SAndroid Build Coastguard Worker payload_entry = (self.SECONDARY_OTA_PAYLOAD_BIN if secondary_payload else 115*5a923131SAndroid Build Coastguard Worker self.OTA_PAYLOAD_BIN) 116*5a923131SAndroid Build Coastguard Worker payload_info = otazip.getinfo(payload_entry) 117*5a923131SAndroid Build Coastguard Worker 118*5a923131SAndroid Build Coastguard Worker if payload_info.compress_type != 0: 119*5a923131SAndroid Build Coastguard Worker logging.error( 120*5a923131SAndroid Build Coastguard Worker "Expected payload to be uncompressed, got compression method %d", 121*5a923131SAndroid Build Coastguard Worker payload_info.compress_type) 122*5a923131SAndroid Build Coastguard Worker # Don't use len(payload_info.extra). Because that returns size of extra 123*5a923131SAndroid Build Coastguard Worker # fields in central directory. We need to look at local file directory, 124*5a923131SAndroid Build Coastguard Worker # as these two might have different sizes. 125*5a923131SAndroid Build Coastguard Worker with open(otafilename, "rb") as fp: 126*5a923131SAndroid Build Coastguard Worker fp.seek(payload_info.header_offset) 127*5a923131SAndroid Build Coastguard Worker data = fp.read(zipfile.sizeFileHeader) 128*5a923131SAndroid Build Coastguard Worker fheader = struct.unpack(zipfile.structFileHeader, data) 129*5a923131SAndroid Build Coastguard Worker # Last two fields of local file header are filename length and 130*5a923131SAndroid Build Coastguard Worker # extra length 131*5a923131SAndroid Build Coastguard Worker filename_len = fheader[-2] 132*5a923131SAndroid Build Coastguard Worker extra_len = fheader[-1] 133*5a923131SAndroid Build Coastguard Worker self.offset = payload_info.header_offset 134*5a923131SAndroid Build Coastguard Worker self.offset += zipfile.sizeFileHeader 135*5a923131SAndroid Build Coastguard Worker self.offset += filename_len + extra_len 136*5a923131SAndroid Build Coastguard Worker self.size = payload_info.file_size 137*5a923131SAndroid Build Coastguard Worker fp.seek(self.offset) 138*5a923131SAndroid Build Coastguard Worker payload_header = fp.read(4) 139*5a923131SAndroid Build Coastguard Worker if payload_header != self.PAYLOAD_MAGIC_HEADER: 140*5a923131SAndroid Build Coastguard Worker logging.warning( 141*5a923131SAndroid Build Coastguard Worker "Invalid header, expected %s, got %s." 142*5a923131SAndroid Build Coastguard Worker "Either the offset is not correct, or payload is corrupted", 143*5a923131SAndroid Build Coastguard Worker binascii.hexlify(self.PAYLOAD_MAGIC_HEADER), 144*5a923131SAndroid Build Coastguard Worker binascii.hexlify(payload_header)) 145*5a923131SAndroid Build Coastguard Worker 146*5a923131SAndroid Build Coastguard Worker property_entry = (self.SECONDARY_OTA_PAYLOAD_PROPERTIES_TXT if 147*5a923131SAndroid Build Coastguard Worker secondary_payload else self.OTA_PAYLOAD_PROPERTIES_TXT) 148*5a923131SAndroid Build Coastguard Worker self.properties = otazip.read(property_entry) 149*5a923131SAndroid Build Coastguard Worker 150*5a923131SAndroid Build Coastguard Worker 151*5a923131SAndroid Build Coastguard Workerclass UpdateHandler(BaseHTTPServer.BaseHTTPRequestHandler): 152*5a923131SAndroid Build Coastguard Worker """A HTTPServer that supports single-range requests. 153*5a923131SAndroid Build Coastguard Worker 154*5a923131SAndroid Build Coastguard Worker Attributes: 155*5a923131SAndroid Build Coastguard Worker serving_payload: path to the only payload file we are serving. 156*5a923131SAndroid Build Coastguard Worker serving_range: the start offset and size tuple of the payload. 157*5a923131SAndroid Build Coastguard Worker """ 158*5a923131SAndroid Build Coastguard Worker 159*5a923131SAndroid Build Coastguard Worker @staticmethod 160*5a923131SAndroid Build Coastguard Worker def _parse_range(range_str, file_size): 161*5a923131SAndroid Build Coastguard Worker """Parse an HTTP range string. 162*5a923131SAndroid Build Coastguard Worker 163*5a923131SAndroid Build Coastguard Worker Args: 164*5a923131SAndroid Build Coastguard Worker range_str: HTTP Range header in the request, not including "Header:". 165*5a923131SAndroid Build Coastguard Worker file_size: total size of the serving file. 166*5a923131SAndroid Build Coastguard Worker 167*5a923131SAndroid Build Coastguard Worker Returns: 168*5a923131SAndroid Build Coastguard Worker A tuple (start_range, end_range) with the range of bytes requested. 169*5a923131SAndroid Build Coastguard Worker """ 170*5a923131SAndroid Build Coastguard Worker start_range = 0 171*5a923131SAndroid Build Coastguard Worker end_range = file_size 172*5a923131SAndroid Build Coastguard Worker 173*5a923131SAndroid Build Coastguard Worker if range_str: 174*5a923131SAndroid Build Coastguard Worker range_str = range_str.split('=', 1)[1] 175*5a923131SAndroid Build Coastguard Worker s, e = range_str.split('-', 1) 176*5a923131SAndroid Build Coastguard Worker if s: 177*5a923131SAndroid Build Coastguard Worker start_range = int(s) 178*5a923131SAndroid Build Coastguard Worker if e: 179*5a923131SAndroid Build Coastguard Worker end_range = int(e) + 1 180*5a923131SAndroid Build Coastguard Worker elif e: 181*5a923131SAndroid Build Coastguard Worker if int(e) < file_size: 182*5a923131SAndroid Build Coastguard Worker start_range = file_size - int(e) 183*5a923131SAndroid Build Coastguard Worker return start_range, end_range 184*5a923131SAndroid Build Coastguard Worker 185*5a923131SAndroid Build Coastguard Worker def do_GET(self): # pylint: disable=invalid-name 186*5a923131SAndroid Build Coastguard Worker """Reply with the requested payload file.""" 187*5a923131SAndroid Build Coastguard Worker if self.path != '/payload': 188*5a923131SAndroid Build Coastguard Worker self.send_error(404, 'Unknown request') 189*5a923131SAndroid Build Coastguard Worker return 190*5a923131SAndroid Build Coastguard Worker 191*5a923131SAndroid Build Coastguard Worker if not self.serving_payload: 192*5a923131SAndroid Build Coastguard Worker self.send_error(500, 'No serving payload set') 193*5a923131SAndroid Build Coastguard Worker return 194*5a923131SAndroid Build Coastguard Worker 195*5a923131SAndroid Build Coastguard Worker try: 196*5a923131SAndroid Build Coastguard Worker f = open(self.serving_payload, 'rb') 197*5a923131SAndroid Build Coastguard Worker except IOError: 198*5a923131SAndroid Build Coastguard Worker self.send_error(404, 'File not found') 199*5a923131SAndroid Build Coastguard Worker return 200*5a923131SAndroid Build Coastguard Worker # Handle the range request. 201*5a923131SAndroid Build Coastguard Worker if 'Range' in self.headers: 202*5a923131SAndroid Build Coastguard Worker self.send_response(206) 203*5a923131SAndroid Build Coastguard Worker else: 204*5a923131SAndroid Build Coastguard Worker self.send_response(200) 205*5a923131SAndroid Build Coastguard Worker 206*5a923131SAndroid Build Coastguard Worker serving_start, serving_size = self.serving_range 207*5a923131SAndroid Build Coastguard Worker start_range, end_range = self._parse_range(self.headers.get('range'), 208*5a923131SAndroid Build Coastguard Worker serving_size) 209*5a923131SAndroid Build Coastguard Worker logging.info('Serving request for %s from %s [%d, %d) length: %d', 210*5a923131SAndroid Build Coastguard Worker self.path, self.serving_payload, serving_start + start_range, 211*5a923131SAndroid Build Coastguard Worker serving_start + end_range, end_range - start_range) 212*5a923131SAndroid Build Coastguard Worker 213*5a923131SAndroid Build Coastguard Worker self.send_header('Accept-Ranges', 'bytes') 214*5a923131SAndroid Build Coastguard Worker self.send_header('Content-Range', 215*5a923131SAndroid Build Coastguard Worker 'bytes ' + str(start_range) + '-' + str(end_range - 1) + 216*5a923131SAndroid Build Coastguard Worker '/' + str(end_range - start_range)) 217*5a923131SAndroid Build Coastguard Worker self.send_header('Content-Length', end_range - start_range) 218*5a923131SAndroid Build Coastguard Worker 219*5a923131SAndroid Build Coastguard Worker stat = os.fstat(f.fileno()) 220*5a923131SAndroid Build Coastguard Worker self.send_header('Last-Modified', self.date_time_string(stat.st_mtime)) 221*5a923131SAndroid Build Coastguard Worker self.send_header('Content-type', 'application/octet-stream') 222*5a923131SAndroid Build Coastguard Worker self.end_headers() 223*5a923131SAndroid Build Coastguard Worker 224*5a923131SAndroid Build Coastguard Worker f.seek(serving_start + start_range) 225*5a923131SAndroid Build Coastguard Worker CopyFileObjLength(f, self.wfile, copy_length=end_range - 226*5a923131SAndroid Build Coastguard Worker start_range, speed_limit=self.speed_limit) 227*5a923131SAndroid Build Coastguard Worker 228*5a923131SAndroid Build Coastguard Worker 229*5a923131SAndroid Build Coastguard Workerclass ServerThread(threading.Thread): 230*5a923131SAndroid Build Coastguard Worker """A thread for serving HTTP requests.""" 231*5a923131SAndroid Build Coastguard Worker 232*5a923131SAndroid Build Coastguard Worker def __init__(self, ota_filename, serving_range, speed_limit): 233*5a923131SAndroid Build Coastguard Worker threading.Thread.__init__(self) 234*5a923131SAndroid Build Coastguard Worker # serving_payload and serving_range are class attributes and the 235*5a923131SAndroid Build Coastguard Worker # UpdateHandler class is instantiated with every request. 236*5a923131SAndroid Build Coastguard Worker UpdateHandler.serving_payload = ota_filename 237*5a923131SAndroid Build Coastguard Worker UpdateHandler.serving_range = serving_range 238*5a923131SAndroid Build Coastguard Worker UpdateHandler.speed_limit = speed_limit 239*5a923131SAndroid Build Coastguard Worker self._httpd = BaseHTTPServer.HTTPServer(('127.0.0.1', 0), UpdateHandler) 240*5a923131SAndroid Build Coastguard Worker self.port = self._httpd.server_port 241*5a923131SAndroid Build Coastguard Worker 242*5a923131SAndroid Build Coastguard Worker def run(self): 243*5a923131SAndroid Build Coastguard Worker try: 244*5a923131SAndroid Build Coastguard Worker self._httpd.serve_forever() 245*5a923131SAndroid Build Coastguard Worker except (KeyboardInterrupt, socket.error): 246*5a923131SAndroid Build Coastguard Worker pass 247*5a923131SAndroid Build Coastguard Worker logging.info('Server Terminated') 248*5a923131SAndroid Build Coastguard Worker 249*5a923131SAndroid Build Coastguard Worker def StopServer(self): 250*5a923131SAndroid Build Coastguard Worker self._httpd.shutdown() 251*5a923131SAndroid Build Coastguard Worker self._httpd.socket.close() 252*5a923131SAndroid Build Coastguard Worker 253*5a923131SAndroid Build Coastguard Worker 254*5a923131SAndroid Build Coastguard Workerdef StartServer(ota_filename, serving_range, speed_limit): 255*5a923131SAndroid Build Coastguard Worker t = ServerThread(ota_filename, serving_range, speed_limit) 256*5a923131SAndroid Build Coastguard Worker t.start() 257*5a923131SAndroid Build Coastguard Worker return t 258*5a923131SAndroid Build Coastguard Worker 259*5a923131SAndroid Build Coastguard Worker 260*5a923131SAndroid Build Coastguard Workerdef AndroidUpdateCommand(ota_filename, secondary, payload_url, extra_headers): 261*5a923131SAndroid Build Coastguard Worker """Return the command to run to start the update in the Android device.""" 262*5a923131SAndroid Build Coastguard Worker ota = AndroidOTAPackage(ota_filename, secondary) 263*5a923131SAndroid Build Coastguard Worker headers = ota.properties 264*5a923131SAndroid Build Coastguard Worker headers += b'USER_AGENT=Dalvik (something, something)\n' 265*5a923131SAndroid Build Coastguard Worker headers += b'NETWORK_ID=0\n' 266*5a923131SAndroid Build Coastguard Worker headers += extra_headers.encode() 267*5a923131SAndroid Build Coastguard Worker 268*5a923131SAndroid Build Coastguard Worker return ['update_engine_client', '--update', '--follow', 269*5a923131SAndroid Build Coastguard Worker '--payload=%s' % payload_url, '--offset=%d' % ota.offset, 270*5a923131SAndroid Build Coastguard Worker '--size=%d' % ota.size, '--headers="%s"' % headers.decode()] 271*5a923131SAndroid Build Coastguard Worker 272*5a923131SAndroid Build Coastguard Worker 273*5a923131SAndroid Build Coastguard Workerclass AdbHost(object): 274*5a923131SAndroid Build Coastguard Worker """Represents a device connected via ADB.""" 275*5a923131SAndroid Build Coastguard Worker 276*5a923131SAndroid Build Coastguard Worker def __init__(self, device_serial=None): 277*5a923131SAndroid Build Coastguard Worker """Construct an instance. 278*5a923131SAndroid Build Coastguard Worker 279*5a923131SAndroid Build Coastguard Worker Args: 280*5a923131SAndroid Build Coastguard Worker device_serial: options string serial number of attached device. 281*5a923131SAndroid Build Coastguard Worker """ 282*5a923131SAndroid Build Coastguard Worker self._device_serial = device_serial 283*5a923131SAndroid Build Coastguard Worker self._command_prefix = ['adb'] 284*5a923131SAndroid Build Coastguard Worker if self._device_serial: 285*5a923131SAndroid Build Coastguard Worker self._command_prefix += ['-s', self._device_serial] 286*5a923131SAndroid Build Coastguard Worker 287*5a923131SAndroid Build Coastguard Worker def adb(self, command, timeout_seconds: float = None): 288*5a923131SAndroid Build Coastguard Worker """Run an ADB command like "adb push". 289*5a923131SAndroid Build Coastguard Worker 290*5a923131SAndroid Build Coastguard Worker Args: 291*5a923131SAndroid Build Coastguard Worker command: list of strings containing command and arguments to run 292*5a923131SAndroid Build Coastguard Worker 293*5a923131SAndroid Build Coastguard Worker Returns: 294*5a923131SAndroid Build Coastguard Worker the program's return code. 295*5a923131SAndroid Build Coastguard Worker 296*5a923131SAndroid Build Coastguard Worker Raises: 297*5a923131SAndroid Build Coastguard Worker subprocess.CalledProcessError on command exit != 0. 298*5a923131SAndroid Build Coastguard Worker """ 299*5a923131SAndroid Build Coastguard Worker command = self._command_prefix + command 300*5a923131SAndroid Build Coastguard Worker logging.info('Running: %s', ' '.join(str(x) for x in command)) 301*5a923131SAndroid Build Coastguard Worker p = subprocess.Popen(command, universal_newlines=True) 302*5a923131SAndroid Build Coastguard Worker p.wait(timeout_seconds) 303*5a923131SAndroid Build Coastguard Worker return p.returncode 304*5a923131SAndroid Build Coastguard Worker 305*5a923131SAndroid Build Coastguard Worker def adb_output(self, command): 306*5a923131SAndroid Build Coastguard Worker """Run an ADB command like "adb push" and return the output. 307*5a923131SAndroid Build Coastguard Worker 308*5a923131SAndroid Build Coastguard Worker Args: 309*5a923131SAndroid Build Coastguard Worker command: list of strings containing command and arguments to run 310*5a923131SAndroid Build Coastguard Worker 311*5a923131SAndroid Build Coastguard Worker Returns: 312*5a923131SAndroid Build Coastguard Worker the program's output as a string. 313*5a923131SAndroid Build Coastguard Worker 314*5a923131SAndroid Build Coastguard Worker Raises: 315*5a923131SAndroid Build Coastguard Worker subprocess.CalledProcessError on command exit != 0. 316*5a923131SAndroid Build Coastguard Worker """ 317*5a923131SAndroid Build Coastguard Worker command = self._command_prefix + command 318*5a923131SAndroid Build Coastguard Worker logging.info('Running: %s', ' '.join(str(x) for x in command)) 319*5a923131SAndroid Build Coastguard Worker return subprocess.check_output(command, universal_newlines=True) 320*5a923131SAndroid Build Coastguard Worker 321*5a923131SAndroid Build Coastguard Worker 322*5a923131SAndroid Build Coastguard Workerdef PushMetadata(dut, otafile, metadata_path): 323*5a923131SAndroid Build Coastguard Worker header_format = ">4sQQL" 324*5a923131SAndroid Build Coastguard Worker with tempfile.TemporaryDirectory() as tmpdir: 325*5a923131SAndroid Build Coastguard Worker with zipfile.ZipFile(otafile, "r") as zfp: 326*5a923131SAndroid Build Coastguard Worker extracted_path = os.path.join(tmpdir, "payload.bin") 327*5a923131SAndroid Build Coastguard Worker with zfp.open("payload.bin") as payload_fp, \ 328*5a923131SAndroid Build Coastguard Worker open(extracted_path, "wb") as output_fp: 329*5a923131SAndroid Build Coastguard Worker # Only extract the first |data_offset| bytes from the payload. 330*5a923131SAndroid Build Coastguard Worker # This is because allocateSpaceForPayload only needs to see 331*5a923131SAndroid Build Coastguard Worker # the manifest, not the entire payload. 332*5a923131SAndroid Build Coastguard Worker # Extracting the entire payload works, but is slow for full 333*5a923131SAndroid Build Coastguard Worker # OTA. 334*5a923131SAndroid Build Coastguard Worker header = payload_fp.read(struct.calcsize(header_format)) 335*5a923131SAndroid Build Coastguard Worker magic, major_version, manifest_size, metadata_signature_size = struct.unpack(header_format, header) 336*5a923131SAndroid Build Coastguard Worker assert magic == b"CrAU", "Invalid magic {}, expected CrAU".format(magic) 337*5a923131SAndroid Build Coastguard Worker assert major_version == 2, "Invalid major version {}, only version 2 is supported".format(major_version) 338*5a923131SAndroid Build Coastguard Worker output_fp.write(header) 339*5a923131SAndroid Build Coastguard Worker output_fp.write(payload_fp.read(manifest_size + metadata_signature_size)) 340*5a923131SAndroid Build Coastguard Worker 341*5a923131SAndroid Build Coastguard Worker return dut.adb([ 342*5a923131SAndroid Build Coastguard Worker "push", 343*5a923131SAndroid Build Coastguard Worker extracted_path, 344*5a923131SAndroid Build Coastguard Worker metadata_path 345*5a923131SAndroid Build Coastguard Worker ]) == 0 346*5a923131SAndroid Build Coastguard Worker 347*5a923131SAndroid Build Coastguard Worker 348*5a923131SAndroid Build Coastguard Workerdef ParseSpeedLimit(arg: str) -> int: 349*5a923131SAndroid Build Coastguard Worker arg = arg.strip().upper() 350*5a923131SAndroid Build Coastguard Worker if not re.match(r"\d+[KkMmGgTt]?", arg): 351*5a923131SAndroid Build Coastguard Worker raise argparse.ArgumentError( 352*5a923131SAndroid Build Coastguard Worker "Wrong speed limit format, expected format is number followed by unit, such as 10K, 5m, 3G (case insensitive)") 353*5a923131SAndroid Build Coastguard Worker unit = 1 354*5a923131SAndroid Build Coastguard Worker if arg[-1].isalpha(): 355*5a923131SAndroid Build Coastguard Worker if arg[-1] == "K": 356*5a923131SAndroid Build Coastguard Worker unit = 1024 357*5a923131SAndroid Build Coastguard Worker elif arg[-1] == "M": 358*5a923131SAndroid Build Coastguard Worker unit = 1024 * 1024 359*5a923131SAndroid Build Coastguard Worker elif arg[-1] == "G": 360*5a923131SAndroid Build Coastguard Worker unit = 1024 * 1024 * 1024 361*5a923131SAndroid Build Coastguard Worker elif arg[-1] == "T": 362*5a923131SAndroid Build Coastguard Worker unit = 1024 * 1024 * 1024 * 1024 363*5a923131SAndroid Build Coastguard Worker else: 364*5a923131SAndroid Build Coastguard Worker raise argparse.ArgumentError( 365*5a923131SAndroid Build Coastguard Worker f"Unsupported unit for download speed: {arg[-1]}, supported units are K,M,G,T (case insensitive)") 366*5a923131SAndroid Build Coastguard Worker return int(float(arg[:-1]) * unit) 367*5a923131SAndroid Build Coastguard Worker 368*5a923131SAndroid Build Coastguard Worker 369*5a923131SAndroid Build Coastguard Workerdef main(): 370*5a923131SAndroid Build Coastguard Worker parser = argparse.ArgumentParser(description='Android A/B OTA helper.') 371*5a923131SAndroid Build Coastguard Worker parser.add_argument('otafile', metavar='PAYLOAD', type=str, 372*5a923131SAndroid Build Coastguard Worker help='the OTA package file (a .zip file) or raw payload \ 373*5a923131SAndroid Build Coastguard Worker if device uses Omaha.') 374*5a923131SAndroid Build Coastguard Worker parser.add_argument('--file', action='store_true', 375*5a923131SAndroid Build Coastguard Worker help='Push the file to the device before updating.') 376*5a923131SAndroid Build Coastguard Worker parser.add_argument('--no-push', action='store_true', 377*5a923131SAndroid Build Coastguard Worker help='Skip the "push" command when using --file') 378*5a923131SAndroid Build Coastguard Worker parser.add_argument('-s', type=str, default='', metavar='DEVICE', 379*5a923131SAndroid Build Coastguard Worker help='The specific device to use.') 380*5a923131SAndroid Build Coastguard Worker parser.add_argument('--no-verbose', action='store_true', 381*5a923131SAndroid Build Coastguard Worker help='Less verbose output') 382*5a923131SAndroid Build Coastguard Worker parser.add_argument('--public-key', type=str, default='', 383*5a923131SAndroid Build Coastguard Worker help='Override the public key used to verify payload.') 384*5a923131SAndroid Build Coastguard Worker parser.add_argument('--extra-headers', type=str, default='', 385*5a923131SAndroid Build Coastguard Worker help='Extra headers to pass to the device.') 386*5a923131SAndroid Build Coastguard Worker parser.add_argument('--secondary', action='store_true', 387*5a923131SAndroid Build Coastguard Worker help='Update with the secondary payload in the package.') 388*5a923131SAndroid Build Coastguard Worker parser.add_argument('--no-slot-switch', action='store_true', 389*5a923131SAndroid Build Coastguard Worker help='Do not perform slot switch after the update.') 390*5a923131SAndroid Build Coastguard Worker parser.add_argument('--no-postinstall', action='store_true', 391*5a923131SAndroid Build Coastguard Worker help='Do not execute postinstall scripts after the update.') 392*5a923131SAndroid Build Coastguard Worker parser.add_argument('--allocate-only', action='store_true', 393*5a923131SAndroid Build Coastguard Worker help='Allocate space for this OTA, instead of actually \ 394*5a923131SAndroid Build Coastguard Worker applying the OTA.') 395*5a923131SAndroid Build Coastguard Worker parser.add_argument('--verify-only', action='store_true', 396*5a923131SAndroid Build Coastguard Worker help='Verify metadata then exit, instead of applying the OTA.') 397*5a923131SAndroid Build Coastguard Worker parser.add_argument('--no-care-map', action='store_true', 398*5a923131SAndroid Build Coastguard Worker help='Do not push care_map.pb to device.') 399*5a923131SAndroid Build Coastguard Worker parser.add_argument('--perform-slot-switch', action='store_true', 400*5a923131SAndroid Build Coastguard Worker help='Perform slot switch for this OTA package') 401*5a923131SAndroid Build Coastguard Worker parser.add_argument('--perform-reset-slot-switch', action='store_true', 402*5a923131SAndroid Build Coastguard Worker help='Perform reset slot switch for this OTA package') 403*5a923131SAndroid Build Coastguard Worker parser.add_argument('--wipe-user-data', action='store_true', 404*5a923131SAndroid Build Coastguard Worker help='Wipe userdata after installing OTA') 405*5a923131SAndroid Build Coastguard Worker parser.add_argument('--vabc-none', action='store_true', 406*5a923131SAndroid Build Coastguard Worker help='Set Virtual AB Compression algorithm to none, but still use Android COW format') 407*5a923131SAndroid Build Coastguard Worker parser.add_argument('--disable-vabc', action='store_true', 408*5a923131SAndroid Build Coastguard Worker help='Option to enable or disable vabc. If set to false, will fall back on A/B') 409*5a923131SAndroid Build Coastguard Worker parser.add_argument('--enable-threading', action='store_true', 410*5a923131SAndroid Build Coastguard Worker help='Enable multi-threaded compression for VABC') 411*5a923131SAndroid Build Coastguard Worker parser.add_argument('--disable-threading', action='store_true', 412*5a923131SAndroid Build Coastguard Worker help='Disable multi-threaded compression for VABC') 413*5a923131SAndroid Build Coastguard Worker parser.add_argument('--batched-writes', action='store_true', 414*5a923131SAndroid Build Coastguard Worker help='Enable batched writes for VABC') 415*5a923131SAndroid Build Coastguard Worker parser.add_argument('--speed-limit', type=str, 416*5a923131SAndroid Build Coastguard Worker help='Speed limit for serving payloads over HTTP. For ' 417*5a923131SAndroid Build Coastguard Worker 'example: 10K, 5m, 1G, input is case insensitive') 418*5a923131SAndroid Build Coastguard Worker 419*5a923131SAndroid Build Coastguard Worker args = parser.parse_args() 420*5a923131SAndroid Build Coastguard Worker if args.speed_limit: 421*5a923131SAndroid Build Coastguard Worker args.speed_limit = ParseSpeedLimit(args.speed_limit) 422*5a923131SAndroid Build Coastguard Worker 423*5a923131SAndroid Build Coastguard Worker logging.basicConfig( 424*5a923131SAndroid Build Coastguard Worker level=logging.WARNING if args.no_verbose else logging.INFO) 425*5a923131SAndroid Build Coastguard Worker 426*5a923131SAndroid Build Coastguard Worker start_time = time.perf_counter() 427*5a923131SAndroid Build Coastguard Worker 428*5a923131SAndroid Build Coastguard Worker dut = AdbHost(args.s) 429*5a923131SAndroid Build Coastguard Worker 430*5a923131SAndroid Build Coastguard Worker server_thread = None 431*5a923131SAndroid Build Coastguard Worker # List of commands to execute on exit. 432*5a923131SAndroid Build Coastguard Worker finalize_cmds = [] 433*5a923131SAndroid Build Coastguard Worker # Commands to execute when canceling an update. 434*5a923131SAndroid Build Coastguard Worker cancel_cmd = ['shell', 'su', '0', 'update_engine_client', '--cancel'] 435*5a923131SAndroid Build Coastguard Worker # List of commands to perform the update. 436*5a923131SAndroid Build Coastguard Worker cmds = [] 437*5a923131SAndroid Build Coastguard Worker 438*5a923131SAndroid Build Coastguard Worker help_cmd = ['shell', 'su', '0', 'update_engine_client', '--help'] 439*5a923131SAndroid Build Coastguard Worker 440*5a923131SAndroid Build Coastguard Worker metadata_path = "/data/ota_package/metadata" 441*5a923131SAndroid Build Coastguard Worker if args.allocate_only: 442*5a923131SAndroid Build Coastguard Worker with zipfile.ZipFile(args.otafile, "r") as zfp: 443*5a923131SAndroid Build Coastguard Worker headers = zfp.read("payload_properties.txt").decode() 444*5a923131SAndroid Build Coastguard Worker if PushMetadata(dut, args.otafile, metadata_path): 445*5a923131SAndroid Build Coastguard Worker dut.adb([ 446*5a923131SAndroid Build Coastguard Worker "shell", "update_engine_client", "--allocate", 447*5a923131SAndroid Build Coastguard Worker "--metadata={} --headers='{}'".format(metadata_path, headers)]) 448*5a923131SAndroid Build Coastguard Worker # Return 0, as we are executing ADB commands here, no work needed after 449*5a923131SAndroid Build Coastguard Worker # this point 450*5a923131SAndroid Build Coastguard Worker return 0 451*5a923131SAndroid Build Coastguard Worker if args.verify_only: 452*5a923131SAndroid Build Coastguard Worker if PushMetadata(dut, args.otafile, metadata_path): 453*5a923131SAndroid Build Coastguard Worker dut.adb([ 454*5a923131SAndroid Build Coastguard Worker "shell", "update_engine_client", "--verify", 455*5a923131SAndroid Build Coastguard Worker "--metadata={}".format(metadata_path)]) 456*5a923131SAndroid Build Coastguard Worker # Return 0, as we are executing ADB commands here, no work needed after 457*5a923131SAndroid Build Coastguard Worker # this point 458*5a923131SAndroid Build Coastguard Worker return 0 459*5a923131SAndroid Build Coastguard Worker if args.perform_slot_switch: 460*5a923131SAndroid Build Coastguard Worker assert PushMetadata(dut, args.otafile, metadata_path) 461*5a923131SAndroid Build Coastguard Worker dut.adb(["shell", "update_engine_client", 462*5a923131SAndroid Build Coastguard Worker "--switch_slot=true", "--metadata={}".format(metadata_path), "--follow"]) 463*5a923131SAndroid Build Coastguard Worker return 0 464*5a923131SAndroid Build Coastguard Worker if args.perform_reset_slot_switch: 465*5a923131SAndroid Build Coastguard Worker assert PushMetadata(dut, args.otafile, metadata_path) 466*5a923131SAndroid Build Coastguard Worker dut.adb(["shell", "update_engine_client", 467*5a923131SAndroid Build Coastguard Worker "--switch_slot=false", "--metadata={}".format(metadata_path)]) 468*5a923131SAndroid Build Coastguard Worker return 0 469*5a923131SAndroid Build Coastguard Worker 470*5a923131SAndroid Build Coastguard Worker if args.no_slot_switch: 471*5a923131SAndroid Build Coastguard Worker args.extra_headers += "\nSWITCH_SLOT_ON_REBOOT=0" 472*5a923131SAndroid Build Coastguard Worker if args.no_postinstall: 473*5a923131SAndroid Build Coastguard Worker args.extra_headers += "\nRUN_POST_INSTALL=0" 474*5a923131SAndroid Build Coastguard Worker if args.wipe_user_data: 475*5a923131SAndroid Build Coastguard Worker args.extra_headers += "\nPOWERWASH=1" 476*5a923131SAndroid Build Coastguard Worker if args.vabc_none: 477*5a923131SAndroid Build Coastguard Worker args.extra_headers += "\nVABC_NONE=1" 478*5a923131SAndroid Build Coastguard Worker if args.disable_vabc: 479*5a923131SAndroid Build Coastguard Worker args.extra_headers += "\nDISABLE_VABC=1" 480*5a923131SAndroid Build Coastguard Worker if args.enable_threading: 481*5a923131SAndroid Build Coastguard Worker args.extra_headers += "\nENABLE_THREADING=1" 482*5a923131SAndroid Build Coastguard Worker elif args.disable_threading: 483*5a923131SAndroid Build Coastguard Worker args.extra_headers += "\nENABLE_THREADING=0" 484*5a923131SAndroid Build Coastguard Worker if args.batched_writes: 485*5a923131SAndroid Build Coastguard Worker args.extra_headers += "\nBATCHED_WRITES=1" 486*5a923131SAndroid Build Coastguard Worker 487*5a923131SAndroid Build Coastguard Worker with zipfile.ZipFile(args.otafile) as zfp: 488*5a923131SAndroid Build Coastguard Worker CARE_MAP_ENTRY_NAME = "care_map.pb" 489*5a923131SAndroid Build Coastguard Worker if CARE_MAP_ENTRY_NAME in zfp.namelist() and not args.no_care_map: 490*5a923131SAndroid Build Coastguard Worker # Need root permission to push to /data 491*5a923131SAndroid Build Coastguard Worker dut.adb(["root"]) 492*5a923131SAndroid Build Coastguard Worker with tempfile.NamedTemporaryFile() as care_map_fp: 493*5a923131SAndroid Build Coastguard Worker care_map_fp.write(zfp.read(CARE_MAP_ENTRY_NAME)) 494*5a923131SAndroid Build Coastguard Worker care_map_fp.flush() 495*5a923131SAndroid Build Coastguard Worker dut.adb(["push", care_map_fp.name, 496*5a923131SAndroid Build Coastguard Worker "/data/ota_package/" + CARE_MAP_ENTRY_NAME]) 497*5a923131SAndroid Build Coastguard Worker 498*5a923131SAndroid Build Coastguard Worker if args.file: 499*5a923131SAndroid Build Coastguard Worker # Update via pushing a file to /data. 500*5a923131SAndroid Build Coastguard Worker device_ota_file = os.path.join(OTA_PACKAGE_PATH, 'debug.zip') 501*5a923131SAndroid Build Coastguard Worker payload_url = 'file://' + device_ota_file 502*5a923131SAndroid Build Coastguard Worker if not args.no_push: 503*5a923131SAndroid Build Coastguard Worker data_local_tmp_file = '/data/local/tmp/debug.zip' 504*5a923131SAndroid Build Coastguard Worker cmds.append(['push', args.otafile, data_local_tmp_file]) 505*5a923131SAndroid Build Coastguard Worker cmds.append(['shell', 'su', '0', 'mv', data_local_tmp_file, 506*5a923131SAndroid Build Coastguard Worker device_ota_file]) 507*5a923131SAndroid Build Coastguard Worker cmds.append(['shell', 'su', '0', 'chcon', 508*5a923131SAndroid Build Coastguard Worker 'u:object_r:ota_package_file:s0', device_ota_file]) 509*5a923131SAndroid Build Coastguard Worker cmds.append(['shell', 'su', '0', 'chown', 'system:cache', device_ota_file]) 510*5a923131SAndroid Build Coastguard Worker cmds.append(['shell', 'su', '0', 'chmod', '0660', device_ota_file]) 511*5a923131SAndroid Build Coastguard Worker else: 512*5a923131SAndroid Build Coastguard Worker # Update via sending the payload over the network with an "adb reverse" 513*5a923131SAndroid Build Coastguard Worker # command. 514*5a923131SAndroid Build Coastguard Worker payload_url = 'http://127.0.0.1:%d/payload' % DEVICE_PORT 515*5a923131SAndroid Build Coastguard Worker serving_range = (0, os.stat(args.otafile).st_size) 516*5a923131SAndroid Build Coastguard Worker server_thread = StartServer(args.otafile, serving_range, args.speed_limit) 517*5a923131SAndroid Build Coastguard Worker cmds.append( 518*5a923131SAndroid Build Coastguard Worker ['reverse', 'tcp:%d' % DEVICE_PORT, 'tcp:%d' % server_thread.port]) 519*5a923131SAndroid Build Coastguard Worker finalize_cmds.append(['reverse', '--remove', 'tcp:%d' % DEVICE_PORT]) 520*5a923131SAndroid Build Coastguard Worker 521*5a923131SAndroid Build Coastguard Worker if args.public_key: 522*5a923131SAndroid Build Coastguard Worker payload_key_dir = os.path.dirname(PAYLOAD_KEY_PATH) 523*5a923131SAndroid Build Coastguard Worker cmds.append( 524*5a923131SAndroid Build Coastguard Worker ['shell', 'su', '0', 'mount', '-t', 'tmpfs', 'tmpfs', payload_key_dir]) 525*5a923131SAndroid Build Coastguard Worker # Allow adb push to payload_key_dir 526*5a923131SAndroid Build Coastguard Worker cmds.append(['shell', 'su', '0', 'chcon', 'u:object_r:shell_data_file:s0', 527*5a923131SAndroid Build Coastguard Worker payload_key_dir]) 528*5a923131SAndroid Build Coastguard Worker cmds.append(['push', args.public_key, PAYLOAD_KEY_PATH]) 529*5a923131SAndroid Build Coastguard Worker # Allow update_engine to read it. 530*5a923131SAndroid Build Coastguard Worker cmds.append(['shell', 'su', '0', 'chcon', '-R', 'u:object_r:system_file:s0', 531*5a923131SAndroid Build Coastguard Worker payload_key_dir]) 532*5a923131SAndroid Build Coastguard Worker finalize_cmds.append(['shell', 'su', '0', 'umount', payload_key_dir]) 533*5a923131SAndroid Build Coastguard Worker 534*5a923131SAndroid Build Coastguard Worker try: 535*5a923131SAndroid Build Coastguard Worker # The main update command using the configured payload_url. 536*5a923131SAndroid Build Coastguard Worker update_cmd = AndroidUpdateCommand(args.otafile, args.secondary, 537*5a923131SAndroid Build Coastguard Worker payload_url, args.extra_headers) 538*5a923131SAndroid Build Coastguard Worker cmds.append(['shell', 'su', '0'] + update_cmd) 539*5a923131SAndroid Build Coastguard Worker 540*5a923131SAndroid Build Coastguard Worker for cmd in cmds: 541*5a923131SAndroid Build Coastguard Worker dut.adb(cmd) 542*5a923131SAndroid Build Coastguard Worker except KeyboardInterrupt: 543*5a923131SAndroid Build Coastguard Worker dut.adb(cancel_cmd) 544*5a923131SAndroid Build Coastguard Worker finally: 545*5a923131SAndroid Build Coastguard Worker if server_thread: 546*5a923131SAndroid Build Coastguard Worker server_thread.StopServer() 547*5a923131SAndroid Build Coastguard Worker for cmd in finalize_cmds: 548*5a923131SAndroid Build Coastguard Worker dut.adb(cmd, 5) 549*5a923131SAndroid Build Coastguard Worker 550*5a923131SAndroid Build Coastguard Worker logging.info('Update took %.3f seconds', (time.perf_counter() - start_time)) 551*5a923131SAndroid Build Coastguard Worker return 0 552*5a923131SAndroid Build Coastguard Worker 553*5a923131SAndroid Build Coastguard Worker 554*5a923131SAndroid Build Coastguard Workerif __name__ == '__main__': 555*5a923131SAndroid Build Coastguard Worker sys.exit(main()) 556