blob: 950ff3d0636a7e64c54d6ff2813f4c0cd18992ae [file] [log] [blame]
Yifan Hong0c715502021-04-19 13:48:21 -07001#!/usr/bin/env python3
Alex Deymo6751bbe2017-03-21 11:20:02 -07002#
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
Kelvin Zhang4b883ea2020-10-08 13:26:44 -040020from __future__ import print_function
Andrew Lassalle165843c2019-11-05 13:30:34 -080021from __future__ import absolute_import
22
Alex Deymo6751bbe2017-03-21 11:20:02 -070023import argparse
Kelvin Zhangaba70ab2020-08-04 10:32:59 -040024import binascii
Sen Jiang3b15b592017-09-26 18:21:04 -070025import hashlib
Alex Deymo6751bbe2017-03-21 11:20:02 -070026import logging
27import os
28import socket
29import subprocess
30import sys
Kelvin Zhangaba70ab2020-08-04 10:32:59 -040031import struct
Kelvin Zhang51aad992021-02-19 14:46:28 -050032import tempfile
Nikita Ioffe3a327e62021-06-30 16:05:09 +010033import time
Alex Deymo6751bbe2017-03-21 11:20:02 -070034import threading
Sen Jiang144f9f82017-09-26 15:49:45 -070035import xml.etree.ElementTree
Alex Deymo6751bbe2017-03-21 11:20:02 -070036import zipfile
37
Andrew Lassalle165843c2019-11-05 13:30:34 -080038from six.moves import BaseHTTPServer
39
Sen Jianga1784b72017-08-09 17:42:36 -070040import update_payload.payload
41
Alex Deymo6751bbe2017-03-21 11:20:02 -070042
43# The path used to store the OTA package when applying the package from a file.
44OTA_PACKAGE_PATH = '/data/ota_package'
45
Sen Jianga1784b72017-08-09 17:42:36 -070046# The path to the payload public key on the device.
47PAYLOAD_KEY_PATH = '/etc/update_engine/update-payload-key.pub.pem'
48
49# The port on the device that update_engine should connect to.
50DEVICE_PORT = 1234
Alex Deymo6751bbe2017-03-21 11:20:02 -070051
Andrew Lassalle165843c2019-11-05 13:30:34 -080052
Alex Deymo6751bbe2017-03-21 11:20:02 -070053def CopyFileObjLength(fsrc, fdst, buffer_size=128 * 1024, copy_length=None):
54 """Copy from a file object to another.
55
56 This function is similar to shutil.copyfileobj except that it allows to copy
57 less than the full source file.
58
59 Args:
60 fsrc: source file object where to read from.
61 fdst: destination file object where to write to.
62 buffer_size: size of the copy buffer in memory.
63 copy_length: maximum number of bytes to copy, or None to copy everything.
64
65 Returns:
66 the number of bytes copied.
67 """
68 copied = 0
69 while True:
70 chunk_size = buffer_size
71 if copy_length is not None:
72 chunk_size = min(chunk_size, copy_length - copied)
73 if not chunk_size:
74 break
75 buf = fsrc.read(chunk_size)
76 if not buf:
77 break
78 fdst.write(buf)
79 copied += len(buf)
80 return copied
81
82
83class AndroidOTAPackage(object):
84 """Android update payload using the .zip format.
85
86 Android OTA packages traditionally used a .zip file to store the payload. When
87 applying A/B updates over the network, a payload binary is stored RAW inside
88 this .zip file which is used by update_engine to apply the payload. To do
89 this, an offset and size inside the .zip file are provided.
90 """
91
92 # Android OTA package file paths.
93 OTA_PAYLOAD_BIN = 'payload.bin'
94 OTA_PAYLOAD_PROPERTIES_TXT = 'payload_properties.txt'
Tianjie Xu3f9be772019-11-02 18:31:50 -070095 SECONDARY_OTA_PAYLOAD_BIN = 'secondary/payload.bin'
96 SECONDARY_OTA_PAYLOAD_PROPERTIES_TXT = 'secondary/payload_properties.txt'
Kelvin Zhangaba70ab2020-08-04 10:32:59 -040097 PAYLOAD_MAGIC_HEADER = b'CrAU'
Alex Deymo6751bbe2017-03-21 11:20:02 -070098
Tianjie Xu3f9be772019-11-02 18:31:50 -070099 def __init__(self, otafilename, secondary_payload=False):
Alex Deymo6751bbe2017-03-21 11:20:02 -0700100 self.otafilename = otafilename
101
102 otazip = zipfile.ZipFile(otafilename, 'r')
Tianjie Xu3f9be772019-11-02 18:31:50 -0700103 payload_entry = (self.SECONDARY_OTA_PAYLOAD_BIN if secondary_payload else
104 self.OTA_PAYLOAD_BIN)
105 payload_info = otazip.getinfo(payload_entry)
Kelvin Zhangaba70ab2020-08-04 10:32:59 -0400106
107 if payload_info.compress_type != 0:
108 logging.error(
Kelvin Zhang07676f52020-12-01 10:45:09 -0500109 "Expected payload to be uncompressed, got compression method %d",
Kelvin Zhangaba70ab2020-08-04 10:32:59 -0400110 payload_info.compress_type)
111 # Don't use len(payload_info.extra). Because that returns size of extra
112 # fields in central directory. We need to look at local file directory,
113 # as these two might have different sizes.
114 with open(otafilename, "rb") as fp:
115 fp.seek(payload_info.header_offset)
116 data = fp.read(zipfile.sizeFileHeader)
117 fheader = struct.unpack(zipfile.structFileHeader, data)
118 # Last two fields of local file header are filename length and
119 # extra length
120 filename_len = fheader[-2]
121 extra_len = fheader[-1]
122 self.offset = payload_info.header_offset
123 self.offset += zipfile.sizeFileHeader
124 self.offset += filename_len + extra_len
125 self.size = payload_info.file_size
126 fp.seek(self.offset)
127 payload_header = fp.read(4)
128 if payload_header != self.PAYLOAD_MAGIC_HEADER:
129 logging.warning(
Kelvin Zhang07676f52020-12-01 10:45:09 -0500130 "Invalid header, expected %s, got %s."
Kelvin Zhangaba70ab2020-08-04 10:32:59 -0400131 "Either the offset is not correct, or payload is corrupted",
132 binascii.hexlify(self.PAYLOAD_MAGIC_HEADER),
Kelvin Zhang07676f52020-12-01 10:45:09 -0500133 binascii.hexlify(payload_header))
Tianjie Xu3f9be772019-11-02 18:31:50 -0700134
135 property_entry = (self.SECONDARY_OTA_PAYLOAD_PROPERTIES_TXT if
136 secondary_payload else self.OTA_PAYLOAD_PROPERTIES_TXT)
137 self.properties = otazip.read(property_entry)
Alex Deymo6751bbe2017-03-21 11:20:02 -0700138
139
140class UpdateHandler(BaseHTTPServer.BaseHTTPRequestHandler):
141 """A HTTPServer that supports single-range requests.
142
143 Attributes:
144 serving_payload: path to the only payload file we are serving.
Sen Jiang3b15b592017-09-26 18:21:04 -0700145 serving_range: the start offset and size tuple of the payload.
Alex Deymo6751bbe2017-03-21 11:20:02 -0700146 """
147
148 @staticmethod
Sen Jiang10485592017-08-15 18:20:24 -0700149 def _parse_range(range_str, file_size):
Alex Deymo6751bbe2017-03-21 11:20:02 -0700150 """Parse an HTTP range string.
151
152 Args:
153 range_str: HTTP Range header in the request, not including "Header:".
154 file_size: total size of the serving file.
155
156 Returns:
157 A tuple (start_range, end_range) with the range of bytes requested.
158 """
159 start_range = 0
160 end_range = file_size
161
162 if range_str:
163 range_str = range_str.split('=', 1)[1]
164 s, e = range_str.split('-', 1)
165 if s:
166 start_range = int(s)
167 if e:
168 end_range = int(e) + 1
169 elif e:
170 if int(e) < file_size:
171 start_range = file_size - int(e)
172 return start_range, end_range
173
Alex Deymo6751bbe2017-03-21 11:20:02 -0700174 def do_GET(self): # pylint: disable=invalid-name
175 """Reply with the requested payload file."""
176 if self.path != '/payload':
177 self.send_error(404, 'Unknown request')
178 return
179
180 if not self.serving_payload:
181 self.send_error(500, 'No serving payload set')
182 return
183
184 try:
185 f = open(self.serving_payload, 'rb')
186 except IOError:
187 self.send_error(404, 'File not found')
188 return
189 # Handle the range request.
190 if 'Range' in self.headers:
191 self.send_response(206)
192 else:
193 self.send_response(200)
194
Sen Jiang3b15b592017-09-26 18:21:04 -0700195 serving_start, serving_size = self.serving_range
Sen Jiang10485592017-08-15 18:20:24 -0700196 start_range, end_range = self._parse_range(self.headers.get('range'),
Sen Jiang3b15b592017-09-26 18:21:04 -0700197 serving_size)
Alex Deymo6751bbe2017-03-21 11:20:02 -0700198 logging.info('Serving request for %s from %s [%d, %d) length: %d',
Sen Jiang3b15b592017-09-26 18:21:04 -0700199 self.path, self.serving_payload, serving_start + start_range,
200 serving_start + end_range, end_range - start_range)
Alex Deymo6751bbe2017-03-21 11:20:02 -0700201
202 self.send_header('Accept-Ranges', 'bytes')
203 self.send_header('Content-Range',
204 'bytes ' + str(start_range) + '-' + str(end_range - 1) +
205 '/' + str(end_range - start_range))
206 self.send_header('Content-Length', end_range - start_range)
207
Sen Jiang3b15b592017-09-26 18:21:04 -0700208 stat = os.fstat(f.fileno())
Alex Deymo6751bbe2017-03-21 11:20:02 -0700209 self.send_header('Last-Modified', self.date_time_string(stat.st_mtime))
210 self.send_header('Content-type', 'application/octet-stream')
211 self.end_headers()
212
Sen Jiang3b15b592017-09-26 18:21:04 -0700213 f.seek(serving_start + start_range)
Alex Deymo6751bbe2017-03-21 11:20:02 -0700214 CopyFileObjLength(f, self.wfile, copy_length=end_range - start_range)
215
Sen Jianga1784b72017-08-09 17:42:36 -0700216 def do_POST(self): # pylint: disable=invalid-name
217 """Reply with the omaha response xml."""
218 if self.path != '/update':
219 self.send_error(404, 'Unknown request')
220 return
221
222 if not self.serving_payload:
223 self.send_error(500, 'No serving payload set')
224 return
225
226 try:
227 f = open(self.serving_payload, 'rb')
228 except IOError:
229 self.send_error(404, 'File not found')
230 return
231
Sen Jiang144f9f82017-09-26 15:49:45 -0700232 content_length = int(self.headers.getheader('Content-Length'))
233 request_xml = self.rfile.read(content_length)
234 xml_root = xml.etree.ElementTree.fromstring(request_xml)
235 appid = None
236 for app in xml_root.iter('app'):
237 if 'appid' in app.attrib:
238 appid = app.attrib['appid']
239 break
240 if not appid:
241 self.send_error(400, 'No appid in Omaha request')
242 return
243
Sen Jianga1784b72017-08-09 17:42:36 -0700244 self.send_response(200)
245 self.send_header("Content-type", "text/xml")
246 self.end_headers()
247
Sen Jiang3b15b592017-09-26 18:21:04 -0700248 serving_start, serving_size = self.serving_range
249 sha256 = hashlib.sha256()
250 f.seek(serving_start)
251 bytes_to_hash = serving_size
252 while bytes_to_hash:
253 buf = f.read(min(bytes_to_hash, 1024 * 1024))
254 if not buf:
255 self.send_error(500, 'Payload too small')
256 return
257 sha256.update(buf)
258 bytes_to_hash -= len(buf)
259
260 payload = update_payload.Payload(f, payload_file_offset=serving_start)
Sen Jianga1784b72017-08-09 17:42:36 -0700261 payload.Init()
262
Sen Jiang144f9f82017-09-26 15:49:45 -0700263 response_xml = '''
Sen Jianga1784b72017-08-09 17:42:36 -0700264 <?xml version="1.0" encoding="UTF-8"?>
265 <response protocol="3.0">
Sen Jiang144f9f82017-09-26 15:49:45 -0700266 <app appid="{appid}">
Sen Jianga1784b72017-08-09 17:42:36 -0700267 <updatecheck status="ok">
268 <urls>
Sen Jiang144f9f82017-09-26 15:49:45 -0700269 <url codebase="http://127.0.0.1:{port}/"/>
Sen Jianga1784b72017-08-09 17:42:36 -0700270 </urls>
271 <manifest version="0.0.0.1">
272 <actions>
273 <action event="install" run="payload"/>
Sen Jiang144f9f82017-09-26 15:49:45 -0700274 <action event="postinstall" MetadataSize="{metadata_size}"/>
Sen Jianga1784b72017-08-09 17:42:36 -0700275 </actions>
276 <packages>
Sen Jiang144f9f82017-09-26 15:49:45 -0700277 <package hash_sha256="{payload_hash}" name="payload" size="{payload_size}"/>
Sen Jianga1784b72017-08-09 17:42:36 -0700278 </packages>
279 </manifest>
280 </updatecheck>
281 </app>
282 </response>
Sen Jiang144f9f82017-09-26 15:49:45 -0700283 '''.format(appid=appid, port=DEVICE_PORT,
Sen Jiang3b15b592017-09-26 18:21:04 -0700284 metadata_size=payload.metadata_size,
285 payload_hash=sha256.hexdigest(),
286 payload_size=serving_size)
Sen Jiang144f9f82017-09-26 15:49:45 -0700287 self.wfile.write(response_xml.strip())
Sen Jianga1784b72017-08-09 17:42:36 -0700288 return
289
290
Alex Deymo6751bbe2017-03-21 11:20:02 -0700291class ServerThread(threading.Thread):
292 """A thread for serving HTTP requests."""
293
Sen Jiang3b15b592017-09-26 18:21:04 -0700294 def __init__(self, ota_filename, serving_range):
Alex Deymo6751bbe2017-03-21 11:20:02 -0700295 threading.Thread.__init__(self)
Sen Jiang3b15b592017-09-26 18:21:04 -0700296 # serving_payload and serving_range are class attributes and the
297 # UpdateHandler class is instantiated with every request.
Alex Deymo6751bbe2017-03-21 11:20:02 -0700298 UpdateHandler.serving_payload = ota_filename
Sen Jiang3b15b592017-09-26 18:21:04 -0700299 UpdateHandler.serving_range = serving_range
Alex Deymo6751bbe2017-03-21 11:20:02 -0700300 self._httpd = BaseHTTPServer.HTTPServer(('127.0.0.1', 0), UpdateHandler)
301 self.port = self._httpd.server_port
302
303 def run(self):
304 try:
305 self._httpd.serve_forever()
306 except (KeyboardInterrupt, socket.error):
307 pass
308 logging.info('Server Terminated')
309
310 def StopServer(self):
Kelvin Zhang4b883ea2020-10-08 13:26:44 -0400311 self._httpd.shutdown()
Alex Deymo6751bbe2017-03-21 11:20:02 -0700312 self._httpd.socket.close()
313
314
Sen Jiang3b15b592017-09-26 18:21:04 -0700315def StartServer(ota_filename, serving_range):
316 t = ServerThread(ota_filename, serving_range)
Alex Deymo6751bbe2017-03-21 11:20:02 -0700317 t.start()
318 return t
319
320
Tianjie Xu3f9be772019-11-02 18:31:50 -0700321def AndroidUpdateCommand(ota_filename, secondary, payload_url, extra_headers):
Alex Deymo6751bbe2017-03-21 11:20:02 -0700322 """Return the command to run to start the update in the Android device."""
Tianjie Xu3f9be772019-11-02 18:31:50 -0700323 ota = AndroidOTAPackage(ota_filename, secondary)
Alex Deymo6751bbe2017-03-21 11:20:02 -0700324 headers = ota.properties
Kelvin Zhang4b883ea2020-10-08 13:26:44 -0400325 headers += b'USER_AGENT=Dalvik (something, something)\n'
326 headers += b'NETWORK_ID=0\n'
327 headers += extra_headers.encode()
Alex Deymo6751bbe2017-03-21 11:20:02 -0700328
329 return ['update_engine_client', '--update', '--follow',
330 '--payload=%s' % payload_url, '--offset=%d' % ota.offset,
Kelvin Zhang4b883ea2020-10-08 13:26:44 -0400331 '--size=%d' % ota.size, '--headers="%s"' % headers.decode()]
Alex Deymo6751bbe2017-03-21 11:20:02 -0700332
333
Sen Jianga1784b72017-08-09 17:42:36 -0700334def OmahaUpdateCommand(omaha_url):
335 """Return the command to run to start the update in a device using Omaha."""
336 return ['update_engine_client', '--update', '--follow',
337 '--omaha_url=%s' % omaha_url]
338
339
Alex Deymo6751bbe2017-03-21 11:20:02 -0700340class AdbHost(object):
341 """Represents a device connected via ADB."""
342
343 def __init__(self, device_serial=None):
344 """Construct an instance.
345
346 Args:
347 device_serial: options string serial number of attached device.
348 """
349 self._device_serial = device_serial
350 self._command_prefix = ['adb']
351 if self._device_serial:
352 self._command_prefix += ['-s', self._device_serial]
353
Kelvin Zhang3a188952021-04-13 12:44:45 -0400354 def adb(self, command, timeout_seconds: float = None):
Alex Deymo6751bbe2017-03-21 11:20:02 -0700355 """Run an ADB command like "adb push".
356
357 Args:
358 command: list of strings containing command and arguments to run
359
360 Returns:
361 the program's return code.
362
363 Raises:
364 subprocess.CalledProcessError on command exit != 0.
365 """
366 command = self._command_prefix + command
367 logging.info('Running: %s', ' '.join(str(x) for x in command))
368 p = subprocess.Popen(command, universal_newlines=True)
Kelvin Zhang3a188952021-04-13 12:44:45 -0400369 p.wait(timeout_seconds)
Alex Deymo6751bbe2017-03-21 11:20:02 -0700370 return p.returncode
371
Sen Jianga1784b72017-08-09 17:42:36 -0700372 def adb_output(self, command):
373 """Run an ADB command like "adb push" and return the output.
374
375 Args:
376 command: list of strings containing command and arguments to run
377
378 Returns:
379 the program's output as a string.
380
381 Raises:
382 subprocess.CalledProcessError on command exit != 0.
383 """
384 command = self._command_prefix + command
385 logging.info('Running: %s', ' '.join(str(x) for x in command))
386 return subprocess.check_output(command, universal_newlines=True)
387
Alex Deymo6751bbe2017-03-21 11:20:02 -0700388
Kelvin Zhang63b39112021-03-05 12:31:38 -0500389def PushMetadata(dut, otafile, metadata_path):
390 payload = update_payload.Payload(otafile)
391 payload.Init()
392 with tempfile.TemporaryDirectory() as tmpdir:
393 with zipfile.ZipFile(otafile, "r") as zfp:
394 extracted_path = os.path.join(tmpdir, "payload.bin")
395 with zfp.open("payload.bin") as payload_fp, \
396 open(extracted_path, "wb") as output_fp:
397 # Only extract the first |data_offset| bytes from the payload.
398 # This is because allocateSpaceForPayload only needs to see
399 # the manifest, not the entire payload.
400 # Extracting the entire payload works, but is slow for full
401 # OTA.
402 output_fp.write(payload_fp.read(payload.data_offset))
403
404 return dut.adb([
405 "push",
406 extracted_path,
407 metadata_path
408 ]) == 0
409
410
Alex Deymo6751bbe2017-03-21 11:20:02 -0700411def main():
412 parser = argparse.ArgumentParser(description='Android A/B OTA helper.')
Sen Jiang3b15b592017-09-26 18:21:04 -0700413 parser.add_argument('otafile', metavar='PAYLOAD', type=str,
414 help='the OTA package file (a .zip file) or raw payload \
415 if device uses Omaha.')
Alex Deymo6751bbe2017-03-21 11:20:02 -0700416 parser.add_argument('--file', action='store_true',
417 help='Push the file to the device before updating.')
418 parser.add_argument('--no-push', action='store_true',
419 help='Skip the "push" command when using --file')
420 parser.add_argument('-s', type=str, default='', metavar='DEVICE',
421 help='The specific device to use.')
422 parser.add_argument('--no-verbose', action='store_true',
423 help='Less verbose output')
Sen Jianga1784b72017-08-09 17:42:36 -0700424 parser.add_argument('--public-key', type=str, default='',
425 help='Override the public key used to verify payload.')
Sen Jiang6fbfd7d2017-10-31 16:16:56 -0700426 parser.add_argument('--extra-headers', type=str, default='',
427 help='Extra headers to pass to the device.')
Tianjie Xu3f9be772019-11-02 18:31:50 -0700428 parser.add_argument('--secondary', action='store_true',
429 help='Update with the secondary payload in the package.')
Kelvin Zhang8212f532020-11-13 16:00:00 -0500430 parser.add_argument('--no-slot-switch', action='store_true',
431 help='Do not perform slot switch after the update.')
Kelvin Zhangbec0f072021-03-31 16:09:00 -0400432 parser.add_argument('--no-postinstall', action='store_true',
433 help='Do not execute postinstall scripts after the update.')
Kelvin Zhangffd21442021-04-14 09:09:41 -0400434 parser.add_argument('--allocate-only', action='store_true',
Kelvin Zhang51aad992021-02-19 14:46:28 -0500435 help='Allocate space for this OTA, instead of actually \
436 applying the OTA.')
Kelvin Zhangffd21442021-04-14 09:09:41 -0400437 parser.add_argument('--verify-only', action='store_true',
Kelvin Zhang63b39112021-03-05 12:31:38 -0500438 help='Verify metadata then exit, instead of applying the OTA.')
Kelvin Zhangffd21442021-04-14 09:09:41 -0400439 parser.add_argument('--no-care-map', action='store_true',
Kelvin Zhang5bd46222021-03-02 12:36:14 -0500440 help='Do not push care_map.pb to device.')
Kelvin Zhangc56afa32021-08-13 12:32:31 -0700441 parser.add_argument('--perform-slot-switch', action='store_true',
442 help='Perform slot switch for this OTA package')
443 parser.add_argument('--perform-reset-slot-switch', action='store_true',
444 help='Perform reset slot switch for this OTA package')
Kelvin Zhang2451a302022-03-23 19:18:35 -0700445 parser.add_argument('--wipe-user-data', action='store_true',
446 help='Wipe userdata after installing OTA')
Daniel Zheng730ae9b2022-08-25 22:37:22 +0000447 parser.add_argument('--disable-vabc', action='store_true',
448 help='Disable vabc during OTA')
Alex Deymo6751bbe2017-03-21 11:20:02 -0700449 args = parser.parse_args()
450 logging.basicConfig(
451 level=logging.WARNING if args.no_verbose else logging.INFO)
452
Nikita Ioffe3a327e62021-06-30 16:05:09 +0100453 start_time = time.perf_counter()
454
Alex Deymo6751bbe2017-03-21 11:20:02 -0700455 dut = AdbHost(args.s)
456
457 server_thread = None
458 # List of commands to execute on exit.
459 finalize_cmds = []
460 # Commands to execute when canceling an update.
461 cancel_cmd = ['shell', 'su', '0', 'update_engine_client', '--cancel']
462 # List of commands to perform the update.
463 cmds = []
464
Sen Jianga1784b72017-08-09 17:42:36 -0700465 help_cmd = ['shell', 'su', '0', 'update_engine_client', '--help']
466 use_omaha = 'omaha' in dut.adb_output(help_cmd)
467
Kelvin Zhang63b39112021-03-05 12:31:38 -0500468 metadata_path = "/data/ota_package/metadata"
Kelvin Zhang51aad992021-02-19 14:46:28 -0500469 if args.allocate_only:
Kelvin Zhang63b39112021-03-05 12:31:38 -0500470 if PushMetadata(dut, args.otafile, metadata_path):
471 dut.adb([
472 "shell", "update_engine_client", "--allocate",
473 "--metadata={}".format(metadata_path)])
474 # Return 0, as we are executing ADB commands here, no work needed after
475 # this point
476 return 0
477 if args.verify_only:
478 if PushMetadata(dut, args.otafile, metadata_path):
479 dut.adb([
480 "shell", "update_engine_client", "--verify",
481 "--metadata={}".format(metadata_path)])
Kelvin Zhang51aad992021-02-19 14:46:28 -0500482 # Return 0, as we are executing ADB commands here, no work needed after
483 # this point
484 return 0
Kelvin Zhangc56afa32021-08-13 12:32:31 -0700485 if args.perform_slot_switch:
486 assert PushMetadata(dut, args.otafile, metadata_path)
487 dut.adb(["shell", "update_engine_client",
488 "--switch_slot=true", "--metadata={}".format(metadata_path), "--follow"])
489 return 0
490 if args.perform_reset_slot_switch:
491 assert PushMetadata(dut, args.otafile, metadata_path)
492 dut.adb(["shell", "update_engine_client",
493 "--switch_slot=false", "--metadata={}".format(metadata_path)])
494 return 0
Kelvin Zhang51aad992021-02-19 14:46:28 -0500495
Kelvin Zhang8212f532020-11-13 16:00:00 -0500496 if args.no_slot_switch:
497 args.extra_headers += "\nSWITCH_SLOT_ON_REBOOT=0"
Kelvin Zhangbec0f072021-03-31 16:09:00 -0400498 if args.no_postinstall:
499 args.extra_headers += "\nRUN_POST_INSTALL=0"
Kelvin Zhang2451a302022-03-23 19:18:35 -0700500 if args.wipe_user_data:
501 args.extra_headers += "\nPOWERWASH=1"
Daniel Zheng730ae9b2022-08-25 22:37:22 +0000502 if args.disable_vabc:
503 args.extra_headers += "\nDISABLE_VABC=1"
Kelvin Zhang8212f532020-11-13 16:00:00 -0500504
Kelvin Zhang5bd46222021-03-02 12:36:14 -0500505 with zipfile.ZipFile(args.otafile) as zfp:
506 CARE_MAP_ENTRY_NAME = "care_map.pb"
507 if CARE_MAP_ENTRY_NAME in zfp.namelist() and not args.no_care_map:
508 # Need root permission to push to /data
509 dut.adb(["root"])
Kelvin Zhang472d5612021-03-05 12:32:19 -0500510 with tempfile.NamedTemporaryFile() as care_map_fp:
Kelvin Zhang5bd46222021-03-02 12:36:14 -0500511 care_map_fp.write(zfp.read(CARE_MAP_ENTRY_NAME))
512 care_map_fp.flush()
513 dut.adb(["push", care_map_fp.name,
Kelvin Zhangffd21442021-04-14 09:09:41 -0400514 "/data/ota_package/" + CARE_MAP_ENTRY_NAME])
Kelvin Zhang5bd46222021-03-02 12:36:14 -0500515
Alex Deymo6751bbe2017-03-21 11:20:02 -0700516 if args.file:
517 # Update via pushing a file to /data.
518 device_ota_file = os.path.join(OTA_PACKAGE_PATH, 'debug.zip')
519 payload_url = 'file://' + device_ota_file
520 if not args.no_push:
Tao Baoabb45a52017-10-25 11:13:03 -0700521 data_local_tmp_file = '/data/local/tmp/debug.zip'
522 cmds.append(['push', args.otafile, data_local_tmp_file])
523 cmds.append(['shell', 'su', '0', 'mv', data_local_tmp_file,
524 device_ota_file])
525 cmds.append(['shell', 'su', '0', 'chcon',
526 'u:object_r:ota_package_file:s0', device_ota_file])
Alex Deymo6751bbe2017-03-21 11:20:02 -0700527 cmds.append(['shell', 'su', '0', 'chown', 'system:cache', device_ota_file])
528 cmds.append(['shell', 'su', '0', 'chmod', '0660', device_ota_file])
529 else:
530 # Update via sending the payload over the network with an "adb reverse"
531 # command.
Sen Jianga1784b72017-08-09 17:42:36 -0700532 payload_url = 'http://127.0.0.1:%d/payload' % DEVICE_PORT
Sen Jiang3b15b592017-09-26 18:21:04 -0700533 if use_omaha and zipfile.is_zipfile(args.otafile):
Tianjie Xu3f9be772019-11-02 18:31:50 -0700534 ota = AndroidOTAPackage(args.otafile, args.secondary)
Sen Jiang3b15b592017-09-26 18:21:04 -0700535 serving_range = (ota.offset, ota.size)
536 else:
537 serving_range = (0, os.stat(args.otafile).st_size)
538 server_thread = StartServer(args.otafile, serving_range)
Alex Deymo6751bbe2017-03-21 11:20:02 -0700539 cmds.append(
Sen Jianga1784b72017-08-09 17:42:36 -0700540 ['reverse', 'tcp:%d' % DEVICE_PORT, 'tcp:%d' % server_thread.port])
541 finalize_cmds.append(['reverse', '--remove', 'tcp:%d' % DEVICE_PORT])
542
543 if args.public_key:
544 payload_key_dir = os.path.dirname(PAYLOAD_KEY_PATH)
545 cmds.append(
546 ['shell', 'su', '0', 'mount', '-t', 'tmpfs', 'tmpfs', payload_key_dir])
547 # Allow adb push to payload_key_dir
548 cmds.append(['shell', 'su', '0', 'chcon', 'u:object_r:shell_data_file:s0',
549 payload_key_dir])
550 cmds.append(['push', args.public_key, PAYLOAD_KEY_PATH])
551 # Allow update_engine to read it.
552 cmds.append(['shell', 'su', '0', 'chcon', '-R', 'u:object_r:system_file:s0',
553 payload_key_dir])
554 finalize_cmds.append(['shell', 'su', '0', 'umount', payload_key_dir])
Alex Deymo6751bbe2017-03-21 11:20:02 -0700555
556 try:
557 # The main update command using the configured payload_url.
Sen Jianga1784b72017-08-09 17:42:36 -0700558 if use_omaha:
559 update_cmd = \
560 OmahaUpdateCommand('http://127.0.0.1:%d/update' % DEVICE_PORT)
561 else:
Tianjie Xu3f9be772019-11-02 18:31:50 -0700562 update_cmd = AndroidUpdateCommand(args.otafile, args.secondary,
563 payload_url, args.extra_headers)
Alex Deymo6751bbe2017-03-21 11:20:02 -0700564 cmds.append(['shell', 'su', '0'] + update_cmd)
565
566 for cmd in cmds:
567 dut.adb(cmd)
568 except KeyboardInterrupt:
569 dut.adb(cancel_cmd)
570 finally:
571 if server_thread:
572 server_thread.StopServer()
573 for cmd in finalize_cmds:
Kelvin Zhang3a188952021-04-13 12:44:45 -0400574 dut.adb(cmd, 5)
Alex Deymo6751bbe2017-03-21 11:20:02 -0700575
Nikita Ioffe3a327e62021-06-30 16:05:09 +0100576 logging.info('Update took %.3f seconds', (time.perf_counter() - start_time))
Alex Deymo6751bbe2017-03-21 11:20:02 -0700577 return 0
578
Andrew Lassalle165843c2019-11-05 13:30:34 -0800579
Alex Deymo6751bbe2017-03-21 11:20:02 -0700580if __name__ == '__main__':
581 sys.exit(main())