blob: 26e6e1cbfd776c2623e65769d06b78bbe3486204 [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
Alex Deymo6751bbe2017-03-21 11:20:02 -070025import logging
26import os
Kelvin Zhang46a860c2022-09-28 19:42:39 -070027import re
Alex Deymo6751bbe2017-03-21 11:20:02 -070028import 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
35import zipfile
Kelvin Zhang9a2d9a32023-11-28 09:42:16 -080036import shutil
Alex Deymo6751bbe2017-03-21 11:20:02 -070037
Andrew Lassalle165843c2019-11-05 13:30:34 -080038from six.moves import BaseHTTPServer
39
Alex Deymo6751bbe2017-03-21 11:20:02 -070040
41# The path used to store the OTA package when applying the package from a file.
42OTA_PACKAGE_PATH = '/data/ota_package'
43
Sen Jianga1784b72017-08-09 17:42:36 -070044# The path to the payload public key on the device.
45PAYLOAD_KEY_PATH = '/etc/update_engine/update-payload-key.pub.pem'
46
47# The port on the device that update_engine should connect to.
48DEVICE_PORT = 1234
Alex Deymo6751bbe2017-03-21 11:20:02 -070049
Andrew Lassalle165843c2019-11-05 13:30:34 -080050
Kelvin Zhang46a860c2022-09-28 19:42:39 -070051def CopyFileObjLength(fsrc, fdst, buffer_size=128 * 1024, copy_length=None, speed_limit=None):
Alex Deymo6751bbe2017-03-21 11:20:02 -070052 """Copy from a file object to another.
53
54 This function is similar to shutil.copyfileobj except that it allows to copy
55 less than the full source file.
56
57 Args:
58 fsrc: source file object where to read from.
59 fdst: destination file object where to write to.
60 buffer_size: size of the copy buffer in memory.
61 copy_length: maximum number of bytes to copy, or None to copy everything.
Kelvin Zhang46a860c2022-09-28 19:42:39 -070062 speed_limit: upper limit for copying speed, in bytes per second.
Alex Deymo6751bbe2017-03-21 11:20:02 -070063
64 Returns:
65 the number of bytes copied.
66 """
Kelvin Zhang46a860c2022-09-28 19:42:39 -070067 # If buffer size significantly bigger than speed limit
68 # traffic would seem extremely spiky to the client.
69 if speed_limit:
70 print(f"Applying speed limit: {speed_limit}")
71 buffer_size = min(speed_limit//32, buffer_size)
72
73 start_time = time.time()
Alex Deymo6751bbe2017-03-21 11:20:02 -070074 copied = 0
75 while True:
76 chunk_size = buffer_size
77 if copy_length is not None:
78 chunk_size = min(chunk_size, copy_length - copied)
79 if not chunk_size:
80 break
81 buf = fsrc.read(chunk_size)
82 if not buf:
83 break
Kelvin Zhang46a860c2022-09-28 19:42:39 -070084 if speed_limit:
85 expected_duration = copied/speed_limit
86 actual_duration = time.time() - start_time
87 if actual_duration < expected_duration:
88 time.sleep(expected_duration-actual_duration)
Alex Deymo6751bbe2017-03-21 11:20:02 -070089 fdst.write(buf)
90 copied += len(buf)
91 return copied
92
93
94class AndroidOTAPackage(object):
95 """Android update payload using the .zip format.
96
97 Android OTA packages traditionally used a .zip file to store the payload. When
98 applying A/B updates over the network, a payload binary is stored RAW inside
99 this .zip file which is used by update_engine to apply the payload. To do
100 this, an offset and size inside the .zip file are provided.
101 """
102
103 # Android OTA package file paths.
104 OTA_PAYLOAD_BIN = 'payload.bin'
105 OTA_PAYLOAD_PROPERTIES_TXT = 'payload_properties.txt'
Tianjie Xu3f9be772019-11-02 18:31:50 -0700106 SECONDARY_OTA_PAYLOAD_BIN = 'secondary/payload.bin'
107 SECONDARY_OTA_PAYLOAD_PROPERTIES_TXT = 'secondary/payload_properties.txt'
Kelvin Zhangaba70ab2020-08-04 10:32:59 -0400108 PAYLOAD_MAGIC_HEADER = b'CrAU'
Alex Deymo6751bbe2017-03-21 11:20:02 -0700109
Tianjie Xu3f9be772019-11-02 18:31:50 -0700110 def __init__(self, otafilename, secondary_payload=False):
Alex Deymo6751bbe2017-03-21 11:20:02 -0700111 self.otafilename = otafilename
112
113 otazip = zipfile.ZipFile(otafilename, 'r')
Tianjie Xu3f9be772019-11-02 18:31:50 -0700114 payload_entry = (self.SECONDARY_OTA_PAYLOAD_BIN if secondary_payload else
115 self.OTA_PAYLOAD_BIN)
116 payload_info = otazip.getinfo(payload_entry)
Kelvin Zhangaba70ab2020-08-04 10:32:59 -0400117
118 if payload_info.compress_type != 0:
119 logging.error(
Kelvin Zhang07676f52020-12-01 10:45:09 -0500120 "Expected payload to be uncompressed, got compression method %d",
Kelvin Zhangaba70ab2020-08-04 10:32:59 -0400121 payload_info.compress_type)
122 # Don't use len(payload_info.extra). Because that returns size of extra
123 # fields in central directory. We need to look at local file directory,
124 # as these two might have different sizes.
125 with open(otafilename, "rb") as fp:
126 fp.seek(payload_info.header_offset)
127 data = fp.read(zipfile.sizeFileHeader)
128 fheader = struct.unpack(zipfile.structFileHeader, data)
129 # Last two fields of local file header are filename length and
130 # extra length
131 filename_len = fheader[-2]
132 extra_len = fheader[-1]
133 self.offset = payload_info.header_offset
134 self.offset += zipfile.sizeFileHeader
135 self.offset += filename_len + extra_len
136 self.size = payload_info.file_size
137 fp.seek(self.offset)
138 payload_header = fp.read(4)
139 if payload_header != self.PAYLOAD_MAGIC_HEADER:
140 logging.warning(
Kelvin Zhang07676f52020-12-01 10:45:09 -0500141 "Invalid header, expected %s, got %s."
Kelvin Zhangaba70ab2020-08-04 10:32:59 -0400142 "Either the offset is not correct, or payload is corrupted",
143 binascii.hexlify(self.PAYLOAD_MAGIC_HEADER),
Kelvin Zhang07676f52020-12-01 10:45:09 -0500144 binascii.hexlify(payload_header))
Tianjie Xu3f9be772019-11-02 18:31:50 -0700145
146 property_entry = (self.SECONDARY_OTA_PAYLOAD_PROPERTIES_TXT if
147 secondary_payload else self.OTA_PAYLOAD_PROPERTIES_TXT)
148 self.properties = otazip.read(property_entry)
Alex Deymo6751bbe2017-03-21 11:20:02 -0700149
150
151class UpdateHandler(BaseHTTPServer.BaseHTTPRequestHandler):
152 """A HTTPServer that supports single-range requests.
153
154 Attributes:
155 serving_payload: path to the only payload file we are serving.
Sen Jiang3b15b592017-09-26 18:21:04 -0700156 serving_range: the start offset and size tuple of the payload.
Alex Deymo6751bbe2017-03-21 11:20:02 -0700157 """
158
159 @staticmethod
Sen Jiang10485592017-08-15 18:20:24 -0700160 def _parse_range(range_str, file_size):
Alex Deymo6751bbe2017-03-21 11:20:02 -0700161 """Parse an HTTP range string.
162
163 Args:
164 range_str: HTTP Range header in the request, not including "Header:".
165 file_size: total size of the serving file.
166
167 Returns:
168 A tuple (start_range, end_range) with the range of bytes requested.
169 """
170 start_range = 0
171 end_range = file_size
172
173 if range_str:
174 range_str = range_str.split('=', 1)[1]
175 s, e = range_str.split('-', 1)
176 if s:
177 start_range = int(s)
178 if e:
179 end_range = int(e) + 1
180 elif e:
181 if int(e) < file_size:
182 start_range = file_size - int(e)
183 return start_range, end_range
184
Alex Deymo6751bbe2017-03-21 11:20:02 -0700185 def do_GET(self): # pylint: disable=invalid-name
186 """Reply with the requested payload file."""
187 if self.path != '/payload':
188 self.send_error(404, 'Unknown request')
189 return
190
191 if not self.serving_payload:
192 self.send_error(500, 'No serving payload set')
193 return
194
195 try:
196 f = open(self.serving_payload, 'rb')
197 except IOError:
198 self.send_error(404, 'File not found')
199 return
200 # Handle the range request.
201 if 'Range' in self.headers:
202 self.send_response(206)
203 else:
204 self.send_response(200)
205
Sen Jiang3b15b592017-09-26 18:21:04 -0700206 serving_start, serving_size = self.serving_range
Sen Jiang10485592017-08-15 18:20:24 -0700207 start_range, end_range = self._parse_range(self.headers.get('range'),
Sen Jiang3b15b592017-09-26 18:21:04 -0700208 serving_size)
Alex Deymo6751bbe2017-03-21 11:20:02 -0700209 logging.info('Serving request for %s from %s [%d, %d) length: %d',
Sen Jiang3b15b592017-09-26 18:21:04 -0700210 self.path, self.serving_payload, serving_start + start_range,
211 serving_start + end_range, end_range - start_range)
Alex Deymo6751bbe2017-03-21 11:20:02 -0700212
213 self.send_header('Accept-Ranges', 'bytes')
214 self.send_header('Content-Range',
215 'bytes ' + str(start_range) + '-' + str(end_range - 1) +
216 '/' + str(end_range - start_range))
217 self.send_header('Content-Length', end_range - start_range)
218
Sen Jiang3b15b592017-09-26 18:21:04 -0700219 stat = os.fstat(f.fileno())
Alex Deymo6751bbe2017-03-21 11:20:02 -0700220 self.send_header('Last-Modified', self.date_time_string(stat.st_mtime))
221 self.send_header('Content-type', 'application/octet-stream')
222 self.end_headers()
223
Sen Jiang3b15b592017-09-26 18:21:04 -0700224 f.seek(serving_start + start_range)
Kelvin Zhang46a860c2022-09-28 19:42:39 -0700225 CopyFileObjLength(f, self.wfile, copy_length=end_range -
226 start_range, speed_limit=self.speed_limit)
Alex Deymo6751bbe2017-03-21 11:20:02 -0700227
Sen Jianga1784b72017-08-09 17:42:36 -0700228
Alex Deymo6751bbe2017-03-21 11:20:02 -0700229class ServerThread(threading.Thread):
230 """A thread for serving HTTP requests."""
231
Kelvin Zhang46a860c2022-09-28 19:42:39 -0700232 def __init__(self, ota_filename, serving_range, speed_limit):
Alex Deymo6751bbe2017-03-21 11:20:02 -0700233 threading.Thread.__init__(self)
Sen Jiang3b15b592017-09-26 18:21:04 -0700234 # serving_payload and serving_range are class attributes and the
235 # UpdateHandler class is instantiated with every request.
Alex Deymo6751bbe2017-03-21 11:20:02 -0700236 UpdateHandler.serving_payload = ota_filename
Sen Jiang3b15b592017-09-26 18:21:04 -0700237 UpdateHandler.serving_range = serving_range
Kelvin Zhang46a860c2022-09-28 19:42:39 -0700238 UpdateHandler.speed_limit = speed_limit
Alex Deymo6751bbe2017-03-21 11:20:02 -0700239 self._httpd = BaseHTTPServer.HTTPServer(('127.0.0.1', 0), UpdateHandler)
240 self.port = self._httpd.server_port
241
242 def run(self):
243 try:
244 self._httpd.serve_forever()
245 except (KeyboardInterrupt, socket.error):
246 pass
247 logging.info('Server Terminated')
248
249 def StopServer(self):
Kelvin Zhang4b883ea2020-10-08 13:26:44 -0400250 self._httpd.shutdown()
Alex Deymo6751bbe2017-03-21 11:20:02 -0700251 self._httpd.socket.close()
252
253
Kelvin Zhang46a860c2022-09-28 19:42:39 -0700254def StartServer(ota_filename, serving_range, speed_limit):
255 t = ServerThread(ota_filename, serving_range, speed_limit)
Alex Deymo6751bbe2017-03-21 11:20:02 -0700256 t.start()
257 return t
258
259
Tianjie Xu3f9be772019-11-02 18:31:50 -0700260def AndroidUpdateCommand(ota_filename, secondary, payload_url, extra_headers):
Alex Deymo6751bbe2017-03-21 11:20:02 -0700261 """Return the command to run to start the update in the Android device."""
Tianjie Xu3f9be772019-11-02 18:31:50 -0700262 ota = AndroidOTAPackage(ota_filename, secondary)
Alex Deymo6751bbe2017-03-21 11:20:02 -0700263 headers = ota.properties
Kelvin Zhang4b883ea2020-10-08 13:26:44 -0400264 headers += b'USER_AGENT=Dalvik (something, something)\n'
265 headers += b'NETWORK_ID=0\n'
266 headers += extra_headers.encode()
Alex Deymo6751bbe2017-03-21 11:20:02 -0700267
268 return ['update_engine_client', '--update', '--follow',
269 '--payload=%s' % payload_url, '--offset=%d' % ota.offset,
Kelvin Zhang4b883ea2020-10-08 13:26:44 -0400270 '--size=%d' % ota.size, '--headers="%s"' % headers.decode()]
Alex Deymo6751bbe2017-03-21 11:20:02 -0700271
272
273class AdbHost(object):
274 """Represents a device connected via ADB."""
275
276 def __init__(self, device_serial=None):
277 """Construct an instance.
278
279 Args:
280 device_serial: options string serial number of attached device.
281 """
282 self._device_serial = device_serial
283 self._command_prefix = ['adb']
284 if self._device_serial:
285 self._command_prefix += ['-s', self._device_serial]
286
Kelvin Zhang3a188952021-04-13 12:44:45 -0400287 def adb(self, command, timeout_seconds: float = None):
Alex Deymo6751bbe2017-03-21 11:20:02 -0700288 """Run an ADB command like "adb push".
289
290 Args:
291 command: list of strings containing command and arguments to run
292
293 Returns:
294 the program's return code.
295
296 Raises:
297 subprocess.CalledProcessError on command exit != 0.
298 """
299 command = self._command_prefix + command
300 logging.info('Running: %s', ' '.join(str(x) for x in command))
301 p = subprocess.Popen(command, universal_newlines=True)
Kelvin Zhang3a188952021-04-13 12:44:45 -0400302 p.wait(timeout_seconds)
Alex Deymo6751bbe2017-03-21 11:20:02 -0700303 return p.returncode
304
Sen Jianga1784b72017-08-09 17:42:36 -0700305 def adb_output(self, command):
306 """Run an ADB command like "adb push" and return the output.
307
308 Args:
309 command: list of strings containing command and arguments to run
310
311 Returns:
312 the program's output as a string.
313
314 Raises:
315 subprocess.CalledProcessError on command exit != 0.
316 """
317 command = self._command_prefix + command
318 logging.info('Running: %s', ' '.join(str(x) for x in command))
319 return subprocess.check_output(command, universal_newlines=True)
320
Alex Deymo6751bbe2017-03-21 11:20:02 -0700321
Kelvin Zhang63b39112021-03-05 12:31:38 -0500322def PushMetadata(dut, otafile, metadata_path):
Kelvin Zhang9a2d9a32023-11-28 09:42:16 -0800323 header_format = ">4sQQL"
Kelvin Zhang63b39112021-03-05 12:31:38 -0500324 with tempfile.TemporaryDirectory() as tmpdir:
325 with zipfile.ZipFile(otafile, "r") as zfp:
326 extracted_path = os.path.join(tmpdir, "payload.bin")
327 with zfp.open("payload.bin") as payload_fp, \
328 open(extracted_path, "wb") as output_fp:
Kelvin Zhang9a2d9a32023-11-28 09:42:16 -0800329 # Only extract the first |data_offset| bytes from the payload.
330 # This is because allocateSpaceForPayload only needs to see
331 # the manifest, not the entire payload.
332 # Extracting the entire payload works, but is slow for full
333 # OTA.
334 header = payload_fp.read(struct.calcsize(header_format))
335 magic, major_version, manifest_size, metadata_signature_size = struct.unpack(header_format, header)
336 assert magic == b"CrAU", "Invalid magic {}, expected CrAU".format(magic)
337 assert major_version == 2, "Invalid major version {}, only version 2 is supported".format(major_version)
338 output_fp.write(header)
Kelvin Zhang944cdac2023-12-12 12:55:27 -0800339 output_fp.write(payload_fp.read(manifest_size + metadata_signature_size))
Kelvin Zhang63b39112021-03-05 12:31:38 -0500340
341 return dut.adb([
342 "push",
343 extracted_path,
344 metadata_path
345 ]) == 0
346
347
Kelvin Zhang46a860c2022-09-28 19:42:39 -0700348def ParseSpeedLimit(arg: str) -> int:
349 arg = arg.strip().upper()
350 if not re.match(r"\d+[KkMmGgTt]?", arg):
351 raise argparse.ArgumentError(
352 "Wrong speed limit format, expected format is number followed by unit, such as 10K, 5m, 3G (case insensitive)")
353 unit = 1
354 if arg[-1].isalpha():
355 if arg[-1] == "K":
356 unit = 1024
357 elif arg[-1] == "M":
358 unit = 1024 * 1024
359 elif arg[-1] == "G":
360 unit = 1024 * 1024 * 1024
361 elif arg[-1] == "T":
362 unit = 1024 * 1024 * 1024 * 1024
363 else:
364 raise argparse.ArgumentError(
365 f"Unsupported unit for download speed: {arg[-1]}, supported units are K,M,G,T (case insensitive)")
366 return int(float(arg[:-1]) * unit)
367
368
Alex Deymo6751bbe2017-03-21 11:20:02 -0700369def main():
370 parser = argparse.ArgumentParser(description='Android A/B OTA helper.')
Sen Jiang3b15b592017-09-26 18:21:04 -0700371 parser.add_argument('otafile', metavar='PAYLOAD', type=str,
372 help='the OTA package file (a .zip file) or raw payload \
373 if device uses Omaha.')
Alex Deymo6751bbe2017-03-21 11:20:02 -0700374 parser.add_argument('--file', action='store_true',
375 help='Push the file to the device before updating.')
376 parser.add_argument('--no-push', action='store_true',
377 help='Skip the "push" command when using --file')
378 parser.add_argument('-s', type=str, default='', metavar='DEVICE',
379 help='The specific device to use.')
380 parser.add_argument('--no-verbose', action='store_true',
381 help='Less verbose output')
Sen Jianga1784b72017-08-09 17:42:36 -0700382 parser.add_argument('--public-key', type=str, default='',
383 help='Override the public key used to verify payload.')
Sen Jiang6fbfd7d2017-10-31 16:16:56 -0700384 parser.add_argument('--extra-headers', type=str, default='',
385 help='Extra headers to pass to the device.')
Tianjie Xu3f9be772019-11-02 18:31:50 -0700386 parser.add_argument('--secondary', action='store_true',
387 help='Update with the secondary payload in the package.')
Kelvin Zhang8212f532020-11-13 16:00:00 -0500388 parser.add_argument('--no-slot-switch', action='store_true',
389 help='Do not perform slot switch after the update.')
Kelvin Zhangbec0f072021-03-31 16:09:00 -0400390 parser.add_argument('--no-postinstall', action='store_true',
391 help='Do not execute postinstall scripts after the update.')
Kelvin Zhangffd21442021-04-14 09:09:41 -0400392 parser.add_argument('--allocate-only', action='store_true',
Kelvin Zhang51aad992021-02-19 14:46:28 -0500393 help='Allocate space for this OTA, instead of actually \
394 applying the OTA.')
Kelvin Zhangffd21442021-04-14 09:09:41 -0400395 parser.add_argument('--verify-only', action='store_true',
Kelvin Zhang63b39112021-03-05 12:31:38 -0500396 help='Verify metadata then exit, instead of applying the OTA.')
Kelvin Zhangffd21442021-04-14 09:09:41 -0400397 parser.add_argument('--no-care-map', action='store_true',
Kelvin Zhang5bd46222021-03-02 12:36:14 -0500398 help='Do not push care_map.pb to device.')
Kelvin Zhangc56afa32021-08-13 12:32:31 -0700399 parser.add_argument('--perform-slot-switch', action='store_true',
400 help='Perform slot switch for this OTA package')
401 parser.add_argument('--perform-reset-slot-switch', action='store_true',
402 help='Perform reset slot switch for this OTA package')
Kelvin Zhang2451a302022-03-23 19:18:35 -0700403 parser.add_argument('--wipe-user-data', action='store_true',
404 help='Wipe userdata after installing OTA')
Kelvin Zhanga7407b52023-03-13 15:05:14 -0700405 parser.add_argument('--vabc-none', action='store_true',
406 help='Set Virtual AB Compression algorithm to none, but still use Android COW format')
Daniel Zheng9fc62b82023-03-24 22:57:20 +0000407 parser.add_argument('--disable-vabc', action='store_true',
408 help='Option to enable or disable vabc. If set to false, will fall back on A/B')
Kelvin Zhang6bef4902023-02-22 12:43:27 -0800409 parser.add_argument('--enable-threading', action='store_true',
410 help='Enable multi-threaded compression for VABC')
Kelvin Zhang944cdac2023-12-12 12:55:27 -0800411 parser.add_argument('--disable-threading', action='store_true',
412 help='Enable multi-threaded compression for VABC')
Kelvin Zhang6bef4902023-02-22 12:43:27 -0800413 parser.add_argument('--batched-writes', action='store_true',
414 help='Enable batched writes for VABC')
Kelvin Zhang46a860c2022-09-28 19:42:39 -0700415 parser.add_argument('--speed-limit', type=str,
416 help='Speed limit for serving payloads over HTTP. For '
417 'example: 10K, 5m, 1G, input is case insensitive')
418
Alex Deymo6751bbe2017-03-21 11:20:02 -0700419 args = parser.parse_args()
Kelvin Zhang46a860c2022-09-28 19:42:39 -0700420 if args.speed_limit:
421 args.speed_limit = ParseSpeedLimit(args.speed_limit)
422
Alex Deymo6751bbe2017-03-21 11:20:02 -0700423 logging.basicConfig(
424 level=logging.WARNING if args.no_verbose else logging.INFO)
425
Nikita Ioffe3a327e62021-06-30 16:05:09 +0100426 start_time = time.perf_counter()
427
Alex Deymo6751bbe2017-03-21 11:20:02 -0700428 dut = AdbHost(args.s)
429
430 server_thread = None
431 # List of commands to execute on exit.
432 finalize_cmds = []
433 # Commands to execute when canceling an update.
434 cancel_cmd = ['shell', 'su', '0', 'update_engine_client', '--cancel']
435 # List of commands to perform the update.
436 cmds = []
437
Sen Jianga1784b72017-08-09 17:42:36 -0700438 help_cmd = ['shell', 'su', '0', 'update_engine_client', '--help']
Sen Jianga1784b72017-08-09 17:42:36 -0700439
Kelvin Zhang63b39112021-03-05 12:31:38 -0500440 metadata_path = "/data/ota_package/metadata"
Kelvin Zhang51aad992021-02-19 14:46:28 -0500441 if args.allocate_only:
Kelvin Zhang027eb382023-04-27 20:44:45 -0700442 with zipfile.ZipFile(args.otafile, "r") as zfp:
443 headers = zfp.read("payload_properties.txt").decode()
Kelvin Zhang63b39112021-03-05 12:31:38 -0500444 if PushMetadata(dut, args.otafile, metadata_path):
445 dut.adb([
446 "shell", "update_engine_client", "--allocate",
Kelvin Zhang027eb382023-04-27 20:44:45 -0700447 "--metadata={} --headers='{}'".format(metadata_path, headers)])
Kelvin Zhang63b39112021-03-05 12:31:38 -0500448 # Return 0, as we are executing ADB commands here, no work needed after
449 # this point
450 return 0
451 if args.verify_only:
452 if PushMetadata(dut, args.otafile, metadata_path):
453 dut.adb([
454 "shell", "update_engine_client", "--verify",
455 "--metadata={}".format(metadata_path)])
Kelvin Zhang51aad992021-02-19 14:46:28 -0500456 # Return 0, as we are executing ADB commands here, no work needed after
457 # this point
458 return 0
Kelvin Zhangc56afa32021-08-13 12:32:31 -0700459 if args.perform_slot_switch:
460 assert PushMetadata(dut, args.otafile, metadata_path)
461 dut.adb(["shell", "update_engine_client",
462 "--switch_slot=true", "--metadata={}".format(metadata_path), "--follow"])
463 return 0
464 if args.perform_reset_slot_switch:
465 assert PushMetadata(dut, args.otafile, metadata_path)
466 dut.adb(["shell", "update_engine_client",
467 "--switch_slot=false", "--metadata={}".format(metadata_path)])
468 return 0
Kelvin Zhang51aad992021-02-19 14:46:28 -0500469
Kelvin Zhang8212f532020-11-13 16:00:00 -0500470 if args.no_slot_switch:
471 args.extra_headers += "\nSWITCH_SLOT_ON_REBOOT=0"
Kelvin Zhangbec0f072021-03-31 16:09:00 -0400472 if args.no_postinstall:
473 args.extra_headers += "\nRUN_POST_INSTALL=0"
Kelvin Zhang2451a302022-03-23 19:18:35 -0700474 if args.wipe_user_data:
475 args.extra_headers += "\nPOWERWASH=1"
Kelvin Zhanga7407b52023-03-13 15:05:14 -0700476 if args.vabc_none:
477 args.extra_headers += "\nVABC_NONE=1"
Daniel Zheng9fc62b82023-03-24 22:57:20 +0000478 if args.disable_vabc:
479 args.extra_headers += "\nDISABLE_VABC=1"
Kelvin Zhang6bef4902023-02-22 12:43:27 -0800480 if args.enable_threading:
481 args.extra_headers += "\nENABLE_THREADING=1"
Kelvin Zhang944cdac2023-12-12 12:55:27 -0800482 elif args.disable_threading:
483 args.extra_headers += "\nENABLE_THREADING=0"
Kelvin Zhang6bef4902023-02-22 12:43:27 -0800484 if args.batched_writes:
485 args.extra_headers += "\nBATCHED_WRITES=1"
Kelvin Zhang8212f532020-11-13 16:00:00 -0500486
Kelvin Zhang5bd46222021-03-02 12:36:14 -0500487 with zipfile.ZipFile(args.otafile) as zfp:
488 CARE_MAP_ENTRY_NAME = "care_map.pb"
489 if CARE_MAP_ENTRY_NAME in zfp.namelist() and not args.no_care_map:
490 # Need root permission to push to /data
491 dut.adb(["root"])
Kelvin Zhang472d5612021-03-05 12:32:19 -0500492 with tempfile.NamedTemporaryFile() as care_map_fp:
Kelvin Zhang5bd46222021-03-02 12:36:14 -0500493 care_map_fp.write(zfp.read(CARE_MAP_ENTRY_NAME))
494 care_map_fp.flush()
495 dut.adb(["push", care_map_fp.name,
Kelvin Zhangffd21442021-04-14 09:09:41 -0400496 "/data/ota_package/" + CARE_MAP_ENTRY_NAME])
Kelvin Zhang5bd46222021-03-02 12:36:14 -0500497
Alex Deymo6751bbe2017-03-21 11:20:02 -0700498 if args.file:
499 # Update via pushing a file to /data.
500 device_ota_file = os.path.join(OTA_PACKAGE_PATH, 'debug.zip')
501 payload_url = 'file://' + device_ota_file
502 if not args.no_push:
Tao Baoabb45a52017-10-25 11:13:03 -0700503 data_local_tmp_file = '/data/local/tmp/debug.zip'
504 cmds.append(['push', args.otafile, data_local_tmp_file])
505 cmds.append(['shell', 'su', '0', 'mv', data_local_tmp_file,
506 device_ota_file])
507 cmds.append(['shell', 'su', '0', 'chcon',
508 'u:object_r:ota_package_file:s0', device_ota_file])
Alex Deymo6751bbe2017-03-21 11:20:02 -0700509 cmds.append(['shell', 'su', '0', 'chown', 'system:cache', device_ota_file])
510 cmds.append(['shell', 'su', '0', 'chmod', '0660', device_ota_file])
511 else:
512 # Update via sending the payload over the network with an "adb reverse"
513 # command.
Sen Jianga1784b72017-08-09 17:42:36 -0700514 payload_url = 'http://127.0.0.1:%d/payload' % DEVICE_PORT
Kelvin Zhang033e1732023-09-11 16:32:11 -0700515 serving_range = (0, os.stat(args.otafile).st_size)
Kelvin Zhang46a860c2022-09-28 19:42:39 -0700516 server_thread = StartServer(args.otafile, serving_range, args.speed_limit)
Alex Deymo6751bbe2017-03-21 11:20:02 -0700517 cmds.append(
Sen Jianga1784b72017-08-09 17:42:36 -0700518 ['reverse', 'tcp:%d' % DEVICE_PORT, 'tcp:%d' % server_thread.port])
519 finalize_cmds.append(['reverse', '--remove', 'tcp:%d' % DEVICE_PORT])
520
521 if args.public_key:
522 payload_key_dir = os.path.dirname(PAYLOAD_KEY_PATH)
523 cmds.append(
524 ['shell', 'su', '0', 'mount', '-t', 'tmpfs', 'tmpfs', payload_key_dir])
525 # Allow adb push to payload_key_dir
526 cmds.append(['shell', 'su', '0', 'chcon', 'u:object_r:shell_data_file:s0',
527 payload_key_dir])
528 cmds.append(['push', args.public_key, PAYLOAD_KEY_PATH])
529 # Allow update_engine to read it.
530 cmds.append(['shell', 'su', '0', 'chcon', '-R', 'u:object_r:system_file:s0',
531 payload_key_dir])
532 finalize_cmds.append(['shell', 'su', '0', 'umount', payload_key_dir])
Alex Deymo6751bbe2017-03-21 11:20:02 -0700533
534 try:
535 # The main update command using the configured payload_url.
Kelvin Zhang033e1732023-09-11 16:32:11 -0700536 update_cmd = AndroidUpdateCommand(args.otafile, args.secondary,
Tianjie Xu3f9be772019-11-02 18:31:50 -0700537 payload_url, args.extra_headers)
Alex Deymo6751bbe2017-03-21 11:20:02 -0700538 cmds.append(['shell', 'su', '0'] + update_cmd)
539
540 for cmd in cmds:
541 dut.adb(cmd)
542 except KeyboardInterrupt:
543 dut.adb(cancel_cmd)
544 finally:
545 if server_thread:
546 server_thread.StopServer()
547 for cmd in finalize_cmds:
Kelvin Zhang3a188952021-04-13 12:44:45 -0400548 dut.adb(cmd, 5)
Alex Deymo6751bbe2017-03-21 11:20:02 -0700549
Nikita Ioffe3a327e62021-06-30 16:05:09 +0100550 logging.info('Update took %.3f seconds', (time.perf_counter() - start_time))
Alex Deymo6751bbe2017-03-21 11:20:02 -0700551 return 0
552
Andrew Lassalle165843c2019-11-05 13:30:34 -0800553
Alex Deymo6751bbe2017-03-21 11:20:02 -0700554if __name__ == '__main__':
555 sys.exit(main())