diff options
Diffstat (limited to 'scripts/update_device.py')
-rwxr-xr-x | scripts/update_device.py | 101 |
1 files changed, 87 insertions, 14 deletions
diff --git a/scripts/update_device.py b/scripts/update_device.py index 371a89a8..f672cda0 100755 --- a/scripts/update_device.py +++ b/scripts/update_device.py @@ -1,4 +1,4 @@ -#!/usr/bin/python2 +#!/usr/bin/env python3 # # Copyright (C) 2017 The Android Open Source Project # @@ -17,8 +17,10 @@ """Send an A/B update to an Android device over adb.""" +from __future__ import print_function +from __future__ import absolute_import + import argparse -import BaseHTTPServer import binascii import hashlib import logging @@ -27,10 +29,13 @@ import socket import subprocess import sys import struct +import tempfile import threading import xml.etree.ElementTree import zipfile +from six.moves import BaseHTTPServer + import update_payload.payload @@ -43,6 +48,7 @@ PAYLOAD_KEY_PATH = '/etc/update_engine/update-payload-key.pub.pem' # The port on the device that update_engine should connect to. DEVICE_PORT = 1234 + def CopyFileObjLength(fsrc, fdst, buffer_size=128 * 1024, copy_length=None): """Copy from a file object to another. @@ -99,7 +105,7 @@ class AndroidOTAPackage(object): if payload_info.compress_type != 0: logging.error( - "Expected layload to be uncompressed, got compression method %d", + "Expected payload to be uncompressed, got compression method %d", payload_info.compress_type) # Don't use len(payload_info.extra). Because that returns size of extra # fields in central directory. We need to look at local file directory, @@ -120,10 +126,10 @@ class AndroidOTAPackage(object): payload_header = fp.read(4) if payload_header != self.PAYLOAD_MAGIC_HEADER: logging.warning( - "Invalid header, expeted %s, got %s." + "Invalid header, expected %s, got %s." "Either the offset is not correct, or payload is corrupted", binascii.hexlify(self.PAYLOAD_MAGIC_HEADER), - payload_header) + binascii.hexlify(payload_header)) property_entry = (self.SECONDARY_OTA_PAYLOAD_PROPERTIES_TXT if secondary_payload else self.OTA_PAYLOAD_PROPERTIES_TXT) @@ -164,7 +170,6 @@ class UpdateHandler(BaseHTTPServer.BaseHTTPRequestHandler): start_range = file_size - int(e) return start_range, end_range - def do_GET(self): # pylint: disable=invalid-name """Reply with the requested payload file.""" if self.path != '/payload': @@ -207,7 +212,6 @@ class UpdateHandler(BaseHTTPServer.BaseHTTPRequestHandler): f.seek(serving_start + start_range) CopyFileObjLength(f, self.wfile, copy_length=end_range - start_range) - def do_POST(self): # pylint: disable=invalid-name """Reply with the omaha response xml.""" if self.path != '/update': @@ -303,6 +307,7 @@ class ServerThread(threading.Thread): logging.info('Server Terminated') def StopServer(self): + self._httpd.shutdown() self._httpd.socket.close() @@ -316,13 +321,13 @@ def AndroidUpdateCommand(ota_filename, secondary, payload_url, extra_headers): """Return the command to run to start the update in the Android device.""" ota = AndroidOTAPackage(ota_filename, secondary) headers = ota.properties - headers += 'USER_AGENT=Dalvik (something, something)\n' - headers += 'NETWORK_ID=0\n' - headers += extra_headers + headers += b'USER_AGENT=Dalvik (something, something)\n' + headers += b'NETWORK_ID=0\n' + headers += extra_headers.encode() return ['update_engine_client', '--update', '--follow', '--payload=%s' % payload_url, '--offset=%d' % ota.offset, - '--size=%d' % ota.size, '--headers="%s"' % headers] + '--size=%d' % ota.size, '--headers="%s"' % headers.decode()] def OmahaUpdateCommand(omaha_url): @@ -345,7 +350,7 @@ class AdbHost(object): if self._device_serial: self._command_prefix += ['-s', self._device_serial] - def adb(self, command): + def adb(self, command, timeout_seconds: float = None): """Run an ADB command like "adb push". Args: @@ -360,7 +365,7 @@ class AdbHost(object): command = self._command_prefix + command logging.info('Running: %s', ' '.join(str(x) for x in command)) p = subprocess.Popen(command, universal_newlines=True) - p.wait() + p.wait(timeout_seconds) return p.returncode def adb_output(self, command): @@ -380,6 +385,28 @@ class AdbHost(object): return subprocess.check_output(command, universal_newlines=True) +def PushMetadata(dut, otafile, metadata_path): + payload = update_payload.Payload(otafile) + payload.Init() + with tempfile.TemporaryDirectory() as tmpdir: + with zipfile.ZipFile(otafile, "r") as zfp: + extracted_path = os.path.join(tmpdir, "payload.bin") + with zfp.open("payload.bin") as payload_fp, \ + open(extracted_path, "wb") as output_fp: + # Only extract the first |data_offset| bytes from the payload. + # This is because allocateSpaceForPayload only needs to see + # the manifest, not the entire payload. + # Extracting the entire payload works, but is slow for full + # OTA. + output_fp.write(payload_fp.read(payload.data_offset)) + + return dut.adb([ + "push", + extracted_path, + metadata_path + ]) == 0 + + def main(): parser = argparse.ArgumentParser(description='Android A/B OTA helper.') parser.add_argument('otafile', metavar='PAYLOAD', type=str, @@ -399,6 +426,17 @@ def main(): help='Extra headers to pass to the device.') parser.add_argument('--secondary', action='store_true', help='Update with the secondary payload in the package.') + parser.add_argument('--no-slot-switch', action='store_true', + help='Do not perform slot switch after the update.') + parser.add_argument('--no-postinstall', action='store_true', + help='Do not execute postinstall scripts after the update.') + parser.add_argument('--allocate-only', action='store_true', + help='Allocate space for this OTA, instead of actually \ + applying the OTA.') + parser.add_argument('--verify-only', action='store_true', + help='Verify metadata then exit, instead of applying the OTA.') + parser.add_argument('--no-care-map', action='store_true', + help='Do not push care_map.pb to device.') args = parser.parse_args() logging.basicConfig( level=logging.WARNING if args.no_verbose else logging.INFO) @@ -416,6 +454,40 @@ def main(): help_cmd = ['shell', 'su', '0', 'update_engine_client', '--help'] use_omaha = 'omaha' in dut.adb_output(help_cmd) + metadata_path = "/data/ota_package/metadata" + if args.allocate_only: + if PushMetadata(dut, args.otafile, metadata_path): + dut.adb([ + "shell", "update_engine_client", "--allocate", + "--metadata={}".format(metadata_path)]) + # Return 0, as we are executing ADB commands here, no work needed after + # this point + return 0 + if args.verify_only: + if PushMetadata(dut, args.otafile, metadata_path): + dut.adb([ + "shell", "update_engine_client", "--verify", + "--metadata={}".format(metadata_path)]) + # Return 0, as we are executing ADB commands here, no work needed after + # this point + return 0 + + if args.no_slot_switch: + args.extra_headers += "\nSWITCH_SLOT_ON_REBOOT=0" + if args.no_postinstall: + args.extra_headers += "\nRUN_POST_INSTALL=0" + + with zipfile.ZipFile(args.otafile) as zfp: + CARE_MAP_ENTRY_NAME = "care_map.pb" + if CARE_MAP_ENTRY_NAME in zfp.namelist() and not args.no_care_map: + # Need root permission to push to /data + dut.adb(["root"]) + with tempfile.NamedTemporaryFile() as care_map_fp: + care_map_fp.write(zfp.read(CARE_MAP_ENTRY_NAME)) + care_map_fp.flush() + dut.adb(["push", care_map_fp.name, + "/data/ota_package/" + CARE_MAP_ENTRY_NAME]) + if args.file: # Update via pushing a file to /data. device_ota_file = os.path.join(OTA_PACKAGE_PATH, 'debug.zip') @@ -474,9 +546,10 @@ def main(): if server_thread: server_thread.StopServer() for cmd in finalize_cmds: - dut.adb(cmd) + dut.adb(cmd, 5) return 0 + if __name__ == '__main__': sys.exit(main()) |