• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#      http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15from __future__ import print_function
16
17import collections
18import copy
19import errno
20import fnmatch
21import getopt
22import getpass
23import gzip
24import imp
25import json
26import logging
27import logging.config
28import os
29import platform
30import re
31import shlex
32import shutil
33import string
34import subprocess
35import sys
36import tempfile
37import threading
38import time
39import zipfile
40from hashlib import sha1, sha256
41
42import blockimgdiff
43import sparse_img
44
45logger = logging.getLogger(__name__)
46
47
48class Options(object):
49  def __init__(self):
50    base_out_path = os.getenv('OUT_DIR_COMMON_BASE')
51    if base_out_path is None:
52      base_search_path = "out"
53    else:
54      base_search_path = os.path.join(base_out_path,
55                                      os.path.basename(os.getcwd()))
56
57    platform_search_path = {
58        "linux2": os.path.join(base_search_path, "host/linux-x86"),
59        "darwin": os.path.join(base_search_path, "host/darwin-x86"),
60    }
61
62    self.search_path = platform_search_path.get(sys.platform)
63    self.signapk_path = "framework/signapk.jar"  # Relative to search_path
64    self.signapk_shared_library_path = "lib64"   # Relative to search_path
65    self.extra_signapk_args = []
66    self.java_path = "java"  # Use the one on the path by default.
67    self.java_args = ["-Xmx2048m"]  # The default JVM args.
68    self.public_key_suffix = ".x509.pem"
69    self.private_key_suffix = ".pk8"
70    # use otatools built boot_signer by default
71    self.boot_signer_path = "boot_signer"
72    self.boot_signer_args = []
73    self.verity_signer_path = None
74    self.verity_signer_args = []
75    self.verbose = False
76    self.tempfiles = []
77    self.device_specific = None
78    self.extras = {}
79    self.info_dict = None
80    self.source_info_dict = None
81    self.target_info_dict = None
82    self.worker_threads = None
83    # Stash size cannot exceed cache_size * threshold.
84    self.cache_size = None
85    self.stash_threshold = 0.8
86
87
88OPTIONS = Options()
89
90# The block size that's used across the releasetools scripts.
91BLOCK_SIZE = 4096
92
93# Values for "certificate" in apkcerts that mean special things.
94SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
95
96# The partitions allowed to be signed by AVB (Android verified boot 2.0).
97AVB_PARTITIONS = ('boot', 'recovery', 'system', 'vendor', 'product',
98                  'product_services', 'dtbo', 'odm')
99
100# Partitions that should have their care_map added to META/care_map.pb
101PARTITIONS_WITH_CARE_MAP = ('system', 'vendor', 'product', 'product_services',
102                            'odm')
103
104
105class ErrorCode(object):
106  """Define error_codes for failures that happen during the actual
107  update package installation.
108
109  Error codes 0-999 are reserved for failures before the package
110  installation (i.e. low battery, package verification failure).
111  Detailed code in 'bootable/recovery/error_code.h' """
112
113  SYSTEM_VERIFICATION_FAILURE = 1000
114  SYSTEM_UPDATE_FAILURE = 1001
115  SYSTEM_UNEXPECTED_CONTENTS = 1002
116  SYSTEM_NONZERO_CONTENTS = 1003
117  SYSTEM_RECOVER_FAILURE = 1004
118  VENDOR_VERIFICATION_FAILURE = 2000
119  VENDOR_UPDATE_FAILURE = 2001
120  VENDOR_UNEXPECTED_CONTENTS = 2002
121  VENDOR_NONZERO_CONTENTS = 2003
122  VENDOR_RECOVER_FAILURE = 2004
123  OEM_PROP_MISMATCH = 3000
124  FINGERPRINT_MISMATCH = 3001
125  THUMBPRINT_MISMATCH = 3002
126  OLDER_BUILD = 3003
127  DEVICE_MISMATCH = 3004
128  BAD_PATCH_FILE = 3005
129  INSUFFICIENT_CACHE_SPACE = 3006
130  TUNE_PARTITION_FAILURE = 3007
131  APPLY_PATCH_FAILURE = 3008
132
133
134class ExternalError(RuntimeError):
135  pass
136
137
138def InitLogging():
139  DEFAULT_LOGGING_CONFIG = {
140      'version': 1,
141      'disable_existing_loggers': False,
142      'formatters': {
143          'standard': {
144              'format':
145                  '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
146              'datefmt': '%Y-%m-%d %H:%M:%S',
147          },
148      },
149      'handlers': {
150          'default': {
151              'class': 'logging.StreamHandler',
152              'formatter': 'standard',
153          },
154      },
155      'loggers': {
156          '': {
157              'handlers': ['default'],
158              'level': 'WARNING',
159              'propagate': True,
160          }
161      }
162  }
163  env_config = os.getenv('LOGGING_CONFIG')
164  if env_config:
165    with open(env_config) as f:
166      config = json.load(f)
167  else:
168    config = DEFAULT_LOGGING_CONFIG
169
170    # Increase the logging level for verbose mode.
171    if OPTIONS.verbose:
172      config = copy.deepcopy(DEFAULT_LOGGING_CONFIG)
173      config['loggers']['']['level'] = 'INFO'
174
175  logging.config.dictConfig(config)
176
177
178def Run(args, verbose=None, **kwargs):
179  """Creates and returns a subprocess.Popen object.
180
181  Args:
182    args: The command represented as a list of strings.
183    verbose: Whether the commands should be shown. Default to the global
184        verbosity if unspecified.
185    kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
186        stdin, etc. stdout and stderr will default to subprocess.PIPE and
187        subprocess.STDOUT respectively unless caller specifies any of them.
188
189  Returns:
190    A subprocess.Popen object.
191  """
192  if 'stdout' not in kwargs and 'stderr' not in kwargs:
193    kwargs['stdout'] = subprocess.PIPE
194    kwargs['stderr'] = subprocess.STDOUT
195  # Don't log any if caller explicitly says so.
196  if verbose != False:
197    logger.info("  Running: \"%s\"", " ".join(args))
198  return subprocess.Popen(args, **kwargs)
199
200
201def RunAndWait(args, verbose=None, **kwargs):
202  """Runs the given command waiting for it to complete.
203
204  Args:
205    args: The command represented as a list of strings.
206    verbose: Whether the commands should be shown. Default to the global
207        verbosity if unspecified.
208    kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
209        stdin, etc. stdout and stderr will default to subprocess.PIPE and
210        subprocess.STDOUT respectively unless caller specifies any of them.
211
212  Raises:
213    ExternalError: On non-zero exit from the command.
214  """
215  proc = Run(args, verbose=verbose, **kwargs)
216  proc.wait()
217
218  if proc.returncode != 0:
219    raise ExternalError(
220        "Failed to run command '{}' (exit code {})".format(
221            args, proc.returncode))
222
223
224def RunAndCheckOutput(args, verbose=None, **kwargs):
225  """Runs the given command and returns the output.
226
227  Args:
228    args: The command represented as a list of strings.
229    verbose: Whether the commands should be shown. Default to the global
230        verbosity if unspecified.
231    kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
232        stdin, etc. stdout and stderr will default to subprocess.PIPE and
233        subprocess.STDOUT respectively unless caller specifies any of them.
234
235  Returns:
236    The output string.
237
238  Raises:
239    ExternalError: On non-zero exit from the command.
240  """
241  proc = Run(args, verbose=verbose, **kwargs)
242  output, _ = proc.communicate()
243  # Don't log any if caller explicitly says so.
244  if verbose != False:
245    logger.info("%s", output.rstrip())
246  if proc.returncode != 0:
247    raise ExternalError(
248        "Failed to run command '{}' (exit code {}):\n{}".format(
249            args, proc.returncode, output))
250  return output
251
252
253def RoundUpTo4K(value):
254  rounded_up = value + 4095
255  return rounded_up - (rounded_up % 4096)
256
257
258def CloseInheritedPipes():
259  """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
260  before doing other work."""
261  if platform.system() != "Darwin":
262    return
263  for d in range(3, 1025):
264    try:
265      stat = os.fstat(d)
266      if stat is not None:
267        pipebit = stat[0] & 0x1000
268        if pipebit != 0:
269          os.close(d)
270    except OSError:
271      pass
272
273
274def LoadInfoDict(input_file, repacking=False):
275  """Loads the key/value pairs from the given input target_files.
276
277  It reads `META/misc_info.txt` file in the target_files input, does sanity
278  checks and returns the parsed key/value pairs for to the given build. It's
279  usually called early when working on input target_files files, e.g. when
280  generating OTAs, or signing builds. Note that the function may be called
281  against an old target_files file (i.e. from past dessert releases). So the
282  property parsing needs to be backward compatible.
283
284  In a `META/misc_info.txt`, a few properties are stored as links to the files
285  in the PRODUCT_OUT directory. It works fine with the build system. However,
286  they are no longer available when (re)generating images from target_files zip.
287  When `repacking` is True, redirect these properties to the actual files in the
288  unzipped directory.
289
290  Args:
291    input_file: The input target_files file, which could be an open
292        zipfile.ZipFile instance, or a str for the dir that contains the files
293        unzipped from a target_files file.
294    repacking: Whether it's trying repack an target_files file after loading the
295        info dict (default: False). If so, it will rewrite a few loaded
296        properties (e.g. selinux_fc, root_dir) to point to the actual files in
297        target_files file. When doing repacking, `input_file` must be a dir.
298
299  Returns:
300    A dict that contains the parsed key/value pairs.
301
302  Raises:
303    AssertionError: On invalid input arguments.
304    ValueError: On malformed input values.
305  """
306  if repacking:
307    assert isinstance(input_file, str), \
308        "input_file must be a path str when doing repacking"
309
310  def read_helper(fn):
311    if isinstance(input_file, zipfile.ZipFile):
312      return input_file.read(fn)
313    else:
314      path = os.path.join(input_file, *fn.split("/"))
315      try:
316        with open(path) as f:
317          return f.read()
318      except IOError as e:
319        if e.errno == errno.ENOENT:
320          raise KeyError(fn)
321
322  try:
323    d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
324  except KeyError:
325    raise ValueError("Failed to find META/misc_info.txt in input target-files")
326
327  if "recovery_api_version" not in d:
328    raise ValueError("Failed to find 'recovery_api_version'")
329  if "fstab_version" not in d:
330    raise ValueError("Failed to find 'fstab_version'")
331
332  if repacking:
333    # We carry a copy of file_contexts.bin under META/. If not available, search
334    # BOOT/RAMDISK/. Note that sometimes we may need a different file to build
335    # images than the one running on device, in that case, we must have the one
336    # for image generation copied to META/.
337    fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
338    fc_config = os.path.join(input_file, "META", fc_basename)
339    assert os.path.exists(fc_config)
340
341    d["selinux_fc"] = fc_config
342
343    # Similarly we need to redirect "root_dir", and "root_fs_config".
344    d["root_dir"] = os.path.join(input_file, "ROOT")
345    d["root_fs_config"] = os.path.join(
346        input_file, "META", "root_filesystem_config.txt")
347
348    # Redirect {system,vendor}_base_fs_file.
349    if "system_base_fs_file" in d:
350      basename = os.path.basename(d["system_base_fs_file"])
351      system_base_fs_file = os.path.join(input_file, "META", basename)
352      if os.path.exists(system_base_fs_file):
353        d["system_base_fs_file"] = system_base_fs_file
354      else:
355        logger.warning(
356            "Failed to find system base fs file: %s", system_base_fs_file)
357        del d["system_base_fs_file"]
358
359    if "vendor_base_fs_file" in d:
360      basename = os.path.basename(d["vendor_base_fs_file"])
361      vendor_base_fs_file = os.path.join(input_file, "META", basename)
362      if os.path.exists(vendor_base_fs_file):
363        d["vendor_base_fs_file"] = vendor_base_fs_file
364      else:
365        logger.warning(
366            "Failed to find vendor base fs file: %s", vendor_base_fs_file)
367        del d["vendor_base_fs_file"]
368
369  def makeint(key):
370    if key in d:
371      d[key] = int(d[key], 0)
372
373  makeint("recovery_api_version")
374  makeint("blocksize")
375  makeint("system_size")
376  makeint("vendor_size")
377  makeint("userdata_size")
378  makeint("cache_size")
379  makeint("recovery_size")
380  makeint("boot_size")
381  makeint("fstab_version")
382
383  # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
384  # ../RAMDISK/system/etc/recovery.fstab. LoadInfoDict() has to handle both
385  # cases, since it may load the info_dict from an old build (e.g. when
386  # generating incremental OTAs from that build).
387  system_root_image = d.get("system_root_image") == "true"
388  if d.get("no_recovery") != "true":
389    recovery_fstab_path = "RECOVERY/RAMDISK/system/etc/recovery.fstab"
390    if isinstance(input_file, zipfile.ZipFile):
391      if recovery_fstab_path not in input_file.namelist():
392        recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
393    else:
394      path = os.path.join(input_file, *recovery_fstab_path.split("/"))
395      if not os.path.exists(path):
396        recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
397    d["fstab"] = LoadRecoveryFSTab(
398        read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
399
400  elif d.get("recovery_as_boot") == "true":
401    recovery_fstab_path = "BOOT/RAMDISK/system/etc/recovery.fstab"
402    if isinstance(input_file, zipfile.ZipFile):
403      if recovery_fstab_path not in input_file.namelist():
404        recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
405    else:
406      path = os.path.join(input_file, *recovery_fstab_path.split("/"))
407      if not os.path.exists(path):
408        recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
409    d["fstab"] = LoadRecoveryFSTab(
410        read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
411
412  else:
413    d["fstab"] = None
414
415  # Tries to load the build props for all partitions with care_map, including
416  # system and vendor.
417  for partition in PARTITIONS_WITH_CARE_MAP:
418    partition_prop = "{}.build.prop".format(partition)
419    d[partition_prop] = LoadBuildProp(
420        read_helper, "{}/build.prop".format(partition.upper()))
421    # Some partition might use /<partition>/etc/build.prop as the new path.
422    # TODO: try new path first when majority of them switch to the new path.
423    if not d[partition_prop]:
424      d[partition_prop] = LoadBuildProp(
425          read_helper, "{}/etc/build.prop".format(partition.upper()))
426  d["build.prop"] = d["system.build.prop"]
427
428  # Set up the salt (based on fingerprint or thumbprint) that will be used when
429  # adding AVB footer.
430  if d.get("avb_enable") == "true":
431    fp = None
432    if "build.prop" in d:
433      build_prop = d["build.prop"]
434      if "ro.build.fingerprint" in build_prop:
435        fp = build_prop["ro.build.fingerprint"]
436      elif "ro.build.thumbprint" in build_prop:
437        fp = build_prop["ro.build.thumbprint"]
438    if fp:
439      d["avb_salt"] = sha256(fp).hexdigest()
440
441  return d
442
443
444def LoadBuildProp(read_helper, prop_file):
445  try:
446    data = read_helper(prop_file)
447  except KeyError:
448    logger.warning("Failed to read %s", prop_file)
449    data = ""
450  return LoadDictionaryFromLines(data.split("\n"))
451
452
453def LoadDictionaryFromLines(lines):
454  d = {}
455  for line in lines:
456    line = line.strip()
457    if not line or line.startswith("#"):
458      continue
459    if "=" in line:
460      name, value = line.split("=", 1)
461      d[name] = value
462  return d
463
464
465def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
466                      system_root_image=False):
467  class Partition(object):
468    def __init__(self, mount_point, fs_type, device, length, context):
469      self.mount_point = mount_point
470      self.fs_type = fs_type
471      self.device = device
472      self.length = length
473      self.context = context
474
475  try:
476    data = read_helper(recovery_fstab_path)
477  except KeyError:
478    logger.warning("Failed to find %s", recovery_fstab_path)
479    data = ""
480
481  assert fstab_version == 2
482
483  d = {}
484  for line in data.split("\n"):
485    line = line.strip()
486    if not line or line.startswith("#"):
487      continue
488
489    # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
490    pieces = line.split()
491    if len(pieces) != 5:
492      raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
493
494    # Ignore entries that are managed by vold.
495    options = pieces[4]
496    if "voldmanaged=" in options:
497      continue
498
499    # It's a good line, parse it.
500    length = 0
501    options = options.split(",")
502    for i in options:
503      if i.startswith("length="):
504        length = int(i[7:])
505      else:
506        # Ignore all unknown options in the unified fstab.
507        continue
508
509    mount_flags = pieces[3]
510    # Honor the SELinux context if present.
511    context = None
512    for i in mount_flags.split(","):
513      if i.startswith("context="):
514        context = i
515
516    mount_point = pieces[1]
517    d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
518                               device=pieces[0], length=length, context=context)
519
520  # / is used for the system mount point when the root directory is included in
521  # system. Other areas assume system is always at "/system" so point /system
522  # at /.
523  if system_root_image:
524    assert not d.has_key("/system") and d.has_key("/")
525    d["/system"] = d["/"]
526  return d
527
528
529def DumpInfoDict(d):
530  for k, v in sorted(d.items()):
531    logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
532
533
534def AppendAVBSigningArgs(cmd, partition):
535  """Append signing arguments for avbtool."""
536  # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
537  key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
538  algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
539  if key_path and algorithm:
540    cmd.extend(["--key", key_path, "--algorithm", algorithm])
541  avb_salt = OPTIONS.info_dict.get("avb_salt")
542  # make_vbmeta_image doesn't like "--salt" (and it's not needed).
543  if avb_salt and not partition.startswith("vbmeta"):
544    cmd.extend(["--salt", avb_salt])
545
546
547def GetAvbChainedPartitionArg(partition, info_dict, key=None):
548  """Constructs and returns the arg to build or verify a chained partition.
549
550  Args:
551    partition: The partition name.
552    info_dict: The info dict to look up the key info and rollback index
553        location.
554    key: The key to be used for building or verifying the partition. Defaults to
555        the key listed in info_dict.
556
557  Returns:
558    A string of form "partition:rollback_index_location:key" that can be used to
559    build or verify vbmeta image.
560  """
561  if key is None:
562    key = info_dict["avb_" + partition + "_key_path"]
563  pubkey_path = ExtractAvbPublicKey(key)
564  rollback_index_location = info_dict[
565      "avb_" + partition + "_rollback_index_location"]
566  return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
567
568
569def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
570                        has_ramdisk=False, two_step_image=False):
571  """Build a bootable image from the specified sourcedir.
572
573  Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
574  'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
575  we are building a two-step special image (i.e. building a recovery image to
576  be loaded into /boot in two-step OTAs).
577
578  Return the image data, or None if sourcedir does not appear to contains files
579  for building the requested image.
580  """
581
582  def make_ramdisk():
583    ramdisk_img = tempfile.NamedTemporaryFile()
584
585    if os.access(fs_config_file, os.F_OK):
586      cmd = ["mkbootfs", "-f", fs_config_file,
587             os.path.join(sourcedir, "RAMDISK")]
588    else:
589      cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
590    p1 = Run(cmd, stdout=subprocess.PIPE)
591    p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
592
593    p2.wait()
594    p1.wait()
595    assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
596    assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
597
598    return ramdisk_img
599
600  if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
601    return None
602
603  if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
604    return None
605
606  if info_dict is None:
607    info_dict = OPTIONS.info_dict
608
609  img = tempfile.NamedTemporaryFile()
610
611  if has_ramdisk:
612    ramdisk_img = make_ramdisk()
613
614  # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
615  mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
616
617  cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
618
619  fn = os.path.join(sourcedir, "second")
620  if os.access(fn, os.F_OK):
621    cmd.append("--second")
622    cmd.append(fn)
623
624  fn = os.path.join(sourcedir, "dtb")
625  if os.access(fn, os.F_OK):
626    cmd.append("--dtb")
627    cmd.append(fn)
628
629  fn = os.path.join(sourcedir, "cmdline")
630  if os.access(fn, os.F_OK):
631    cmd.append("--cmdline")
632    cmd.append(open(fn).read().rstrip("\n"))
633
634  fn = os.path.join(sourcedir, "base")
635  if os.access(fn, os.F_OK):
636    cmd.append("--base")
637    cmd.append(open(fn).read().rstrip("\n"))
638
639  fn = os.path.join(sourcedir, "pagesize")
640  if os.access(fn, os.F_OK):
641    cmd.append("--pagesize")
642    cmd.append(open(fn).read().rstrip("\n"))
643
644  args = info_dict.get("mkbootimg_args")
645  if args and args.strip():
646    cmd.extend(shlex.split(args))
647
648  args = info_dict.get("mkbootimg_version_args")
649  if args and args.strip():
650    cmd.extend(shlex.split(args))
651
652  if has_ramdisk:
653    cmd.extend(["--ramdisk", ramdisk_img.name])
654
655  img_unsigned = None
656  if info_dict.get("vboot"):
657    img_unsigned = tempfile.NamedTemporaryFile()
658    cmd.extend(["--output", img_unsigned.name])
659  else:
660    cmd.extend(["--output", img.name])
661
662  # "boot" or "recovery", without extension.
663  partition_name = os.path.basename(sourcedir).lower()
664
665  if partition_name == "recovery":
666    if info_dict.get("include_recovery_dtbo") == "true":
667      fn = os.path.join(sourcedir, "recovery_dtbo")
668      cmd.extend(["--recovery_dtbo", fn])
669    if info_dict.get("include_recovery_acpio") == "true":
670      fn = os.path.join(sourcedir, "recovery_acpio")
671      cmd.extend(["--recovery_acpio", fn])
672
673  RunAndCheckOutput(cmd)
674
675  if (info_dict.get("boot_signer") == "true" and
676      info_dict.get("verity_key")):
677    # Hard-code the path as "/boot" for two-step special recovery image (which
678    # will be loaded into /boot during the two-step OTA).
679    if two_step_image:
680      path = "/boot"
681    else:
682      path = "/" + partition_name
683    cmd = [OPTIONS.boot_signer_path]
684    cmd.extend(OPTIONS.boot_signer_args)
685    cmd.extend([path, img.name,
686                info_dict["verity_key"] + ".pk8",
687                info_dict["verity_key"] + ".x509.pem", img.name])
688    RunAndCheckOutput(cmd)
689
690  # Sign the image if vboot is non-empty.
691  elif info_dict.get("vboot"):
692    path = "/" + partition_name
693    img_keyblock = tempfile.NamedTemporaryFile()
694    # We have switched from the prebuilt futility binary to using the tool
695    # (futility-host) built from the source. Override the setting in the old
696    # TF.zip.
697    futility = info_dict["futility"]
698    if futility.startswith("prebuilts/"):
699      futility = "futility-host"
700    cmd = [info_dict["vboot_signer_cmd"], futility,
701           img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
702           info_dict["vboot_key"] + ".vbprivk",
703           info_dict["vboot_subkey"] + ".vbprivk",
704           img_keyblock.name,
705           img.name]
706    RunAndCheckOutput(cmd)
707
708    # Clean up the temp files.
709    img_unsigned.close()
710    img_keyblock.close()
711
712  # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
713  if info_dict.get("avb_enable") == "true":
714    avbtool = info_dict["avb_avbtool"]
715    part_size = info_dict[partition_name + "_size"]
716    cmd = [avbtool, "add_hash_footer", "--image", img.name,
717           "--partition_size", str(part_size), "--partition_name",
718           partition_name]
719    AppendAVBSigningArgs(cmd, partition_name)
720    args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
721    if args and args.strip():
722      cmd.extend(shlex.split(args))
723    RunAndCheckOutput(cmd)
724
725  img.seek(os.SEEK_SET, 0)
726  data = img.read()
727
728  if has_ramdisk:
729    ramdisk_img.close()
730  img.close()
731
732  return data
733
734
735def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
736                     info_dict=None, two_step_image=False):
737  """Return a File object with the desired bootable image.
738
739  Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
740  otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
741  the source files in 'unpack_dir'/'tree_subdir'."""
742
743  prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
744  if os.path.exists(prebuilt_path):
745    logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
746    return File.FromLocalFile(name, prebuilt_path)
747
748  prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
749  if os.path.exists(prebuilt_path):
750    logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
751    return File.FromLocalFile(name, prebuilt_path)
752
753  logger.info("building image from target_files %s...", tree_subdir)
754
755  if info_dict is None:
756    info_dict = OPTIONS.info_dict
757
758  # With system_root_image == "true", we don't pack ramdisk into the boot image.
759  # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
760  # for recovery.
761  has_ramdisk = (info_dict.get("system_root_image") != "true" or
762                 prebuilt_name != "boot.img" or
763                 info_dict.get("recovery_as_boot") == "true")
764
765  fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
766  data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
767                             os.path.join(unpack_dir, fs_config),
768                             info_dict, has_ramdisk, two_step_image)
769  if data:
770    return File(name, data)
771  return None
772
773
774def Gunzip(in_filename, out_filename):
775  """Gunzips the given gzip compressed file to a given output file."""
776  with gzip.open(in_filename, "rb") as in_file, \
777       open(out_filename, "wb") as out_file:
778    shutil.copyfileobj(in_file, out_file)
779
780
781def UnzipToDir(filename, dirname, patterns=None):
782  """Unzips the archive to the given directory.
783
784  Args:
785    filename: The name of the zip file to unzip.
786    dirname: Where the unziped files will land.
787    patterns: Files to unzip from the archive. If omitted, will unzip the entire
788        archvie. Non-matching patterns will be filtered out. If there's no match
789        after the filtering, no file will be unzipped.
790  """
791  cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
792  if patterns is not None:
793    # Filter out non-matching patterns. unzip will complain otherwise.
794    with zipfile.ZipFile(filename) as input_zip:
795      names = input_zip.namelist()
796    filtered = [
797        pattern for pattern in patterns if fnmatch.filter(names, pattern)]
798
799    # There isn't any matching files. Don't unzip anything.
800    if not filtered:
801      return
802    cmd.extend(filtered)
803
804  RunAndCheckOutput(cmd)
805
806
807def UnzipTemp(filename, pattern=None):
808  """Unzips the given archive into a temporary directory and returns the name.
809
810  Args:
811    filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
812    a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
813
814    pattern: Files to unzip from the archive. If omitted, will unzip the entire
815    archvie.
816
817  Returns:
818    The name of the temporary directory.
819  """
820
821  tmp = MakeTempDir(prefix="targetfiles-")
822  m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
823  if m:
824    UnzipToDir(m.group(1), tmp, pattern)
825    UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
826    filename = m.group(1)
827  else:
828    UnzipToDir(filename, tmp, pattern)
829
830  return tmp
831
832
833def GetUserImage(which, tmpdir, input_zip,
834                 info_dict=None,
835                 allow_shared_blocks=None,
836                 hashtree_info_generator=None,
837                 reset_file_map=False):
838  """Returns an Image object suitable for passing to BlockImageDiff.
839
840  This function loads the specified image from the given path. If the specified
841  image is sparse, it also performs additional processing for OTA purpose. For
842  example, it always adds block 0 to clobbered blocks list. It also detects
843  files that cannot be reconstructed from the block list, for whom we should
844  avoid applying imgdiff.
845
846  Args:
847    which: The partition name.
848    tmpdir: The directory that contains the prebuilt image and block map file.
849    input_zip: The target-files ZIP archive.
850    info_dict: The dict to be looked up for relevant info.
851    allow_shared_blocks: If image is sparse, whether having shared blocks is
852        allowed. If none, it is looked up from info_dict.
853    hashtree_info_generator: If present and image is sparse, generates the
854        hashtree_info for this sparse image.
855    reset_file_map: If true and image is sparse, reset file map before returning
856        the image.
857  Returns:
858    A Image object. If it is a sparse image and reset_file_map is False, the
859    image will have file_map info loaded.
860  """
861  if info_dict == None:
862    info_dict = LoadInfoDict(input_zip)
863
864  is_sparse = info_dict.get("extfs_sparse_flag")
865
866  # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
867  # shared blocks (i.e. some blocks will show up in multiple files' block
868  # list). We can only allocate such shared blocks to the first "owner", and
869  # disable imgdiff for all later occurrences.
870  if allow_shared_blocks is None:
871    allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
872
873  if is_sparse:
874    img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
875                         hashtree_info_generator)
876    if reset_file_map:
877      img.ResetFileMap()
878    return img
879  else:
880    return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
881
882
883def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
884  """Returns a Image object suitable for passing to BlockImageDiff.
885
886  This function loads the specified non-sparse image from the given path.
887
888  Args:
889    which: The partition name.
890    tmpdir: The directory that contains the prebuilt image and block map file.
891  Returns:
892    A Image object.
893  """
894  path = os.path.join(tmpdir, "IMAGES", which + ".img")
895  mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
896
897  # The image and map files must have been created prior to calling
898  # ota_from_target_files.py (since LMP).
899  assert os.path.exists(path) and os.path.exists(mappath)
900
901  return blockimgdiff.FileImage(path, hashtree_info_generator=
902                                hashtree_info_generator)
903
904def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
905                   hashtree_info_generator=None):
906  """Returns a SparseImage object suitable for passing to BlockImageDiff.
907
908  This function loads the specified sparse image from the given path, and
909  performs additional processing for OTA purpose. For example, it always adds
910  block 0 to clobbered blocks list. It also detects files that cannot be
911  reconstructed from the block list, for whom we should avoid applying imgdiff.
912
913  Args:
914    which: The partition name, e.g. "system", "vendor".
915    tmpdir: The directory that contains the prebuilt image and block map file.
916    input_zip: The target-files ZIP archive.
917    allow_shared_blocks: Whether having shared blocks is allowed.
918    hashtree_info_generator: If present, generates the hashtree_info for this
919        sparse image.
920  Returns:
921    A SparseImage object, with file_map info loaded.
922  """
923  path = os.path.join(tmpdir, "IMAGES", which + ".img")
924  mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
925
926  # The image and map files must have been created prior to calling
927  # ota_from_target_files.py (since LMP).
928  assert os.path.exists(path) and os.path.exists(mappath)
929
930  # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
931  # it to clobbered_blocks so that it will be written to the target
932  # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
933  clobbered_blocks = "0"
934
935  image = sparse_img.SparseImage(
936      path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
937      hashtree_info_generator=hashtree_info_generator)
938
939  # block.map may contain less blocks, because mke2fs may skip allocating blocks
940  # if they contain all zeros. We can't reconstruct such a file from its block
941  # list. Tag such entries accordingly. (Bug: 65213616)
942  for entry in image.file_map:
943    # Skip artificial names, such as "__ZERO", "__NONZERO-1".
944    if not entry.startswith('/'):
945      continue
946
947    # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
948    # filename listed in system.map may contain an additional leading slash
949    # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
950    # results.
951    arcname = string.replace(entry, which, which.upper(), 1).lstrip('/')
952
953    # Special handling another case, where files not under /system
954    # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
955    if which == 'system' and not arcname.startswith('SYSTEM'):
956      arcname = 'ROOT/' + arcname
957
958    assert arcname in input_zip.namelist(), \
959        "Failed to find the ZIP entry for {}".format(entry)
960
961    info = input_zip.getinfo(arcname)
962    ranges = image.file_map[entry]
963
964    # If a RangeSet has been tagged as using shared blocks while loading the
965    # image, check the original block list to determine its completeness. Note
966    # that the 'incomplete' flag would be tagged to the original RangeSet only.
967    if ranges.extra.get('uses_shared_blocks'):
968      ranges = ranges.extra['uses_shared_blocks']
969
970    if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
971      ranges.extra['incomplete'] = True
972
973  return image
974
975
976def GetKeyPasswords(keylist):
977  """Given a list of keys, prompt the user to enter passwords for
978  those which require them.  Return a {key: password} dict.  password
979  will be None if the key has no password."""
980
981  no_passwords = []
982  need_passwords = []
983  key_passwords = {}
984  devnull = open("/dev/null", "w+b")
985  for k in sorted(keylist):
986    # We don't need a password for things that aren't really keys.
987    if k in SPECIAL_CERT_STRINGS:
988      no_passwords.append(k)
989      continue
990
991    p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
992             "-inform", "DER", "-nocrypt"],
993            stdin=devnull.fileno(),
994            stdout=devnull.fileno(),
995            stderr=subprocess.STDOUT)
996    p.communicate()
997    if p.returncode == 0:
998      # Definitely an unencrypted key.
999      no_passwords.append(k)
1000    else:
1001      p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1002               "-inform", "DER", "-passin", "pass:"],
1003              stdin=devnull.fileno(),
1004              stdout=devnull.fileno(),
1005              stderr=subprocess.PIPE)
1006      _, stderr = p.communicate()
1007      if p.returncode == 0:
1008        # Encrypted key with empty string as password.
1009        key_passwords[k] = ''
1010      elif stderr.startswith('Error decrypting key'):
1011        # Definitely encrypted key.
1012        # It would have said "Error reading key" if it didn't parse correctly.
1013        need_passwords.append(k)
1014      else:
1015        # Potentially, a type of key that openssl doesn't understand.
1016        # We'll let the routines in signapk.jar handle it.
1017        no_passwords.append(k)
1018  devnull.close()
1019
1020  key_passwords.update(PasswordManager().GetPasswords(need_passwords))
1021  key_passwords.update(dict.fromkeys(no_passwords))
1022  return key_passwords
1023
1024
1025def GetMinSdkVersion(apk_name):
1026  """Gets the minSdkVersion declared in the APK.
1027
1028  It calls 'aapt' to query the embedded minSdkVersion from the given APK file.
1029  This can be both a decimal number (API Level) or a codename.
1030
1031  Args:
1032    apk_name: The APK filename.
1033
1034  Returns:
1035    The parsed SDK version string.
1036
1037  Raises:
1038    ExternalError: On failing to obtain the min SDK version.
1039  """
1040  proc = Run(
1041      ["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE,
1042      stderr=subprocess.PIPE)
1043  stdoutdata, stderrdata = proc.communicate()
1044  if proc.returncode != 0:
1045    raise ExternalError(
1046        "Failed to obtain minSdkVersion: aapt return code {}:\n{}\n{}".format(
1047            proc.returncode, stdoutdata, stderrdata))
1048
1049  for line in stdoutdata.split("\n"):
1050    # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
1051    m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1052    if m:
1053      return m.group(1)
1054  raise ExternalError("No minSdkVersion returned by aapt")
1055
1056
1057def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
1058  """Returns the minSdkVersion declared in the APK as a number (API Level).
1059
1060  If minSdkVersion is set to a codename, it is translated to a number using the
1061  provided map.
1062
1063  Args:
1064    apk_name: The APK filename.
1065
1066  Returns:
1067    The parsed SDK version number.
1068
1069  Raises:
1070    ExternalError: On failing to get the min SDK version number.
1071  """
1072  version = GetMinSdkVersion(apk_name)
1073  try:
1074    return int(version)
1075  except ValueError:
1076    # Not a decimal number. Codename?
1077    if version in codename_to_api_level_map:
1078      return codename_to_api_level_map[version]
1079    else:
1080      raise ExternalError(
1081          "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1082              version, codename_to_api_level_map))
1083
1084
1085def SignFile(input_name, output_name, key, password, min_api_level=None,
1086             codename_to_api_level_map=None, whole_file=False,
1087             extra_signapk_args=None):
1088  """Sign the input_name zip/jar/apk, producing output_name.  Use the
1089  given key and password (the latter may be None if the key does not
1090  have a password.
1091
1092  If whole_file is true, use the "-w" option to SignApk to embed a
1093  signature that covers the whole file in the archive comment of the
1094  zip file.
1095
1096  min_api_level is the API Level (int) of the oldest platform this file may end
1097  up on. If not specified for an APK, the API Level is obtained by interpreting
1098  the minSdkVersion attribute of the APK's AndroidManifest.xml.
1099
1100  codename_to_api_level_map is needed to translate the codename which may be
1101  encountered as the APK's minSdkVersion.
1102
1103  Caller may optionally specify extra args to be passed to SignApk, which
1104  defaults to OPTIONS.extra_signapk_args if omitted.
1105  """
1106  if codename_to_api_level_map is None:
1107    codename_to_api_level_map = {}
1108  if extra_signapk_args is None:
1109    extra_signapk_args = OPTIONS.extra_signapk_args
1110
1111  java_library_path = os.path.join(
1112      OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1113
1114  cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1115         ["-Djava.library.path=" + java_library_path,
1116          "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
1117         extra_signapk_args)
1118  if whole_file:
1119    cmd.append("-w")
1120
1121  min_sdk_version = min_api_level
1122  if min_sdk_version is None:
1123    if not whole_file:
1124      min_sdk_version = GetMinSdkVersionInt(
1125          input_name, codename_to_api_level_map)
1126  if min_sdk_version is not None:
1127    cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1128
1129  cmd.extend([key + OPTIONS.public_key_suffix,
1130              key + OPTIONS.private_key_suffix,
1131              input_name, output_name])
1132
1133  proc = Run(cmd, stdin=subprocess.PIPE)
1134  if password is not None:
1135    password += "\n"
1136  stdoutdata, _ = proc.communicate(password)
1137  if proc.returncode != 0:
1138    raise ExternalError(
1139        "Failed to run signapk.jar: return code {}:\n{}".format(
1140            proc.returncode, stdoutdata))
1141
1142
1143def CheckSize(data, target, info_dict):
1144  """Checks the data string passed against the max size limit.
1145
1146  For non-AVB images, raise exception if the data is too big. Print a warning
1147  if the data is nearing the maximum size.
1148
1149  For AVB images, the actual image size should be identical to the limit.
1150
1151  Args:
1152    data: A string that contains all the data for the partition.
1153    target: The partition name. The ".img" suffix is optional.
1154    info_dict: The dict to be looked up for relevant info.
1155  """
1156  if target.endswith(".img"):
1157    target = target[:-4]
1158  mount_point = "/" + target
1159
1160  fs_type = None
1161  limit = None
1162  if info_dict["fstab"]:
1163    if mount_point == "/userdata":
1164      mount_point = "/data"
1165    p = info_dict["fstab"][mount_point]
1166    fs_type = p.fs_type
1167    device = p.device
1168    if "/" in device:
1169      device = device[device.rfind("/")+1:]
1170    limit = info_dict.get(device + "_size")
1171  if not fs_type or not limit:
1172    return
1173
1174  size = len(data)
1175  # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1176  # path.
1177  if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1178    if size != limit:
1179      raise ExternalError(
1180          "Mismatching image size for %s: expected %d actual %d" % (
1181              target, limit, size))
1182  else:
1183    pct = float(size) * 100.0 / limit
1184    msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1185    if pct >= 99.0:
1186      raise ExternalError(msg)
1187    elif pct >= 95.0:
1188      logger.warning("\n  WARNING: %s\n", msg)
1189    else:
1190      logger.info("  %s", msg)
1191
1192
1193def ReadApkCerts(tf_zip):
1194  """Parses the APK certs info from a given target-files zip.
1195
1196  Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1197  tuple with the following elements: (1) a dictionary that maps packages to
1198  certs (based on the "certificate" and "private_key" attributes in the file;
1199  (2) a string representing the extension of compressed APKs in the target files
1200  (e.g ".gz", ".bro").
1201
1202  Args:
1203    tf_zip: The input target_files ZipFile (already open).
1204
1205  Returns:
1206    (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1207        the extension string of compressed APKs (e.g. ".gz"), or None if there's
1208        no compressed APKs.
1209  """
1210  certmap = {}
1211  compressed_extension = None
1212
1213  # META/apkcerts.txt contains the info for _all_ the packages known at build
1214  # time. Filter out the ones that are not installed.
1215  installed_files = set()
1216  for name in tf_zip.namelist():
1217    basename = os.path.basename(name)
1218    if basename:
1219      installed_files.add(basename)
1220
1221  for line in tf_zip.read("META/apkcerts.txt").split("\n"):
1222    line = line.strip()
1223    if not line:
1224      continue
1225    m = re.match(
1226        r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
1227        r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
1228        line)
1229    if not m:
1230      continue
1231
1232    matches = m.groupdict()
1233    cert = matches["CERT"]
1234    privkey = matches["PRIVKEY"]
1235    name = matches["NAME"]
1236    this_compressed_extension = matches["COMPRESSED"]
1237
1238    public_key_suffix_len = len(OPTIONS.public_key_suffix)
1239    private_key_suffix_len = len(OPTIONS.private_key_suffix)
1240    if cert in SPECIAL_CERT_STRINGS and not privkey:
1241      certmap[name] = cert
1242    elif (cert.endswith(OPTIONS.public_key_suffix) and
1243          privkey.endswith(OPTIONS.private_key_suffix) and
1244          cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1245      certmap[name] = cert[:-public_key_suffix_len]
1246    else:
1247      raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1248
1249    if not this_compressed_extension:
1250      continue
1251
1252    # Only count the installed files.
1253    filename = name + '.' + this_compressed_extension
1254    if filename not in installed_files:
1255      continue
1256
1257    # Make sure that all the values in the compression map have the same
1258    # extension. We don't support multiple compression methods in the same
1259    # system image.
1260    if compressed_extension:
1261      if this_compressed_extension != compressed_extension:
1262        raise ValueError(
1263            "Multiple compressed extensions: {} vs {}".format(
1264                compressed_extension, this_compressed_extension))
1265    else:
1266      compressed_extension = this_compressed_extension
1267
1268  return (certmap,
1269          ("." + compressed_extension) if compressed_extension else None)
1270
1271
1272COMMON_DOCSTRING = """
1273Global options
1274
1275  -p  (--path) <dir>
1276      Prepend <dir>/bin to the list of places to search for binaries run by this
1277      script, and expect to find jars in <dir>/framework.
1278
1279  -s  (--device_specific) <file>
1280      Path to the Python module containing device-specific releasetools code.
1281
1282  -x  (--extra) <key=value>
1283      Add a key/value pair to the 'extras' dict, which device-specific extension
1284      code may look at.
1285
1286  -v  (--verbose)
1287      Show command lines being executed.
1288
1289  -h  (--help)
1290      Display this usage message and exit.
1291"""
1292
1293def Usage(docstring):
1294  print(docstring.rstrip("\n"))
1295  print(COMMON_DOCSTRING)
1296
1297
1298def ParseOptions(argv,
1299                 docstring,
1300                 extra_opts="", extra_long_opts=(),
1301                 extra_option_handler=None):
1302  """Parse the options in argv and return any arguments that aren't
1303  flags.  docstring is the calling module's docstring, to be displayed
1304  for errors and -h.  extra_opts and extra_long_opts are for flags
1305  defined by the caller, which are processed by passing them to
1306  extra_option_handler."""
1307
1308  try:
1309    opts, args = getopt.getopt(
1310        argv, "hvp:s:x:" + extra_opts,
1311        ["help", "verbose", "path=", "signapk_path=",
1312         "signapk_shared_library_path=", "extra_signapk_args=",
1313         "java_path=", "java_args=", "public_key_suffix=",
1314         "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1315         "verity_signer_path=", "verity_signer_args=", "device_specific=",
1316         "extra="] +
1317        list(extra_long_opts))
1318  except getopt.GetoptError as err:
1319    Usage(docstring)
1320    print("**", str(err), "**")
1321    sys.exit(2)
1322
1323  for o, a in opts:
1324    if o in ("-h", "--help"):
1325      Usage(docstring)
1326      sys.exit()
1327    elif o in ("-v", "--verbose"):
1328      OPTIONS.verbose = True
1329    elif o in ("-p", "--path"):
1330      OPTIONS.search_path = a
1331    elif o in ("--signapk_path",):
1332      OPTIONS.signapk_path = a
1333    elif o in ("--signapk_shared_library_path",):
1334      OPTIONS.signapk_shared_library_path = a
1335    elif o in ("--extra_signapk_args",):
1336      OPTIONS.extra_signapk_args = shlex.split(a)
1337    elif o in ("--java_path",):
1338      OPTIONS.java_path = a
1339    elif o in ("--java_args",):
1340      OPTIONS.java_args = shlex.split(a)
1341    elif o in ("--public_key_suffix",):
1342      OPTIONS.public_key_suffix = a
1343    elif o in ("--private_key_suffix",):
1344      OPTIONS.private_key_suffix = a
1345    elif o in ("--boot_signer_path",):
1346      OPTIONS.boot_signer_path = a
1347    elif o in ("--boot_signer_args",):
1348      OPTIONS.boot_signer_args = shlex.split(a)
1349    elif o in ("--verity_signer_path",):
1350      OPTIONS.verity_signer_path = a
1351    elif o in ("--verity_signer_args",):
1352      OPTIONS.verity_signer_args = shlex.split(a)
1353    elif o in ("-s", "--device_specific"):
1354      OPTIONS.device_specific = a
1355    elif o in ("-x", "--extra"):
1356      key, value = a.split("=", 1)
1357      OPTIONS.extras[key] = value
1358    else:
1359      if extra_option_handler is None or not extra_option_handler(o, a):
1360        assert False, "unknown option \"%s\"" % (o,)
1361
1362  if OPTIONS.search_path:
1363    os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1364                          os.pathsep + os.environ["PATH"])
1365
1366  return args
1367
1368
1369def MakeTempFile(prefix='tmp', suffix=''):
1370  """Make a temp file and add it to the list of things to be deleted
1371  when Cleanup() is called.  Return the filename."""
1372  fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1373  os.close(fd)
1374  OPTIONS.tempfiles.append(fn)
1375  return fn
1376
1377
1378def MakeTempDir(prefix='tmp', suffix=''):
1379  """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1380
1381  Returns:
1382    The absolute pathname of the new directory.
1383  """
1384  dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1385  OPTIONS.tempfiles.append(dir_name)
1386  return dir_name
1387
1388
1389def Cleanup():
1390  for i in OPTIONS.tempfiles:
1391    if os.path.isdir(i):
1392      shutil.rmtree(i, ignore_errors=True)
1393    else:
1394      os.remove(i)
1395  del OPTIONS.tempfiles[:]
1396
1397
1398class PasswordManager(object):
1399  def __init__(self):
1400    self.editor = os.getenv("EDITOR")
1401    self.pwfile = os.getenv("ANDROID_PW_FILE")
1402
1403  def GetPasswords(self, items):
1404    """Get passwords corresponding to each string in 'items',
1405    returning a dict.  (The dict may have keys in addition to the
1406    values in 'items'.)
1407
1408    Uses the passwords in $ANDROID_PW_FILE if available, letting the
1409    user edit that file to add more needed passwords.  If no editor is
1410    available, or $ANDROID_PW_FILE isn't define, prompts the user
1411    interactively in the ordinary way.
1412    """
1413
1414    current = self.ReadFile()
1415
1416    first = True
1417    while True:
1418      missing = []
1419      for i in items:
1420        if i not in current or not current[i]:
1421          missing.append(i)
1422      # Are all the passwords already in the file?
1423      if not missing:
1424        return current
1425
1426      for i in missing:
1427        current[i] = ""
1428
1429      if not first:
1430        print("key file %s still missing some passwords." % (self.pwfile,))
1431        answer = raw_input("try to edit again? [y]> ").strip()
1432        if answer and answer[0] not in 'yY':
1433          raise RuntimeError("key passwords unavailable")
1434      first = False
1435
1436      current = self.UpdateAndReadFile(current)
1437
1438  def PromptResult(self, current): # pylint: disable=no-self-use
1439    """Prompt the user to enter a value (password) for each key in
1440    'current' whose value is fales.  Returns a new dict with all the
1441    values.
1442    """
1443    result = {}
1444    for k, v in sorted(current.iteritems()):
1445      if v:
1446        result[k] = v
1447      else:
1448        while True:
1449          result[k] = getpass.getpass(
1450              "Enter password for %s key> " % k).strip()
1451          if result[k]:
1452            break
1453    return result
1454
1455  def UpdateAndReadFile(self, current):
1456    if not self.editor or not self.pwfile:
1457      return self.PromptResult(current)
1458
1459    f = open(self.pwfile, "w")
1460    os.chmod(self.pwfile, 0o600)
1461    f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1462    f.write("# (Additional spaces are harmless.)\n\n")
1463
1464    first_line = None
1465    sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1466    for i, (_, k, v) in enumerate(sorted_list):
1467      f.write("[[[  %s  ]]] %s\n" % (v, k))
1468      if not v and first_line is None:
1469        # position cursor on first line with no password.
1470        first_line = i + 4
1471    f.close()
1472
1473    RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
1474
1475    return self.ReadFile()
1476
1477  def ReadFile(self):
1478    result = {}
1479    if self.pwfile is None:
1480      return result
1481    try:
1482      f = open(self.pwfile, "r")
1483      for line in f:
1484        line = line.strip()
1485        if not line or line[0] == '#':
1486          continue
1487        m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1488        if not m:
1489          logger.warning("Failed to parse password file: %s", line)
1490        else:
1491          result[m.group(2)] = m.group(1)
1492      f.close()
1493    except IOError as e:
1494      if e.errno != errno.ENOENT:
1495        logger.exception("Error reading password file:")
1496    return result
1497
1498
1499def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1500             compress_type=None):
1501  import datetime
1502
1503  # http://b/18015246
1504  # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1505  # for files larger than 2GiB. We can work around this by adjusting their
1506  # limit. Note that `zipfile.writestr()` will not work for strings larger than
1507  # 2GiB. The Python interpreter sometimes rejects strings that large (though
1508  # it isn't clear to me exactly what circumstances cause this).
1509  # `zipfile.write()` must be used directly to work around this.
1510  #
1511  # This mess can be avoided if we port to python3.
1512  saved_zip64_limit = zipfile.ZIP64_LIMIT
1513  zipfile.ZIP64_LIMIT = (1 << 32) - 1
1514
1515  if compress_type is None:
1516    compress_type = zip_file.compression
1517  if arcname is None:
1518    arcname = filename
1519
1520  saved_stat = os.stat(filename)
1521
1522  try:
1523    # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1524    # file to be zipped and reset it when we're done.
1525    os.chmod(filename, perms)
1526
1527    # Use a fixed timestamp so the output is repeatable.
1528    # Note: Use of fromtimestamp rather than utcfromtimestamp here is
1529    # intentional. zip stores datetimes in local time without a time zone
1530    # attached, so we need "epoch" but in the local time zone to get 2009/01/01
1531    # in the zip archive.
1532    local_epoch = datetime.datetime.fromtimestamp(0)
1533    timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
1534    os.utime(filename, (timestamp, timestamp))
1535
1536    zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1537  finally:
1538    os.chmod(filename, saved_stat.st_mode)
1539    os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1540    zipfile.ZIP64_LIMIT = saved_zip64_limit
1541
1542
1543def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
1544                compress_type=None):
1545  """Wrap zipfile.writestr() function to work around the zip64 limit.
1546
1547  Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1548  longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1549  when calling crc32(bytes).
1550
1551  But it still works fine to write a shorter string into a large zip file.
1552  We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1553  when we know the string won't be too long.
1554  """
1555
1556  saved_zip64_limit = zipfile.ZIP64_LIMIT
1557  zipfile.ZIP64_LIMIT = (1 << 32) - 1
1558
1559  if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1560    zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
1561    zinfo.compress_type = zip_file.compression
1562    if perms is None:
1563      perms = 0o100644
1564  else:
1565    zinfo = zinfo_or_arcname
1566
1567  # If compress_type is given, it overrides the value in zinfo.
1568  if compress_type is not None:
1569    zinfo.compress_type = compress_type
1570
1571  # If perms is given, it has a priority.
1572  if perms is not None:
1573    # If perms doesn't set the file type, mark it as a regular file.
1574    if perms & 0o770000 == 0:
1575      perms |= 0o100000
1576    zinfo.external_attr = perms << 16
1577
1578  # Use a fixed timestamp so the output is repeatable.
1579  zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1580
1581  zip_file.writestr(zinfo, data)
1582  zipfile.ZIP64_LIMIT = saved_zip64_limit
1583
1584
1585def ZipDelete(zip_filename, entries):
1586  """Deletes entries from a ZIP file.
1587
1588  Since deleting entries from a ZIP file is not supported, it shells out to
1589  'zip -d'.
1590
1591  Args:
1592    zip_filename: The name of the ZIP file.
1593    entries: The name of the entry, or the list of names to be deleted.
1594
1595  Raises:
1596    AssertionError: In case of non-zero return from 'zip'.
1597  """
1598  if isinstance(entries, basestring):
1599    entries = [entries]
1600  cmd = ["zip", "-d", zip_filename] + entries
1601  RunAndCheckOutput(cmd)
1602
1603
1604def ZipClose(zip_file):
1605  # http://b/18015246
1606  # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1607  # central directory.
1608  saved_zip64_limit = zipfile.ZIP64_LIMIT
1609  zipfile.ZIP64_LIMIT = (1 << 32) - 1
1610
1611  zip_file.close()
1612
1613  zipfile.ZIP64_LIMIT = saved_zip64_limit
1614
1615
1616class DeviceSpecificParams(object):
1617  module = None
1618  def __init__(self, **kwargs):
1619    """Keyword arguments to the constructor become attributes of this
1620    object, which is passed to all functions in the device-specific
1621    module."""
1622    for k, v in kwargs.iteritems():
1623      setattr(self, k, v)
1624    self.extras = OPTIONS.extras
1625
1626    if self.module is None:
1627      path = OPTIONS.device_specific
1628      if not path:
1629        return
1630      try:
1631        if os.path.isdir(path):
1632          info = imp.find_module("releasetools", [path])
1633        else:
1634          d, f = os.path.split(path)
1635          b, x = os.path.splitext(f)
1636          if x == ".py":
1637            f = b
1638          info = imp.find_module(f, [d])
1639        logger.info("loaded device-specific extensions from %s", path)
1640        self.module = imp.load_module("device_specific", *info)
1641      except ImportError:
1642        logger.info("unable to load device-specific module; assuming none")
1643
1644  def _DoCall(self, function_name, *args, **kwargs):
1645    """Call the named function in the device-specific module, passing
1646    the given args and kwargs.  The first argument to the call will be
1647    the DeviceSpecific object itself.  If there is no module, or the
1648    module does not define the function, return the value of the
1649    'default' kwarg (which itself defaults to None)."""
1650    if self.module is None or not hasattr(self.module, function_name):
1651      return kwargs.get("default")
1652    return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1653
1654  def FullOTA_Assertions(self):
1655    """Called after emitting the block of assertions at the top of a
1656    full OTA package.  Implementations can add whatever additional
1657    assertions they like."""
1658    return self._DoCall("FullOTA_Assertions")
1659
1660  def FullOTA_InstallBegin(self):
1661    """Called at the start of full OTA installation."""
1662    return self._DoCall("FullOTA_InstallBegin")
1663
1664  def FullOTA_GetBlockDifferences(self):
1665    """Called during full OTA installation and verification.
1666    Implementation should return a list of BlockDifference objects describing
1667    the update on each additional partitions.
1668    """
1669    return self._DoCall("FullOTA_GetBlockDifferences")
1670
1671  def FullOTA_InstallEnd(self):
1672    """Called at the end of full OTA installation; typically this is
1673    used to install the image for the device's baseband processor."""
1674    return self._DoCall("FullOTA_InstallEnd")
1675
1676  def IncrementalOTA_Assertions(self):
1677    """Called after emitting the block of assertions at the top of an
1678    incremental OTA package.  Implementations can add whatever
1679    additional assertions they like."""
1680    return self._DoCall("IncrementalOTA_Assertions")
1681
1682  def IncrementalOTA_VerifyBegin(self):
1683    """Called at the start of the verification phase of incremental
1684    OTA installation; additional checks can be placed here to abort
1685    the script before any changes are made."""
1686    return self._DoCall("IncrementalOTA_VerifyBegin")
1687
1688  def IncrementalOTA_VerifyEnd(self):
1689    """Called at the end of the verification phase of incremental OTA
1690    installation; additional checks can be placed here to abort the
1691    script before any changes are made."""
1692    return self._DoCall("IncrementalOTA_VerifyEnd")
1693
1694  def IncrementalOTA_InstallBegin(self):
1695    """Called at the start of incremental OTA installation (after
1696    verification is complete)."""
1697    return self._DoCall("IncrementalOTA_InstallBegin")
1698
1699  def IncrementalOTA_GetBlockDifferences(self):
1700    """Called during incremental OTA installation and verification.
1701    Implementation should return a list of BlockDifference objects describing
1702    the update on each additional partitions.
1703    """
1704    return self._DoCall("IncrementalOTA_GetBlockDifferences")
1705
1706  def IncrementalOTA_InstallEnd(self):
1707    """Called at the end of incremental OTA installation; typically
1708    this is used to install the image for the device's baseband
1709    processor."""
1710    return self._DoCall("IncrementalOTA_InstallEnd")
1711
1712  def VerifyOTA_Assertions(self):
1713    return self._DoCall("VerifyOTA_Assertions")
1714
1715
1716class File(object):
1717  def __init__(self, name, data, compress_size=None):
1718    self.name = name
1719    self.data = data
1720    self.size = len(data)
1721    self.compress_size = compress_size or self.size
1722    self.sha1 = sha1(data).hexdigest()
1723
1724  @classmethod
1725  def FromLocalFile(cls, name, diskname):
1726    f = open(diskname, "rb")
1727    data = f.read()
1728    f.close()
1729    return File(name, data)
1730
1731  def WriteToTemp(self):
1732    t = tempfile.NamedTemporaryFile()
1733    t.write(self.data)
1734    t.flush()
1735    return t
1736
1737  def WriteToDir(self, d):
1738    with open(os.path.join(d, self.name), "wb") as fp:
1739      fp.write(self.data)
1740
1741  def AddToZip(self, z, compression=None):
1742    ZipWriteStr(z, self.name, self.data, compress_type=compression)
1743
1744
1745DIFF_PROGRAM_BY_EXT = {
1746    ".gz" : "imgdiff",
1747    ".zip" : ["imgdiff", "-z"],
1748    ".jar" : ["imgdiff", "-z"],
1749    ".apk" : ["imgdiff", "-z"],
1750    ".img" : "imgdiff",
1751    }
1752
1753
1754class Difference(object):
1755  def __init__(self, tf, sf, diff_program=None):
1756    self.tf = tf
1757    self.sf = sf
1758    self.patch = None
1759    self.diff_program = diff_program
1760
1761  def ComputePatch(self):
1762    """Compute the patch (as a string of data) needed to turn sf into
1763    tf.  Returns the same tuple as GetPatch()."""
1764
1765    tf = self.tf
1766    sf = self.sf
1767
1768    if self.diff_program:
1769      diff_program = self.diff_program
1770    else:
1771      ext = os.path.splitext(tf.name)[1]
1772      diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
1773
1774    ttemp = tf.WriteToTemp()
1775    stemp = sf.WriteToTemp()
1776
1777    ext = os.path.splitext(tf.name)[1]
1778
1779    try:
1780      ptemp = tempfile.NamedTemporaryFile()
1781      if isinstance(diff_program, list):
1782        cmd = copy.copy(diff_program)
1783      else:
1784        cmd = [diff_program]
1785      cmd.append(stemp.name)
1786      cmd.append(ttemp.name)
1787      cmd.append(ptemp.name)
1788      p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1789      err = []
1790      def run():
1791        _, e = p.communicate()
1792        if e:
1793          err.append(e)
1794      th = threading.Thread(target=run)
1795      th.start()
1796      th.join(timeout=300)   # 5 mins
1797      if th.is_alive():
1798        logger.warning("diff command timed out")
1799        p.terminate()
1800        th.join(5)
1801        if th.is_alive():
1802          p.kill()
1803          th.join()
1804
1805      if p.returncode != 0:
1806        logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
1807        self.patch = None
1808        return None, None, None
1809      diff = ptemp.read()
1810    finally:
1811      ptemp.close()
1812      stemp.close()
1813      ttemp.close()
1814
1815    self.patch = diff
1816    return self.tf, self.sf, self.patch
1817
1818
1819  def GetPatch(self):
1820    """Returns a tuple of (target_file, source_file, patch_data).
1821
1822    patch_data may be None if ComputePatch hasn't been called, or if
1823    computing the patch failed.
1824    """
1825    return self.tf, self.sf, self.patch
1826
1827
1828def ComputeDifferences(diffs):
1829  """Call ComputePatch on all the Difference objects in 'diffs'."""
1830  logger.info("%d diffs to compute", len(diffs))
1831
1832  # Do the largest files first, to try and reduce the long-pole effect.
1833  by_size = [(i.tf.size, i) for i in diffs]
1834  by_size.sort(reverse=True)
1835  by_size = [i[1] for i in by_size]
1836
1837  lock = threading.Lock()
1838  diff_iter = iter(by_size)   # accessed under lock
1839
1840  def worker():
1841    try:
1842      lock.acquire()
1843      for d in diff_iter:
1844        lock.release()
1845        start = time.time()
1846        d.ComputePatch()
1847        dur = time.time() - start
1848        lock.acquire()
1849
1850        tf, sf, patch = d.GetPatch()
1851        if sf.name == tf.name:
1852          name = tf.name
1853        else:
1854          name = "%s (%s)" % (tf.name, sf.name)
1855        if patch is None:
1856          logger.error("patching failed! %40s", name)
1857        else:
1858          logger.info(
1859              "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
1860              tf.size, 100.0 * len(patch) / tf.size, name)
1861      lock.release()
1862    except Exception:
1863      logger.exception("Failed to compute diff from worker")
1864      raise
1865
1866  # start worker threads; wait for them all to finish.
1867  threads = [threading.Thread(target=worker)
1868             for i in range(OPTIONS.worker_threads)]
1869  for th in threads:
1870    th.start()
1871  while threads:
1872    threads.pop().join()
1873
1874
1875class BlockDifference(object):
1876  def __init__(self, partition, tgt, src=None, check_first_block=False,
1877               version=None, disable_imgdiff=False):
1878    self.tgt = tgt
1879    self.src = src
1880    self.partition = partition
1881    self.check_first_block = check_first_block
1882    self.disable_imgdiff = disable_imgdiff
1883
1884    if version is None:
1885      version = max(
1886          int(i) for i in
1887          OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1888    assert version >= 3
1889    self.version = version
1890
1891    b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
1892                                    version=self.version,
1893                                    disable_imgdiff=self.disable_imgdiff)
1894    self.path = os.path.join(MakeTempDir(), partition)
1895    b.Compute(self.path)
1896    self._required_cache = b.max_stashed_size
1897    self.touched_src_ranges = b.touched_src_ranges
1898    self.touched_src_sha1 = b.touched_src_sha1
1899
1900    # On devices with dynamic partitions, for new partitions,
1901    # src is None but OPTIONS.source_info_dict is not.
1902    if OPTIONS.source_info_dict is None:
1903      is_dynamic_build = OPTIONS.info_dict.get(
1904          "use_dynamic_partitions") == "true"
1905      is_dynamic_source = False
1906    else:
1907      is_dynamic_build = OPTIONS.source_info_dict.get(
1908          "use_dynamic_partitions") == "true"
1909      is_dynamic_source = partition in shlex.split(
1910          OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
1911
1912    is_dynamic_target = partition in shlex.split(
1913        OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
1914
1915    # For dynamic partitions builds, check partition list in both source
1916    # and target build because new partitions may be added, and existing
1917    # partitions may be removed.
1918    is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
1919
1920    if is_dynamic:
1921      self.device = 'map_partition("%s")' % partition
1922    else:
1923      if OPTIONS.source_info_dict is None:
1924        _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1925      else:
1926        _, device_path = GetTypeAndDevice("/" + partition,
1927                                          OPTIONS.source_info_dict)
1928      self.device = '"%s"' % device_path
1929
1930  @property
1931  def required_cache(self):
1932    return self._required_cache
1933
1934  def WriteScript(self, script, output_zip, progress=None,
1935                  write_verify_script=False):
1936    if not self.src:
1937      # write the output unconditionally
1938      script.Print("Patching %s image unconditionally..." % (self.partition,))
1939    else:
1940      script.Print("Patching %s image after verification." % (self.partition,))
1941
1942    if progress:
1943      script.ShowProgress(progress, 0)
1944    self._WriteUpdate(script, output_zip)
1945
1946    if write_verify_script:
1947      self.WritePostInstallVerifyScript(script)
1948
1949  def WriteStrictVerifyScript(self, script):
1950    """Verify all the blocks in the care_map, including clobbered blocks.
1951
1952    This differs from the WriteVerifyScript() function: a) it prints different
1953    error messages; b) it doesn't allow half-way updated images to pass the
1954    verification."""
1955
1956    partition = self.partition
1957    script.Print("Verifying %s..." % (partition,))
1958    ranges = self.tgt.care_map
1959    ranges_str = ranges.to_string_raw()
1960    script.AppendExtra(
1961        'range_sha1(%s, "%s") == "%s" && ui_print("    Verified.") || '
1962        'ui_print("%s has unexpected contents.");' % (
1963            self.device, ranges_str,
1964            self.tgt.TotalSha1(include_clobbered_blocks=True),
1965            self.partition))
1966    script.AppendExtra("")
1967
1968  def WriteVerifyScript(self, script, touched_blocks_only=False):
1969    partition = self.partition
1970
1971    # full OTA
1972    if not self.src:
1973      script.Print("Image %s will be patched unconditionally." % (partition,))
1974
1975    # incremental OTA
1976    else:
1977      if touched_blocks_only:
1978        ranges = self.touched_src_ranges
1979        expected_sha1 = self.touched_src_sha1
1980      else:
1981        ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1982        expected_sha1 = self.src.TotalSha1()
1983
1984      # No blocks to be checked, skipping.
1985      if not ranges:
1986        return
1987
1988      ranges_str = ranges.to_string_raw()
1989      script.AppendExtra(
1990          'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
1991          'package_extract_file("%s.transfer.list"), "%s.new.dat", '
1992          '"%s.patch.dat")) then' % (
1993              self.device, ranges_str, expected_sha1,
1994              self.device, partition, partition, partition))
1995      script.Print('Verified %s image...' % (partition,))
1996      script.AppendExtra('else')
1997
1998      if self.version >= 4:
1999
2000        # Bug: 21124327
2001        # When generating incrementals for the system and vendor partitions in
2002        # version 4 or newer, explicitly check the first block (which contains
2003        # the superblock) of the partition to see if it's what we expect. If
2004        # this check fails, give an explicit log message about the partition
2005        # having been remounted R/W (the most likely explanation).
2006        if self.check_first_block:
2007          script.AppendExtra('check_first_block(%s);' % (self.device,))
2008
2009        # If version >= 4, try block recovery before abort update
2010        if partition == "system":
2011          code = ErrorCode.SYSTEM_RECOVER_FAILURE
2012        else:
2013          code = ErrorCode.VENDOR_RECOVER_FAILURE
2014        script.AppendExtra((
2015            'ifelse (block_image_recover({device}, "{ranges}") && '
2016            'block_image_verify({device}, '
2017            'package_extract_file("{partition}.transfer.list"), '
2018            '"{partition}.new.dat", "{partition}.patch.dat"), '
2019            'ui_print("{partition} recovered successfully."), '
2020            'abort("E{code}: {partition} partition fails to recover"));\n'
2021            'endif;').format(device=self.device, ranges=ranges_str,
2022                             partition=partition, code=code))
2023
2024      # Abort the OTA update. Note that the incremental OTA cannot be applied
2025      # even if it may match the checksum of the target partition.
2026      # a) If version < 3, operations like move and erase will make changes
2027      #    unconditionally and damage the partition.
2028      # b) If version >= 3, it won't even reach here.
2029      else:
2030        if partition == "system":
2031          code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2032        else:
2033          code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2034        script.AppendExtra((
2035            'abort("E%d: %s partition has unexpected contents");\n'
2036            'endif;') % (code, partition))
2037
2038  def WritePostInstallVerifyScript(self, script):
2039    partition = self.partition
2040    script.Print('Verifying the updated %s image...' % (partition,))
2041    # Unlike pre-install verification, clobbered_blocks should not be ignored.
2042    ranges = self.tgt.care_map
2043    ranges_str = ranges.to_string_raw()
2044    script.AppendExtra(
2045        'if range_sha1(%s, "%s") == "%s" then' % (
2046            self.device, ranges_str,
2047            self.tgt.TotalSha1(include_clobbered_blocks=True)))
2048
2049    # Bug: 20881595
2050    # Verify that extended blocks are really zeroed out.
2051    if self.tgt.extended:
2052      ranges_str = self.tgt.extended.to_string_raw()
2053      script.AppendExtra(
2054          'if range_sha1(%s, "%s") == "%s" then' % (
2055              self.device, ranges_str,
2056              self._HashZeroBlocks(self.tgt.extended.size())))
2057      script.Print('Verified the updated %s image.' % (partition,))
2058      if partition == "system":
2059        code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2060      else:
2061        code = ErrorCode.VENDOR_NONZERO_CONTENTS
2062      script.AppendExtra(
2063          'else\n'
2064          '  abort("E%d: %s partition has unexpected non-zero contents after '
2065          'OTA update");\n'
2066          'endif;' % (code, partition))
2067    else:
2068      script.Print('Verified the updated %s image.' % (partition,))
2069
2070    if partition == "system":
2071      code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2072    else:
2073      code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2074
2075    script.AppendExtra(
2076        'else\n'
2077        '  abort("E%d: %s partition has unexpected contents after OTA '
2078        'update");\n'
2079        'endif;' % (code, partition))
2080
2081  def _WriteUpdate(self, script, output_zip):
2082    ZipWrite(output_zip,
2083             '{}.transfer.list'.format(self.path),
2084             '{}.transfer.list'.format(self.partition))
2085
2086    # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2087    # its size. Quailty 9 almost triples the compression time but doesn't
2088    # further reduce the size too much. For a typical 1.8G system.new.dat
2089    #                       zip  | brotli(quality 6)  | brotli(quality 9)
2090    #   compressed_size:    942M | 869M (~8% reduced) | 854M
2091    #   compression_time:   75s  | 265s               | 719s
2092    #   decompression_time: 15s  | 25s                | 25s
2093
2094    if not self.src:
2095      brotli_cmd = ['brotli', '--quality=6',
2096                    '--output={}.new.dat.br'.format(self.path),
2097                    '{}.new.dat'.format(self.path)]
2098      print("Compressing {}.new.dat with brotli".format(self.partition))
2099      RunAndCheckOutput(brotli_cmd)
2100
2101      new_data_name = '{}.new.dat.br'.format(self.partition)
2102      ZipWrite(output_zip,
2103               '{}.new.dat.br'.format(self.path),
2104               new_data_name,
2105               compress_type=zipfile.ZIP_STORED)
2106    else:
2107      new_data_name = '{}.new.dat'.format(self.partition)
2108      ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2109
2110    ZipWrite(output_zip,
2111             '{}.patch.dat'.format(self.path),
2112             '{}.patch.dat'.format(self.partition),
2113             compress_type=zipfile.ZIP_STORED)
2114
2115    if self.partition == "system":
2116      code = ErrorCode.SYSTEM_UPDATE_FAILURE
2117    else:
2118      code = ErrorCode.VENDOR_UPDATE_FAILURE
2119
2120    call = ('block_image_update({device}, '
2121            'package_extract_file("{partition}.transfer.list"), '
2122            '"{new_data_name}", "{partition}.patch.dat") ||\n'
2123            '  abort("E{code}: Failed to update {partition} image.");'.format(
2124                device=self.device, partition=self.partition,
2125                new_data_name=new_data_name, code=code))
2126    script.AppendExtra(script.WordWrap(call))
2127
2128  def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
2129    data = source.ReadRangeSet(ranges)
2130    ctx = sha1()
2131
2132    for p in data:
2133      ctx.update(p)
2134
2135    return ctx.hexdigest()
2136
2137  def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2138    """Return the hash value for all zero blocks."""
2139    zero_block = '\x00' * 4096
2140    ctx = sha1()
2141    for _ in range(num_blocks):
2142      ctx.update(zero_block)
2143
2144    return ctx.hexdigest()
2145
2146
2147DataImage = blockimgdiff.DataImage
2148EmptyImage = blockimgdiff.EmptyImage
2149
2150# map recovery.fstab's fs_types to mount/format "partition types"
2151PARTITION_TYPES = {
2152    "ext4": "EMMC",
2153    "emmc": "EMMC",
2154    "f2fs": "EMMC",
2155    "squashfs": "EMMC"
2156}
2157
2158
2159def GetTypeAndDevice(mount_point, info):
2160  fstab = info["fstab"]
2161  if fstab:
2162    return (PARTITION_TYPES[fstab[mount_point].fs_type],
2163            fstab[mount_point].device)
2164  else:
2165    raise KeyError
2166
2167
2168def ParseCertificate(data):
2169  """Parses and converts a PEM-encoded certificate into DER-encoded.
2170
2171  This gives the same result as `openssl x509 -in <filename> -outform DER`.
2172
2173  Returns:
2174    The decoded certificate string.
2175  """
2176  cert_buffer = []
2177  save = False
2178  for line in data.split("\n"):
2179    if "--END CERTIFICATE--" in line:
2180      break
2181    if save:
2182      cert_buffer.append(line)
2183    if "--BEGIN CERTIFICATE--" in line:
2184      save = True
2185  cert = "".join(cert_buffer).decode('base64')
2186  return cert
2187
2188
2189def ExtractPublicKey(cert):
2190  """Extracts the public key (PEM-encoded) from the given certificate file.
2191
2192  Args:
2193    cert: The certificate filename.
2194
2195  Returns:
2196    The public key string.
2197
2198  Raises:
2199    AssertionError: On non-zero return from 'openssl'.
2200  """
2201  # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2202  # While openssl 1.1 writes the key into the given filename followed by '-out',
2203  # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2204  # stdout instead.
2205  cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2206  proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2207  pubkey, stderrdata = proc.communicate()
2208  assert proc.returncode == 0, \
2209      'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2210  return pubkey
2211
2212
2213def ExtractAvbPublicKey(key):
2214  """Extracts the AVB public key from the given public or private key.
2215
2216  Args:
2217    key: The input key file, which should be PEM-encoded public or private key.
2218
2219  Returns:
2220    The path to the extracted AVB public key file.
2221  """
2222  output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
2223  RunAndCheckOutput(
2224      ['avbtool', 'extract_public_key', "--key", key, "--output", output])
2225  return output
2226
2227
2228def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2229                      info_dict=None):
2230  """Generates the recovery-from-boot patch and writes the script to output.
2231
2232  Most of the space in the boot and recovery images is just the kernel, which is
2233  identical for the two, so the resulting patch should be efficient. Add it to
2234  the output zip, along with a shell script that is run from init.rc on first
2235  boot to actually do the patching and install the new recovery image.
2236
2237  Args:
2238    input_dir: The top-level input directory of the target-files.zip.
2239    output_sink: The callback function that writes the result.
2240    recovery_img: File object for the recovery image.
2241    boot_img: File objects for the boot image.
2242    info_dict: A dict returned by common.LoadInfoDict() on the input
2243        target_files. Will use OPTIONS.info_dict if None has been given.
2244  """
2245  if info_dict is None:
2246    info_dict = OPTIONS.info_dict
2247
2248  full_recovery_image = info_dict.get("full_recovery_image") == "true"
2249
2250  if full_recovery_image:
2251    output_sink("etc/recovery.img", recovery_img.data)
2252
2253  else:
2254    system_root_image = info_dict.get("system_root_image") == "true"
2255    path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
2256    # With system-root-image, boot and recovery images will have mismatching
2257    # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2258    # to handle such a case.
2259    if system_root_image:
2260      diff_program = ["bsdiff"]
2261      bonus_args = ""
2262      assert not os.path.exists(path)
2263    else:
2264      diff_program = ["imgdiff"]
2265      if os.path.exists(path):
2266        diff_program.append("-b")
2267        diff_program.append(path)
2268        bonus_args = "--bonus /system/etc/recovery-resource.dat"
2269      else:
2270        bonus_args = ""
2271
2272    d = Difference(recovery_img, boot_img, diff_program=diff_program)
2273    _, _, patch = d.ComputePatch()
2274    output_sink("recovery-from-boot.p", patch)
2275
2276  try:
2277    # The following GetTypeAndDevice()s need to use the path in the target
2278    # info_dict instead of source_info_dict.
2279    boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2280    recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2281  except KeyError:
2282    return
2283
2284  if full_recovery_image:
2285    sh = """#!/system/bin/sh
2286if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2287  applypatch \\
2288          --flash /system/etc/recovery.img \\
2289          --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2290      log -t recovery "Installing new recovery image: succeeded" || \\
2291      log -t recovery "Installing new recovery image: failed"
2292else
2293  log -t recovery "Recovery image already installed"
2294fi
2295""" % {'type': recovery_type,
2296       'device': recovery_device,
2297       'sha1': recovery_img.sha1,
2298       'size': recovery_img.size}
2299  else:
2300    sh = """#!/system/bin/sh
2301if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2302  applypatch %(bonus_args)s \\
2303          --patch /system/recovery-from-boot.p \\
2304          --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2305          --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2306      log -t recovery "Installing new recovery image: succeeded" || \\
2307      log -t recovery "Installing new recovery image: failed"
2308else
2309  log -t recovery "Recovery image already installed"
2310fi
2311""" % {'boot_size': boot_img.size,
2312       'boot_sha1': boot_img.sha1,
2313       'recovery_size': recovery_img.size,
2314       'recovery_sha1': recovery_img.sha1,
2315       'boot_type': boot_type,
2316       'boot_device': boot_device,
2317       'recovery_type': recovery_type,
2318       'recovery_device': recovery_device,
2319       'bonus_args': bonus_args}
2320
2321  # The install script location moved from /system/etc to /system/bin
2322  # in the L release.
2323  sh_location = "bin/install-recovery.sh"
2324
2325  logger.info("putting script in %s", sh_location)
2326
2327  output_sink(sh_location, sh)
2328
2329
2330class DynamicPartitionUpdate(object):
2331  def __init__(self, src_group=None, tgt_group=None, progress=None,
2332               block_difference=None):
2333    self.src_group = src_group
2334    self.tgt_group = tgt_group
2335    self.progress = progress
2336    self.block_difference = block_difference
2337
2338  @property
2339  def src_size(self):
2340    if not self.block_difference:
2341      return 0
2342    return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
2343
2344  @property
2345  def tgt_size(self):
2346    if not self.block_difference:
2347      return 0
2348    return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
2349
2350  @staticmethod
2351  def _GetSparseImageSize(img):
2352    if not img:
2353      return 0
2354    return img.blocksize * img.total_blocks
2355
2356
2357class DynamicGroupUpdate(object):
2358  def __init__(self, src_size=None, tgt_size=None):
2359    # None: group does not exist. 0: no size limits.
2360    self.src_size = src_size
2361    self.tgt_size = tgt_size
2362
2363
2364class DynamicPartitionsDifference(object):
2365  def __init__(self, info_dict, block_diffs, progress_dict=None,
2366               source_info_dict=None):
2367    if progress_dict is None:
2368      progress_dict = dict()
2369
2370    self._remove_all_before_apply = False
2371    if source_info_dict is None:
2372      self._remove_all_before_apply = True
2373      source_info_dict = dict()
2374
2375    block_diff_dict = {e.partition:e for e in block_diffs}
2376    assert len(block_diff_dict) == len(block_diffs), \
2377        "Duplicated BlockDifference object for {}".format(
2378            [partition for partition, count in
2379             collections.Counter(e.partition for e in block_diffs).items()
2380             if count > 1])
2381
2382    self._partition_updates = collections.OrderedDict()
2383
2384    for p, block_diff in block_diff_dict.items():
2385      self._partition_updates[p] = DynamicPartitionUpdate()
2386      self._partition_updates[p].block_difference = block_diff
2387
2388    for p, progress in progress_dict.items():
2389      if p in self._partition_updates:
2390        self._partition_updates[p].progress = progress
2391
2392    tgt_groups = shlex.split(info_dict.get(
2393        "super_partition_groups", "").strip())
2394    src_groups = shlex.split(source_info_dict.get(
2395        "super_partition_groups", "").strip())
2396
2397    for g in tgt_groups:
2398      for p in shlex.split(info_dict.get(
2399          "super_%s_partition_list" % g, "").strip()):
2400        assert p in self._partition_updates, \
2401            "{} is in target super_{}_partition_list but no BlockDifference " \
2402            "object is provided.".format(p, g)
2403        self._partition_updates[p].tgt_group = g
2404
2405    for g in src_groups:
2406      for p in shlex.split(source_info_dict.get(
2407          "super_%s_partition_list" % g, "").strip()):
2408        assert p in self._partition_updates, \
2409            "{} is in source super_{}_partition_list but no BlockDifference " \
2410            "object is provided.".format(p, g)
2411        self._partition_updates[p].src_group = g
2412
2413    target_dynamic_partitions = set(shlex.split(info_dict.get(
2414        "dynamic_partition_list", "").strip()))
2415    block_diffs_with_target = set(p for p, u in self._partition_updates.items()
2416                                  if u.tgt_size)
2417    assert block_diffs_with_target == target_dynamic_partitions, \
2418        "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
2419            list(target_dynamic_partitions), list(block_diffs_with_target))
2420
2421    source_dynamic_partitions = set(shlex.split(source_info_dict.get(
2422        "dynamic_partition_list", "").strip()))
2423    block_diffs_with_source = set(p for p, u in self._partition_updates.items()
2424                                  if u.src_size)
2425    assert block_diffs_with_source == source_dynamic_partitions, \
2426        "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
2427            list(source_dynamic_partitions), list(block_diffs_with_source))
2428
2429    if self._partition_updates:
2430      logger.info("Updating dynamic partitions %s",
2431                  self._partition_updates.keys())
2432
2433    self._group_updates = collections.OrderedDict()
2434
2435    for g in tgt_groups:
2436      self._group_updates[g] = DynamicGroupUpdate()
2437      self._group_updates[g].tgt_size = int(info_dict.get(
2438          "super_%s_group_size" % g, "0").strip())
2439
2440    for g in src_groups:
2441      if g not in self._group_updates:
2442        self._group_updates[g] = DynamicGroupUpdate()
2443      self._group_updates[g].src_size = int(source_info_dict.get(
2444          "super_%s_group_size" % g, "0").strip())
2445
2446    self._Compute()
2447
2448  def WriteScript(self, script, output_zip, write_verify_script=False):
2449    script.Comment('--- Start patching dynamic partitions ---')
2450    for p, u in self._partition_updates.items():
2451      if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2452        script.Comment('Patch partition %s' % p)
2453        u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2454                                       write_verify_script=False)
2455
2456    op_list_path = MakeTempFile()
2457    with open(op_list_path, 'w') as f:
2458      for line in self._op_list:
2459        f.write('{}\n'.format(line))
2460
2461    ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
2462
2463    script.Comment('Update dynamic partition metadata')
2464    script.AppendExtra('assert(update_dynamic_partitions('
2465                       'package_extract_file("dynamic_partitions_op_list")));')
2466
2467    if write_verify_script:
2468      for p, u in self._partition_updates.items():
2469        if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2470          u.block_difference.WritePostInstallVerifyScript(script)
2471          script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2472
2473    for p, u in self._partition_updates.items():
2474      if u.tgt_size and u.src_size <= u.tgt_size:
2475        script.Comment('Patch partition %s' % p)
2476        u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2477                                       write_verify_script=write_verify_script)
2478        if write_verify_script:
2479          script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2480
2481    script.Comment('--- End patching dynamic partitions ---')
2482
2483  def _Compute(self):
2484    self._op_list = list()
2485
2486    def append(line):
2487      self._op_list.append(line)
2488
2489    def comment(line):
2490      self._op_list.append("# %s" % line)
2491
2492    if self._remove_all_before_apply:
2493      comment('Remove all existing dynamic partitions and groups before '
2494              'applying full OTA')
2495      append('remove_all_groups')
2496
2497    for p, u in self._partition_updates.items():
2498      if u.src_group and not u.tgt_group:
2499        append('remove %s' % p)
2500
2501    for p, u in self._partition_updates.items():
2502      if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2503        comment('Move partition %s from %s to default' % (p, u.src_group))
2504        append('move %s default' % p)
2505
2506    for p, u in self._partition_updates.items():
2507      if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2508        comment('Shrink partition %s from %d to %d' %
2509                (p, u.src_size, u.tgt_size))
2510        append('resize %s %s' % (p, u.tgt_size))
2511
2512    for g, u in self._group_updates.items():
2513      if u.src_size is not None and u.tgt_size is None:
2514        append('remove_group %s' % g)
2515      if (u.src_size is not None and u.tgt_size is not None and
2516          u.src_size > u.tgt_size):
2517        comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2518        append('resize_group %s %d' % (g, u.tgt_size))
2519
2520    for g, u in self._group_updates.items():
2521      if u.src_size is None and u.tgt_size is not None:
2522        comment('Add group %s with maximum size %d' % (g, u.tgt_size))
2523        append('add_group %s %d' % (g, u.tgt_size))
2524      if (u.src_size is not None and u.tgt_size is not None and
2525          u.src_size < u.tgt_size):
2526        comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2527        append('resize_group %s %d' % (g, u.tgt_size))
2528
2529    for p, u in self._partition_updates.items():
2530      if u.tgt_group and not u.src_group:
2531        comment('Add partition %s to group %s' % (p, u.tgt_group))
2532        append('add %s %s' % (p, u.tgt_group))
2533
2534    for p, u in self._partition_updates.items():
2535      if u.tgt_size and u.src_size < u.tgt_size:
2536        comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
2537        append('resize %s %d' % (p, u.tgt_size))
2538
2539    for p, u in self._partition_updates.items():
2540      if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2541        comment('Move partition %s from default to %s' %
2542                (p, u.tgt_group))
2543        append('move %s %s' % (p, u.tgt_group))
2544