xref: /aosp_15_r20/system/update_engine/scripts/update_device.py (revision 5a9231315b4521097b8dc3750bc806fcafe0c72f)
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