github.com/westcoastroms/westcoastroms-build@v0.0.0-20190928114312-2350e5a73030/build/make/tools/releasetools/common.py (about)

     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  
    15  from __future__ import print_function
    16  
    17  import copy
    18  import errno
    19  import getopt
    20  import getpass
    21  import gzip
    22  import imp
    23  import os
    24  import platform
    25  import re
    26  import shlex
    27  import shutil
    28  import string
    29  import subprocess
    30  import sys
    31  import tempfile
    32  import threading
    33  import time
    34  import zipfile
    35  from hashlib import sha1, sha256
    36  
    37  import blockimgdiff
    38  import sparse_img
    39  
    40  class Options(object):
    41    def __init__(self):
    42      platform_search_path = {
    43          "linux2": "out/host/linux-x86",
    44          "darwin": "out/host/darwin-x86",
    45      }
    46  
    47      self.search_path = platform_search_path.get(sys.platform, None)
    48      self.signapk_path = "framework/signapk.jar"  # Relative to search_path
    49      self.signapk_shared_library_path = "lib64"   # Relative to search_path
    50      self.extra_signapk_args = []
    51      self.java_path = "java"  # Use the one on the path by default.
    52      self.java_args = ["-Xmx2048m"]  # The default JVM args.
    53      self.public_key_suffix = ".x509.pem"
    54      self.private_key_suffix = ".pk8"
    55      # use otatools built boot_signer by default
    56      self.boot_signer_path = "boot_signer"
    57      self.boot_signer_args = []
    58      self.verity_signer_path = None
    59      self.verity_signer_args = []
    60      self.verbose = False
    61      self.tempfiles = []
    62      self.device_specific = None
    63      self.extras = {}
    64      self.info_dict = None
    65      self.source_info_dict = None
    66      self.target_info_dict = None
    67      self.worker_threads = None
    68      # Stash size cannot exceed cache_size * threshold.
    69      self.cache_size = None
    70      self.stash_threshold = 0.8
    71  
    72  
    73  OPTIONS = Options()
    74  
    75  
    76  # Values for "certificate" in apkcerts that mean special things.
    77  SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
    78  
    79  
    80  # The partitions allowed to be signed by AVB (Android verified boot 2.0).
    81  AVB_PARTITIONS = ('boot', 'recovery', 'system', 'vendor', 'product', 'dtbo')
    82  
    83  
    84  class ErrorCode(object):
    85    """Define error_codes for failures that happen during the actual
    86    update package installation.
    87  
    88    Error codes 0-999 are reserved for failures before the package
    89    installation (i.e. low battery, package verification failure).
    90    Detailed code in 'bootable/recovery/error_code.h' """
    91  
    92    SYSTEM_VERIFICATION_FAILURE = 1000
    93    SYSTEM_UPDATE_FAILURE = 1001
    94    SYSTEM_UNEXPECTED_CONTENTS = 1002
    95    SYSTEM_NONZERO_CONTENTS = 1003
    96    SYSTEM_RECOVER_FAILURE = 1004
    97    VENDOR_VERIFICATION_FAILURE = 2000
    98    VENDOR_UPDATE_FAILURE = 2001
    99    VENDOR_UNEXPECTED_CONTENTS = 2002
   100    VENDOR_NONZERO_CONTENTS = 2003
   101    VENDOR_RECOVER_FAILURE = 2004
   102    OEM_PROP_MISMATCH = 3000
   103    FINGERPRINT_MISMATCH = 3001
   104    THUMBPRINT_MISMATCH = 3002
   105    OLDER_BUILD = 3003
   106    DEVICE_MISMATCH = 3004
   107    BAD_PATCH_FILE = 3005
   108    INSUFFICIENT_CACHE_SPACE = 3006
   109    TUNE_PARTITION_FAILURE = 3007
   110    APPLY_PATCH_FAILURE = 3008
   111  
   112  class ExternalError(RuntimeError):
   113    pass
   114  
   115  
   116  def Run(args, verbose=None, **kwargs):
   117    """Create and return a subprocess.Popen object.
   118  
   119    Caller can specify if the command line should be printed. The global
   120    OPTIONS.verbose will be used if not specified.
   121    """
   122    if verbose is None:
   123      verbose = OPTIONS.verbose
   124    if verbose:
   125      print("  running: ", " ".join(args))
   126    return subprocess.Popen(args, **kwargs)
   127  
   128  
   129  def RoundUpTo4K(value):
   130    rounded_up = value + 4095
   131    return rounded_up - (rounded_up % 4096)
   132  
   133  
   134  def CloseInheritedPipes():
   135    """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
   136    before doing other work."""
   137    if platform.system() != "Darwin":
   138      return
   139    for d in range(3, 1025):
   140      try:
   141        stat = os.fstat(d)
   142        if stat is not None:
   143          pipebit = stat[0] & 0x1000
   144          if pipebit != 0:
   145            os.close(d)
   146      except OSError:
   147        pass
   148  
   149  
   150  def LoadInfoDict(input_file, input_dir=None):
   151    """Read and parse the META/misc_info.txt key/value pairs from the
   152    input target files and return a dict."""
   153  
   154    def read_helper(fn):
   155      if isinstance(input_file, zipfile.ZipFile):
   156        return input_file.read(fn)
   157      else:
   158        path = os.path.join(input_file, *fn.split("/"))
   159        try:
   160          with open(path) as f:
   161            return f.read()
   162        except IOError as e:
   163          if e.errno == errno.ENOENT:
   164            raise KeyError(fn)
   165  
   166    try:
   167      d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
   168    except KeyError:
   169      raise ValueError("can't find META/misc_info.txt in input target-files")
   170  
   171    assert "recovery_api_version" in d
   172    assert "fstab_version" in d
   173  
   174    # A few properties are stored as links to the files in the out/ directory.
   175    # It works fine with the build system. However, they are no longer available
   176    # when (re)generating from target_files zip. If input_dir is not None, we
   177    # are doing repacking. Redirect those properties to the actual files in the
   178    # unzipped directory.
   179    if input_dir is not None:
   180      # We carry a copy of file_contexts.bin under META/. If not available,
   181      # search BOOT/RAMDISK/. Note that sometimes we may need a different file
   182      # to build images than the one running on device, such as when enabling
   183      # system_root_image. In that case, we must have the one for image
   184      # generation copied to META/.
   185      fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
   186      fc_config = os.path.join(input_dir, "META", fc_basename)
   187      if d.get("system_root_image") == "true":
   188        assert os.path.exists(fc_config)
   189      if not os.path.exists(fc_config):
   190        fc_config = os.path.join(input_dir, "BOOT", "RAMDISK", fc_basename)
   191        if not os.path.exists(fc_config):
   192          fc_config = None
   193  
   194      if fc_config:
   195        d["selinux_fc"] = fc_config
   196  
   197      # Similarly we need to redirect "ramdisk_dir" and "ramdisk_fs_config".
   198      if d.get("system_root_image") == "true":
   199        d["ramdisk_dir"] = os.path.join(input_dir, "ROOT")
   200        d["ramdisk_fs_config"] = os.path.join(
   201            input_dir, "META", "root_filesystem_config.txt")
   202  
   203      # Redirect {system,vendor}_base_fs_file.
   204      if "system_base_fs_file" in d:
   205        basename = os.path.basename(d["system_base_fs_file"])
   206        system_base_fs_file = os.path.join(input_dir, "META", basename)
   207        if os.path.exists(system_base_fs_file):
   208          d["system_base_fs_file"] = system_base_fs_file
   209        else:
   210          print("Warning: failed to find system base fs file: %s" % (
   211              system_base_fs_file,))
   212          del d["system_base_fs_file"]
   213  
   214      if "vendor_base_fs_file" in d:
   215        basename = os.path.basename(d["vendor_base_fs_file"])
   216        vendor_base_fs_file = os.path.join(input_dir, "META", basename)
   217        if os.path.exists(vendor_base_fs_file):
   218          d["vendor_base_fs_file"] = vendor_base_fs_file
   219        else:
   220          print("Warning: failed to find vendor base fs file: %s" % (
   221              vendor_base_fs_file,))
   222          del d["vendor_base_fs_file"]
   223  
   224    def makeint(key):
   225      if key in d:
   226        d[key] = int(d[key], 0)
   227  
   228    makeint("recovery_api_version")
   229    makeint("blocksize")
   230    makeint("system_size")
   231    makeint("vendor_size")
   232    makeint("userdata_size")
   233    makeint("cache_size")
   234    makeint("recovery_size")
   235    makeint("boot_size")
   236    makeint("fstab_version")
   237  
   238    system_root_image = d.get("system_root_image", None) == "true"
   239    if d.get("no_recovery", None) != "true":
   240      recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
   241      d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
   242          recovery_fstab_path, system_root_image)
   243    elif d.get("recovery_as_boot", None) == "true":
   244      recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
   245      d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
   246          recovery_fstab_path, system_root_image)
   247    else:
   248      d["fstab"] = None
   249  
   250    d["build.prop"] = LoadBuildProp(read_helper, 'SYSTEM/build.prop')
   251    d["vendor.build.prop"] = LoadBuildProp(read_helper, 'VENDOR/build.prop')
   252  
   253    # Set up the salt (based on fingerprint or thumbprint) that will be used when
   254    # adding AVB footer.
   255    if d.get("avb_enable") == "true":
   256      fp = None
   257      if "build.prop" in d:
   258        build_prop = d["build.prop"]
   259        if "ro.build.fingerprint" in build_prop:
   260          fp = build_prop["ro.build.fingerprint"]
   261        elif "ro.build.thumbprint" in build_prop:
   262          fp = build_prop["ro.build.thumbprint"]
   263      if fp:
   264        d["avb_salt"] = sha256(fp).hexdigest()
   265  
   266    return d
   267  
   268  
   269  def LoadBuildProp(read_helper, prop_file):
   270    try:
   271      data = read_helper(prop_file)
   272    except KeyError:
   273      print("Warning: could not read %s" % (prop_file,))
   274      data = ""
   275    return LoadDictionaryFromLines(data.split("\n"))
   276  
   277  
   278  def LoadDictionaryFromLines(lines):
   279    d = {}
   280    for line in lines:
   281      line = line.strip()
   282      if not line or line.startswith("#"):
   283        continue
   284      if "=" in line:
   285        name, value = line.split("=", 1)
   286        d[name] = value
   287    return d
   288  
   289  
   290  def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
   291                        system_root_image=False):
   292    class Partition(object):
   293      def __init__(self, mount_point, fs_type, device, length, context):
   294        self.mount_point = mount_point
   295        self.fs_type = fs_type
   296        self.device = device
   297        self.length = length
   298        self.context = context
   299  
   300    try:
   301      data = read_helper(recovery_fstab_path)
   302    except KeyError:
   303      print("Warning: could not find {}".format(recovery_fstab_path))
   304      data = ""
   305  
   306    assert fstab_version == 2
   307  
   308    d = {}
   309    for line in data.split("\n"):
   310      line = line.strip()
   311      if not line or line.startswith("#"):
   312        continue
   313  
   314      # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
   315      pieces = line.split()
   316      if len(pieces) != 5:
   317        raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
   318  
   319      # Ignore entries that are managed by vold.
   320      options = pieces[4]
   321      if "voldmanaged=" in options:
   322        continue
   323  
   324      # It's a good line, parse it.
   325      length = 0
   326      options = options.split(",")
   327      for i in options:
   328        if i.startswith("length="):
   329          length = int(i[7:])
   330        else:
   331          # Ignore all unknown options in the unified fstab.
   332          continue
   333  
   334      mount_flags = pieces[3]
   335      # Honor the SELinux context if present.
   336      context = None
   337      for i in mount_flags.split(","):
   338        if i.startswith("context="):
   339          context = i
   340  
   341      mount_point = pieces[1]
   342      d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
   343                                 device=pieces[0], length=length, context=context)
   344  
   345    # / is used for the system mount point when the root directory is included in
   346    # system. Other areas assume system is always at "/system" so point /system
   347    # at /.
   348    if system_root_image:
   349      assert not d.has_key("/system") and d.has_key("/")
   350      d["/system"] = d["/"]
   351    return d
   352  
   353  
   354  def DumpInfoDict(d):
   355    for k, v in sorted(d.items()):
   356      print("%-25s = (%s) %s" % (k, type(v).__name__, v))
   357  
   358  
   359  def AppendAVBSigningArgs(cmd, partition):
   360    """Append signing arguments for avbtool."""
   361    # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
   362    key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
   363    algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
   364    if key_path and algorithm:
   365      cmd.extend(["--key", key_path, "--algorithm", algorithm])
   366    avb_salt = OPTIONS.info_dict.get("avb_salt")
   367    # make_vbmeta_image doesn't like "--salt" (and it's not needed).
   368    if avb_salt and partition != "vbmeta":
   369      cmd.extend(["--salt", avb_salt])
   370  
   371  
   372  def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
   373                          has_ramdisk=False, two_step_image=False):
   374    """Build a bootable image from the specified sourcedir.
   375  
   376    Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
   377    'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
   378    we are building a two-step special image (i.e. building a recovery image to
   379    be loaded into /boot in two-step OTAs).
   380  
   381    Return the image data, or None if sourcedir does not appear to contains files
   382    for building the requested image.
   383    """
   384  
   385    def make_ramdisk():
   386      ramdisk_img = tempfile.NamedTemporaryFile()
   387  
   388      if os.access(fs_config_file, os.F_OK):
   389        cmd = ["mkbootfs", "-f", fs_config_file,
   390               os.path.join(sourcedir, "RAMDISK")]
   391      else:
   392        cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
   393      p1 = Run(cmd, stdout=subprocess.PIPE)
   394      p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
   395  
   396      p2.wait()
   397      p1.wait()
   398      assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
   399      assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
   400  
   401      return ramdisk_img
   402  
   403    if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
   404      return None
   405  
   406    if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
   407      return None
   408  
   409    if info_dict is None:
   410      info_dict = OPTIONS.info_dict
   411  
   412    img = tempfile.NamedTemporaryFile()
   413  
   414    if has_ramdisk:
   415      ramdisk_img = make_ramdisk()
   416  
   417    # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
   418    mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
   419  
   420    cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
   421  
   422    fn = os.path.join(sourcedir, "second")
   423    if os.access(fn, os.F_OK):
   424      cmd.append("--second")
   425      cmd.append(fn)
   426  
   427    fn = os.path.join(sourcedir, "cmdline")
   428    if os.access(fn, os.F_OK):
   429      cmd.append("--cmdline")
   430      cmd.append(open(fn).read().rstrip("\n"))
   431  
   432    fn = os.path.join(sourcedir, "base")
   433    if os.access(fn, os.F_OK):
   434      cmd.append("--base")
   435      cmd.append(open(fn).read().rstrip("\n"))
   436  
   437    fn = os.path.join(sourcedir, "pagesize")
   438    if os.access(fn, os.F_OK):
   439      cmd.append("--pagesize")
   440      cmd.append(open(fn).read().rstrip("\n"))
   441  
   442    args = info_dict.get("mkbootimg_args", None)
   443    if args and args.strip():
   444      cmd.extend(shlex.split(args))
   445  
   446    args = info_dict.get("mkbootimg_version_args", None)
   447    if args and args.strip():
   448      cmd.extend(shlex.split(args))
   449  
   450    if has_ramdisk:
   451      cmd.extend(["--ramdisk", ramdisk_img.name])
   452  
   453    img_unsigned = None
   454    if info_dict.get("vboot", None):
   455      img_unsigned = tempfile.NamedTemporaryFile()
   456      cmd.extend(["--output", img_unsigned.name])
   457    else:
   458      cmd.extend(["--output", img.name])
   459  
   460    # "boot" or "recovery", without extension.
   461    partition_name = os.path.basename(sourcedir).lower()
   462  
   463    if (partition_name == "recovery" and
   464        info_dict.get("include_recovery_dtbo") == "true"):
   465      fn = os.path.join(sourcedir, "recovery_dtbo")
   466      cmd.extend(["--recovery_dtbo", fn])
   467  
   468    p = Run(cmd, stdout=subprocess.PIPE)
   469    p.communicate()
   470    assert p.returncode == 0, "mkbootimg of %s image failed" % (partition_name,)
   471  
   472    if (info_dict.get("boot_signer", None) == "true" and
   473        info_dict.get("verity_key", None)):
   474      # Hard-code the path as "/boot" for two-step special recovery image (which
   475      # will be loaded into /boot during the two-step OTA).
   476      if two_step_image:
   477        path = "/boot"
   478      else:
   479        path = "/" + partition_name
   480      cmd = [OPTIONS.boot_signer_path]
   481      cmd.extend(OPTIONS.boot_signer_args)
   482      cmd.extend([path, img.name,
   483                  info_dict["verity_key"] + ".pk8",
   484                  info_dict["verity_key"] + ".x509.pem", img.name])
   485      p = Run(cmd, stdout=subprocess.PIPE)
   486      p.communicate()
   487      assert p.returncode == 0, "boot_signer of %s image failed" % path
   488  
   489    # Sign the image if vboot is non-empty.
   490    elif info_dict.get("vboot", None):
   491      path = "/" + partition_name
   492      img_keyblock = tempfile.NamedTemporaryFile()
   493      # We have switched from the prebuilt futility binary to using the tool
   494      # (futility-host) built from the source. Override the setting in the old
   495      # TF.zip.
   496      futility = info_dict["futility"]
   497      if futility.startswith("prebuilts/"):
   498        futility = "futility-host"
   499      cmd = [info_dict["vboot_signer_cmd"], futility,
   500             img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
   501             info_dict["vboot_key"] + ".vbprivk",
   502             info_dict["vboot_subkey"] + ".vbprivk",
   503             img_keyblock.name,
   504             img.name]
   505      p = Run(cmd, stdout=subprocess.PIPE)
   506      p.communicate()
   507      assert p.returncode == 0, "vboot_signer of %s image failed" % path
   508  
   509      # Clean up the temp files.
   510      img_unsigned.close()
   511      img_keyblock.close()
   512  
   513    # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
   514    if info_dict.get("avb_enable") == "true":
   515      avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
   516      part_size = info_dict[partition_name + "_size"]
   517      cmd = [avbtool, "add_hash_footer", "--image", img.name,
   518             "--partition_size", str(part_size), "--partition_name",
   519             partition_name]
   520      AppendAVBSigningArgs(cmd, partition_name)
   521      args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
   522      if args and args.strip():
   523        cmd.extend(shlex.split(args))
   524      p = Run(cmd, stdout=subprocess.PIPE)
   525      p.communicate()
   526      assert p.returncode == 0, "avbtool add_hash_footer of %s failed" % (
   527          partition_name,)
   528  
   529    img.seek(os.SEEK_SET, 0)
   530    data = img.read()
   531  
   532    if has_ramdisk:
   533      ramdisk_img.close()
   534    img.close()
   535  
   536    return data
   537  
   538  
   539  def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
   540                       info_dict=None, two_step_image=False):
   541    """Return a File object with the desired bootable image.
   542  
   543    Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
   544    otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
   545    the source files in 'unpack_dir'/'tree_subdir'."""
   546  
   547    prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
   548    if os.path.exists(prebuilt_path):
   549      print("using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,))
   550      return File.FromLocalFile(name, prebuilt_path)
   551  
   552    prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
   553    if os.path.exists(prebuilt_path):
   554      print("using prebuilt %s from IMAGES..." % (prebuilt_name,))
   555      return File.FromLocalFile(name, prebuilt_path)
   556  
   557    print("building image from target_files %s..." % (tree_subdir,))
   558  
   559    if info_dict is None:
   560      info_dict = OPTIONS.info_dict
   561  
   562    # With system_root_image == "true", we don't pack ramdisk into the boot image.
   563    # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
   564    # for recovery.
   565    has_ramdisk = (info_dict.get("system_root_image") != "true" or
   566                   prebuilt_name != "boot.img" or
   567                   info_dict.get("recovery_as_boot") == "true")
   568  
   569    fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
   570    data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
   571                               os.path.join(unpack_dir, fs_config),
   572                               info_dict, has_ramdisk, two_step_image)
   573    if data:
   574      return File(name, data)
   575    return None
   576  
   577  
   578  def Gunzip(in_filename, out_filename):
   579    """Gunzip the given gzip compressed file to a given output file.
   580    """
   581    with gzip.open(in_filename, "rb") as in_file, open(out_filename, "wb") as out_file:
   582      shutil.copyfileobj(in_file, out_file)
   583  
   584  
   585  def UnzipTemp(filename, pattern=None):
   586    """Unzips the given archive into a temporary directory and returns the name.
   587  
   588    If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a temp dir,
   589    then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
   590  
   591    Returns:
   592      The name of the temporary directory.
   593    """
   594  
   595    def unzip_to_dir(filename, dirname):
   596      cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
   597      if pattern is not None:
   598        cmd.extend(pattern)
   599      p = Run(cmd, stdout=subprocess.PIPE)
   600      p.communicate()
   601      if p.returncode != 0:
   602        raise ExternalError("failed to unzip input target-files \"%s\"" %
   603                            (filename,))
   604  
   605    tmp = MakeTempDir(prefix="targetfiles-")
   606    m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
   607    if m:
   608      unzip_to_dir(m.group(1), tmp)
   609      unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
   610      filename = m.group(1)
   611    else:
   612      unzip_to_dir(filename, tmp)
   613  
   614    return tmp
   615  
   616  
   617  def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks):
   618    """Returns a SparseImage object suitable for passing to BlockImageDiff.
   619  
   620    This function loads the specified sparse image from the given path, and
   621    performs additional processing for OTA purpose. For example, it always adds
   622    block 0 to clobbered blocks list. It also detects files that cannot be
   623    reconstructed from the block list, for whom we should avoid applying imgdiff.
   624  
   625    Args:
   626      which: The partition name, which must be "system" or "vendor".
   627      tmpdir: The directory that contains the prebuilt image and block map file.
   628      input_zip: The target-files ZIP archive.
   629      allow_shared_blocks: Whether having shared blocks is allowed.
   630  
   631    Returns:
   632      A SparseImage object, with file_map info loaded.
   633    """
   634    assert which in ("system", "vendor")
   635  
   636    path = os.path.join(tmpdir, "IMAGES", which + ".img")
   637    mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
   638  
   639    # The image and map files must have been created prior to calling
   640    # ota_from_target_files.py (since LMP).
   641    assert os.path.exists(path) and os.path.exists(mappath)
   642  
   643    # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
   644    # it to clobbered_blocks so that it will be written to the target
   645    # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
   646    clobbered_blocks = "0"
   647  
   648    image = sparse_img.SparseImage(path, mappath, clobbered_blocks,
   649                                   allow_shared_blocks=allow_shared_blocks)
   650  
   651    # block.map may contain less blocks, because mke2fs may skip allocating blocks
   652    # if they contain all zeros. We can't reconstruct such a file from its block
   653    # list. Tag such entries accordingly. (Bug: 65213616)
   654    for entry in image.file_map:
   655      # "/system/framework/am.jar" => "SYSTEM/framework/am.jar".
   656      arcname = string.replace(entry, which, which.upper(), 1)[1:]
   657      # Skip artificial names, such as "__ZERO", "__NONZERO-1".
   658      if arcname not in input_zip.namelist():
   659        continue
   660  
   661      info = input_zip.getinfo(arcname)
   662      ranges = image.file_map[entry]
   663  
   664      # If a RangeSet has been tagged as using shared blocks while loading the
   665      # image, its block list must be already incomplete due to that reason. Don't
   666      # give it 'incomplete' tag to avoid messing up the imgdiff stats.
   667      if ranges.extra.get('uses_shared_blocks'):
   668        continue
   669  
   670      if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
   671        ranges.extra['incomplete'] = True
   672  
   673    return image
   674  
   675  
   676  def GetKeyPasswords(keylist):
   677    """Given a list of keys, prompt the user to enter passwords for
   678    those which require them.  Return a {key: password} dict.  password
   679    will be None if the key has no password."""
   680  
   681    no_passwords = []
   682    need_passwords = []
   683    key_passwords = {}
   684    devnull = open("/dev/null", "w+b")
   685    for k in sorted(keylist):
   686      # We don't need a password for things that aren't really keys.
   687      if k in SPECIAL_CERT_STRINGS:
   688        no_passwords.append(k)
   689        continue
   690  
   691      p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
   692               "-inform", "DER", "-nocrypt"],
   693              stdin=devnull.fileno(),
   694              stdout=devnull.fileno(),
   695              stderr=subprocess.STDOUT)
   696      p.communicate()
   697      if p.returncode == 0:
   698        # Definitely an unencrypted key.
   699        no_passwords.append(k)
   700      else:
   701        p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
   702                 "-inform", "DER", "-passin", "pass:"],
   703                stdin=devnull.fileno(),
   704                stdout=devnull.fileno(),
   705                stderr=subprocess.PIPE)
   706        _, stderr = p.communicate()
   707        if p.returncode == 0:
   708          # Encrypted key with empty string as password.
   709          key_passwords[k] = ''
   710        elif stderr.startswith('Error decrypting key'):
   711          # Definitely encrypted key.
   712          # It would have said "Error reading key" if it didn't parse correctly.
   713          need_passwords.append(k)
   714        else:
   715          # Potentially, a type of key that openssl doesn't understand.
   716          # We'll let the routines in signapk.jar handle it.
   717          no_passwords.append(k)
   718    devnull.close()
   719  
   720    key_passwords.update(PasswordManager().GetPasswords(need_passwords))
   721    key_passwords.update(dict.fromkeys(no_passwords, None))
   722    return key_passwords
   723  
   724  
   725  def GetMinSdkVersion(apk_name):
   726    """Get the minSdkVersion delared in the APK. This can be both a decimal number
   727    (API Level) or a codename.
   728    """
   729  
   730    p = Run(["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE)
   731    output, err = p.communicate()
   732    if err:
   733      raise ExternalError("Failed to obtain minSdkVersion: aapt return code %s"
   734          % (p.returncode,))
   735  
   736    for line in output.split("\n"):
   737      # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'
   738      m = re.match(r'sdkVersion:\'([^\']*)\'', line)
   739      if m:
   740        return m.group(1)
   741    raise ExternalError("No minSdkVersion returned by aapt")
   742  
   743  
   744  def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
   745    """Get the minSdkVersion declared in the APK as a number (API Level). If
   746    minSdkVersion is set to a codename, it is translated to a number using the
   747    provided map.
   748    """
   749  
   750    version = GetMinSdkVersion(apk_name)
   751    try:
   752      return int(version)
   753    except ValueError:
   754      # Not a decimal number. Codename?
   755      if version in codename_to_api_level_map:
   756        return codename_to_api_level_map[version]
   757      else:
   758        raise ExternalError("Unknown minSdkVersion: '%s'. Known codenames: %s"
   759                            % (version, codename_to_api_level_map))
   760  
   761  
   762  def SignFile(input_name, output_name, key, password, min_api_level=None,
   763      codename_to_api_level_map=dict(),
   764      whole_file=False):
   765    """Sign the input_name zip/jar/apk, producing output_name.  Use the
   766    given key and password (the latter may be None if the key does not
   767    have a password.
   768  
   769    If whole_file is true, use the "-w" option to SignApk to embed a
   770    signature that covers the whole file in the archive comment of the
   771    zip file.
   772  
   773    min_api_level is the API Level (int) of the oldest platform this file may end
   774    up on. If not specified for an APK, the API Level is obtained by interpreting
   775    the minSdkVersion attribute of the APK's AndroidManifest.xml.
   776  
   777    codename_to_api_level_map is needed to translate the codename which may be
   778    encountered as the APK's minSdkVersion.
   779    """
   780  
   781    java_library_path = os.path.join(
   782        OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
   783  
   784    cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
   785           ["-Djava.library.path=" + java_library_path,
   786            "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
   787           OPTIONS.extra_signapk_args)
   788    if whole_file:
   789      cmd.append("-w")
   790  
   791    min_sdk_version = min_api_level
   792    if min_sdk_version is None:
   793      if not whole_file:
   794        min_sdk_version = GetMinSdkVersionInt(
   795            input_name, codename_to_api_level_map)
   796    if min_sdk_version is not None:
   797      cmd.extend(["--min-sdk-version", str(min_sdk_version)])
   798  
   799    cmd.extend([key + OPTIONS.public_key_suffix,
   800                key + OPTIONS.private_key_suffix,
   801                input_name, output_name])
   802  
   803    p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
   804    if password is not None:
   805      password += "\n"
   806    p.communicate(password)
   807    if p.returncode != 0:
   808      raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
   809  
   810  
   811  def CheckSize(data, target, info_dict):
   812    """Checks the data string passed against the max size limit.
   813  
   814    For non-AVB images, raise exception if the data is too big. Print a warning
   815    if the data is nearing the maximum size.
   816  
   817    For AVB images, the actual image size should be identical to the limit.
   818  
   819    Args:
   820      data: A string that contains all the data for the partition.
   821      target: The partition name. The ".img" suffix is optional.
   822      info_dict: The dict to be looked up for relevant info.
   823    """
   824    if target.endswith(".img"):
   825      target = target[:-4]
   826    mount_point = "/" + target
   827  
   828    fs_type = None
   829    limit = None
   830    if info_dict["fstab"]:
   831      if mount_point == "/userdata":
   832        mount_point = "/data"
   833      p = info_dict["fstab"][mount_point]
   834      fs_type = p.fs_type
   835      device = p.device
   836      if "/" in device:
   837        device = device[device.rfind("/")+1:]
   838      limit = info_dict.get(device + "_size", None)
   839    if not fs_type or not limit:
   840      return
   841  
   842    size = len(data)
   843    # target could be 'userdata' or 'cache'. They should follow the non-AVB image
   844    # path.
   845    if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
   846      if size != limit:
   847        raise ExternalError(
   848            "Mismatching image size for %s: expected %d actual %d" % (
   849                target, limit, size))
   850    else:
   851      pct = float(size) * 100.0 / limit
   852      msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
   853      if pct >= 99.0:
   854        raise ExternalError(msg)
   855      elif pct >= 95.0:
   856        print("\n  WARNING: %s\n" % (msg,))
   857      elif OPTIONS.verbose:
   858        print("  ", msg)
   859  
   860  
   861  def ReadApkCerts(tf_zip):
   862    """Parses the APK certs info from a given target-files zip.
   863  
   864    Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
   865    tuple with the following elements: (1) a dictionary that maps packages to
   866    certs (based on the "certificate" and "private_key" attributes in the file;
   867    (2) a string representing the extension of compressed APKs in the target files
   868    (e.g ".gz", ".bro").
   869  
   870    Args:
   871      tf_zip: The input target_files ZipFile (already open).
   872  
   873    Returns:
   874      (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
   875          the extension string of compressed APKs (e.g. ".gz"), or None if there's
   876          no compressed APKs.
   877    """
   878    certmap = {}
   879    compressed_extension = None
   880  
   881    # META/apkcerts.txt contains the info for _all_ the packages known at build
   882    # time. Filter out the ones that are not installed.
   883    installed_files = set()
   884    for name in tf_zip.namelist():
   885      basename = os.path.basename(name)
   886      if basename:
   887        installed_files.add(basename)
   888  
   889    for line in tf_zip.read("META/apkcerts.txt").split("\n"):
   890      line = line.strip()
   891      if not line:
   892        continue
   893      m = re.match(
   894          r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
   895          r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
   896          line)
   897      if not m:
   898        continue
   899  
   900      matches = m.groupdict()
   901      cert = matches["CERT"]
   902      privkey = matches["PRIVKEY"]
   903      name = matches["NAME"]
   904      this_compressed_extension = matches["COMPRESSED"]
   905  
   906      public_key_suffix_len = len(OPTIONS.public_key_suffix)
   907      private_key_suffix_len = len(OPTIONS.private_key_suffix)
   908      if cert in SPECIAL_CERT_STRINGS and not privkey:
   909        certmap[name] = cert
   910      elif (cert.endswith(OPTIONS.public_key_suffix) and
   911            privkey.endswith(OPTIONS.private_key_suffix) and
   912            cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
   913        certmap[name] = cert[:-public_key_suffix_len]
   914      else:
   915        raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
   916  
   917      if not this_compressed_extension:
   918        continue
   919  
   920      # Only count the installed files.
   921      filename = name + '.' + this_compressed_extension
   922      if filename not in installed_files:
   923        continue
   924  
   925      # Make sure that all the values in the compression map have the same
   926      # extension. We don't support multiple compression methods in the same
   927      # system image.
   928      if compressed_extension:
   929        if this_compressed_extension != compressed_extension:
   930          raise ValueError(
   931              "Multiple compressed extensions: {} vs {}".format(
   932                  compressed_extension, this_compressed_extension))
   933      else:
   934        compressed_extension = this_compressed_extension
   935  
   936    return (certmap,
   937            ("." + compressed_extension) if compressed_extension else None)
   938  
   939  
   940  COMMON_DOCSTRING = """
   941    -p  (--path)  <dir>
   942        Prepend <dir>/bin to the list of places to search for binaries
   943        run by this script, and expect to find jars in <dir>/framework.
   944  
   945    -s  (--device_specific) <file>
   946        Path to the python module containing device-specific
   947        releasetools code.
   948  
   949    -x  (--extra)  <key=value>
   950        Add a key/value pair to the 'extras' dict, which device-specific
   951        extension code may look at.
   952  
   953    -v  (--verbose)
   954        Show command lines being executed.
   955  
   956    -h  (--help)
   957        Display this usage message and exit.
   958  """
   959  
   960  def Usage(docstring):
   961    print(docstring.rstrip("\n"))
   962    print(COMMON_DOCSTRING)
   963  
   964  
   965  def ParseOptions(argv,
   966                   docstring,
   967                   extra_opts="", extra_long_opts=(),
   968                   extra_option_handler=None):
   969    """Parse the options in argv and return any arguments that aren't
   970    flags.  docstring is the calling module's docstring, to be displayed
   971    for errors and -h.  extra_opts and extra_long_opts are for flags
   972    defined by the caller, which are processed by passing them to
   973    extra_option_handler."""
   974  
   975    try:
   976      opts, args = getopt.getopt(
   977          argv, "hvp:s:x:" + extra_opts,
   978          ["help", "verbose", "path=", "signapk_path=",
   979           "signapk_shared_library_path=", "extra_signapk_args=",
   980           "java_path=", "java_args=", "public_key_suffix=",
   981           "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
   982           "verity_signer_path=", "verity_signer_args=", "device_specific=",
   983           "extra="] +
   984          list(extra_long_opts))
   985    except getopt.GetoptError as err:
   986      Usage(docstring)
   987      print("**", str(err), "**")
   988      sys.exit(2)
   989  
   990    for o, a in opts:
   991      if o in ("-h", "--help"):
   992        Usage(docstring)
   993        sys.exit()
   994      elif o in ("-v", "--verbose"):
   995        OPTIONS.verbose = True
   996      elif o in ("-p", "--path"):
   997        OPTIONS.search_path = a
   998      elif o in ("--signapk_path",):
   999        OPTIONS.signapk_path = a
  1000      elif o in ("--signapk_shared_library_path",):
  1001        OPTIONS.signapk_shared_library_path = a
  1002      elif o in ("--extra_signapk_args",):
  1003        OPTIONS.extra_signapk_args = shlex.split(a)
  1004      elif o in ("--java_path",):
  1005        OPTIONS.java_path = a
  1006      elif o in ("--java_args",):
  1007        OPTIONS.java_args = shlex.split(a)
  1008      elif o in ("--public_key_suffix",):
  1009        OPTIONS.public_key_suffix = a
  1010      elif o in ("--private_key_suffix",):
  1011        OPTIONS.private_key_suffix = a
  1012      elif o in ("--boot_signer_path",):
  1013        OPTIONS.boot_signer_path = a
  1014      elif o in ("--boot_signer_args",):
  1015        OPTIONS.boot_signer_args = shlex.split(a)
  1016      elif o in ("--verity_signer_path",):
  1017        OPTIONS.verity_signer_path = a
  1018      elif o in ("--verity_signer_args",):
  1019        OPTIONS.verity_signer_args = shlex.split(a)
  1020      elif o in ("-s", "--device_specific"):
  1021        OPTIONS.device_specific = a
  1022      elif o in ("-x", "--extra"):
  1023        key, value = a.split("=", 1)
  1024        OPTIONS.extras[key] = value
  1025      else:
  1026        if extra_option_handler is None or not extra_option_handler(o, a):
  1027          assert False, "unknown option \"%s\"" % (o,)
  1028  
  1029    if OPTIONS.search_path:
  1030      os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
  1031                            os.pathsep + os.environ["PATH"])
  1032  
  1033    return args
  1034  
  1035  
  1036  def MakeTempFile(prefix='tmp', suffix=''):
  1037    """Make a temp file and add it to the list of things to be deleted
  1038    when Cleanup() is called.  Return the filename."""
  1039    fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
  1040    os.close(fd)
  1041    OPTIONS.tempfiles.append(fn)
  1042    return fn
  1043  
  1044  
  1045  def MakeTempDir(prefix='tmp', suffix=''):
  1046    """Makes a temporary dir that will be cleaned up with a call to Cleanup().
  1047  
  1048    Returns:
  1049      The absolute pathname of the new directory.
  1050    """
  1051    dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
  1052    OPTIONS.tempfiles.append(dir_name)
  1053    return dir_name
  1054  
  1055  
  1056  def Cleanup():
  1057    for i in OPTIONS.tempfiles:
  1058      if os.path.isdir(i):
  1059        shutil.rmtree(i, ignore_errors=True)
  1060      else:
  1061        os.remove(i)
  1062    del OPTIONS.tempfiles[:]
  1063  
  1064  
  1065  class PasswordManager(object):
  1066    def __init__(self):
  1067      self.editor = os.getenv("EDITOR", None)
  1068      self.pwfile = os.getenv("ANDROID_PW_FILE", None)
  1069  
  1070    def GetPasswords(self, items):
  1071      """Get passwords corresponding to each string in 'items',
  1072      returning a dict.  (The dict may have keys in addition to the
  1073      values in 'items'.)
  1074  
  1075      Uses the passwords in $ANDROID_PW_FILE if available, letting the
  1076      user edit that file to add more needed passwords.  If no editor is
  1077      available, or $ANDROID_PW_FILE isn't define, prompts the user
  1078      interactively in the ordinary way.
  1079      """
  1080  
  1081      current = self.ReadFile()
  1082  
  1083      first = True
  1084      while True:
  1085        missing = []
  1086        for i in items:
  1087          if i not in current or not current[i]:
  1088            missing.append(i)
  1089        # Are all the passwords already in the file?
  1090        if not missing:
  1091          return current
  1092  
  1093        for i in missing:
  1094          current[i] = ""
  1095  
  1096        if not first:
  1097          print("key file %s still missing some passwords." % (self.pwfile,))
  1098          answer = raw_input("try to edit again? [y]> ").strip()
  1099          if answer and answer[0] not in 'yY':
  1100            raise RuntimeError("key passwords unavailable")
  1101        first = False
  1102  
  1103        current = self.UpdateAndReadFile(current)
  1104  
  1105    def PromptResult(self, current): # pylint: disable=no-self-use
  1106      """Prompt the user to enter a value (password) for each key in
  1107      'current' whose value is fales.  Returns a new dict with all the
  1108      values.
  1109      """
  1110      result = {}
  1111      for k, v in sorted(current.iteritems()):
  1112        if v:
  1113          result[k] = v
  1114        else:
  1115          while True:
  1116            result[k] = getpass.getpass(
  1117                "Enter password for %s key> " % k).strip()
  1118            if result[k]:
  1119              break
  1120      return result
  1121  
  1122    def UpdateAndReadFile(self, current):
  1123      if not self.editor or not self.pwfile:
  1124        return self.PromptResult(current)
  1125  
  1126      f = open(self.pwfile, "w")
  1127      os.chmod(self.pwfile, 0o600)
  1128      f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
  1129      f.write("# (Additional spaces are harmless.)\n\n")
  1130  
  1131      first_line = None
  1132      sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
  1133      for i, (_, k, v) in enumerate(sorted_list):
  1134        f.write("[[[  %s  ]]] %s\n" % (v, k))
  1135        if not v and first_line is None:
  1136          # position cursor on first line with no password.
  1137          first_line = i + 4
  1138      f.close()
  1139  
  1140      p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
  1141      _, _ = p.communicate()
  1142  
  1143      return self.ReadFile()
  1144  
  1145    def ReadFile(self):
  1146      result = {}
  1147      if self.pwfile is None:
  1148        return result
  1149      try:
  1150        f = open(self.pwfile, "r")
  1151        for line in f:
  1152          line = line.strip()
  1153          if not line or line[0] == '#':
  1154            continue
  1155          m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
  1156          if not m:
  1157            print("failed to parse password file: ", line)
  1158          else:
  1159            result[m.group(2)] = m.group(1)
  1160        f.close()
  1161      except IOError as e:
  1162        if e.errno != errno.ENOENT:
  1163          print("error reading password file: ", str(e))
  1164      return result
  1165  
  1166  
  1167  def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
  1168               compress_type=None):
  1169    import datetime
  1170  
  1171    # http://b/18015246
  1172    # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
  1173    # for files larger than 2GiB. We can work around this by adjusting their
  1174    # limit. Note that `zipfile.writestr()` will not work for strings larger than
  1175    # 2GiB. The Python interpreter sometimes rejects strings that large (though
  1176    # it isn't clear to me exactly what circumstances cause this).
  1177    # `zipfile.write()` must be used directly to work around this.
  1178    #
  1179    # This mess can be avoided if we port to python3.
  1180    saved_zip64_limit = zipfile.ZIP64_LIMIT
  1181    zipfile.ZIP64_LIMIT = (1 << 32) - 1
  1182  
  1183    if compress_type is None:
  1184      compress_type = zip_file.compression
  1185    if arcname is None:
  1186      arcname = filename
  1187  
  1188    saved_stat = os.stat(filename)
  1189  
  1190    try:
  1191      # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
  1192      # file to be zipped and reset it when we're done.
  1193      os.chmod(filename, perms)
  1194  
  1195      # Use a fixed timestamp so the output is repeatable.
  1196      epoch = datetime.datetime.fromtimestamp(0)
  1197      timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
  1198      os.utime(filename, (timestamp, timestamp))
  1199  
  1200      zip_file.write(filename, arcname=arcname, compress_type=compress_type)
  1201    finally:
  1202      os.chmod(filename, saved_stat.st_mode)
  1203      os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
  1204      zipfile.ZIP64_LIMIT = saved_zip64_limit
  1205  
  1206  
  1207  def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
  1208                  compress_type=None):
  1209    """Wrap zipfile.writestr() function to work around the zip64 limit.
  1210  
  1211    Even with the ZIP64_LIMIT workaround, it won't allow writing a string
  1212    longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
  1213    when calling crc32(bytes).
  1214  
  1215    But it still works fine to write a shorter string into a large zip file.
  1216    We should use ZipWrite() whenever possible, and only use ZipWriteStr()
  1217    when we know the string won't be too long.
  1218    """
  1219  
  1220    saved_zip64_limit = zipfile.ZIP64_LIMIT
  1221    zipfile.ZIP64_LIMIT = (1 << 32) - 1
  1222  
  1223    if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
  1224      zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
  1225      zinfo.compress_type = zip_file.compression
  1226      if perms is None:
  1227        perms = 0o100644
  1228    else:
  1229      zinfo = zinfo_or_arcname
  1230  
  1231    # If compress_type is given, it overrides the value in zinfo.
  1232    if compress_type is not None:
  1233      zinfo.compress_type = compress_type
  1234  
  1235    # If perms is given, it has a priority.
  1236    if perms is not None:
  1237      # If perms doesn't set the file type, mark it as a regular file.
  1238      if perms & 0o770000 == 0:
  1239        perms |= 0o100000
  1240      zinfo.external_attr = perms << 16
  1241  
  1242    # Use a fixed timestamp so the output is repeatable.
  1243    zinfo.date_time = (2009, 1, 1, 0, 0, 0)
  1244  
  1245    zip_file.writestr(zinfo, data)
  1246    zipfile.ZIP64_LIMIT = saved_zip64_limit
  1247  
  1248  
  1249  def ZipDelete(zip_filename, entries):
  1250    """Deletes entries from a ZIP file.
  1251  
  1252    Since deleting entries from a ZIP file is not supported, it shells out to
  1253    'zip -d'.
  1254  
  1255    Args:
  1256      zip_filename: The name of the ZIP file.
  1257      entries: The name of the entry, or the list of names to be deleted.
  1258  
  1259    Raises:
  1260      AssertionError: In case of non-zero return from 'zip'.
  1261    """
  1262    if isinstance(entries, basestring):
  1263      entries = [entries]
  1264    cmd = ["zip", "-d", zip_filename] + entries
  1265    proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
  1266    stdoutdata, _ = proc.communicate()
  1267    assert proc.returncode == 0, "Failed to delete %s:\n%s" % (entries,
  1268                                                               stdoutdata)
  1269  
  1270  
  1271  def ZipClose(zip_file):
  1272    # http://b/18015246
  1273    # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
  1274    # central directory.
  1275    saved_zip64_limit = zipfile.ZIP64_LIMIT
  1276    zipfile.ZIP64_LIMIT = (1 << 32) - 1
  1277  
  1278    zip_file.close()
  1279  
  1280    zipfile.ZIP64_LIMIT = saved_zip64_limit
  1281  
  1282  
  1283  class DeviceSpecificParams(object):
  1284    module = None
  1285    def __init__(self, **kwargs):
  1286      """Keyword arguments to the constructor become attributes of this
  1287      object, which is passed to all functions in the device-specific
  1288      module."""
  1289      for k, v in kwargs.iteritems():
  1290        setattr(self, k, v)
  1291      self.extras = OPTIONS.extras
  1292  
  1293      if self.module is None:
  1294        path = OPTIONS.device_specific
  1295        if not path:
  1296          return
  1297        try:
  1298          if os.path.isdir(path):
  1299            info = imp.find_module("releasetools", [path])
  1300          else:
  1301            d, f = os.path.split(path)
  1302            b, x = os.path.splitext(f)
  1303            if x == ".py":
  1304              f = b
  1305            info = imp.find_module(f, [d])
  1306          print("loaded device-specific extensions from", path)
  1307          self.module = imp.load_module("device_specific", *info)
  1308        except ImportError:
  1309          print("unable to load device-specific module; assuming none")
  1310  
  1311    def _DoCall(self, function_name, *args, **kwargs):
  1312      """Call the named function in the device-specific module, passing
  1313      the given args and kwargs.  The first argument to the call will be
  1314      the DeviceSpecific object itself.  If there is no module, or the
  1315      module does not define the function, return the value of the
  1316      'default' kwarg (which itself defaults to None)."""
  1317      if self.module is None or not hasattr(self.module, function_name):
  1318        return kwargs.get("default", None)
  1319      return getattr(self.module, function_name)(*((self,) + args), **kwargs)
  1320  
  1321    def FullOTA_Assertions(self):
  1322      """Called after emitting the block of assertions at the top of a
  1323      full OTA package.  Implementations can add whatever additional
  1324      assertions they like."""
  1325      return self._DoCall("FullOTA_Assertions")
  1326  
  1327    def FullOTA_InstallBegin(self):
  1328      """Called at the start of full OTA installation."""
  1329      return self._DoCall("FullOTA_InstallBegin")
  1330  
  1331    def FullOTA_InstallEnd(self):
  1332      """Called at the end of full OTA installation; typically this is
  1333      used to install the image for the device's baseband processor."""
  1334      return self._DoCall("FullOTA_InstallEnd")
  1335  
  1336    def IncrementalOTA_Assertions(self):
  1337      """Called after emitting the block of assertions at the top of an
  1338      incremental OTA package.  Implementations can add whatever
  1339      additional assertions they like."""
  1340      return self._DoCall("IncrementalOTA_Assertions")
  1341  
  1342    def IncrementalOTA_VerifyBegin(self):
  1343      """Called at the start of the verification phase of incremental
  1344      OTA installation; additional checks can be placed here to abort
  1345      the script before any changes are made."""
  1346      return self._DoCall("IncrementalOTA_VerifyBegin")
  1347  
  1348    def IncrementalOTA_VerifyEnd(self):
  1349      """Called at the end of the verification phase of incremental OTA
  1350      installation; additional checks can be placed here to abort the
  1351      script before any changes are made."""
  1352      return self._DoCall("IncrementalOTA_VerifyEnd")
  1353  
  1354    def IncrementalOTA_InstallBegin(self):
  1355      """Called at the start of incremental OTA installation (after
  1356      verification is complete)."""
  1357      return self._DoCall("IncrementalOTA_InstallBegin")
  1358  
  1359    def IncrementalOTA_InstallEnd(self):
  1360      """Called at the end of incremental OTA installation; typically
  1361      this is used to install the image for the device's baseband
  1362      processor."""
  1363      return self._DoCall("IncrementalOTA_InstallEnd")
  1364  
  1365    def VerifyOTA_Assertions(self):
  1366      return self._DoCall("VerifyOTA_Assertions")
  1367  
  1368  class File(object):
  1369    def __init__(self, name, data, compress_size = None):
  1370      self.name = name
  1371      self.data = data
  1372      self.size = len(data)
  1373      self.compress_size = compress_size or self.size
  1374      self.sha1 = sha1(data).hexdigest()
  1375  
  1376    @classmethod
  1377    def FromLocalFile(cls, name, diskname):
  1378      f = open(diskname, "rb")
  1379      data = f.read()
  1380      f.close()
  1381      return File(name, data)
  1382  
  1383    def WriteToTemp(self):
  1384      t = tempfile.NamedTemporaryFile()
  1385      t.write(self.data)
  1386      t.flush()
  1387      return t
  1388  
  1389    def WriteToDir(self, d):
  1390      with open(os.path.join(d, self.name), "wb") as fp:
  1391        fp.write(self.data)
  1392  
  1393    def AddToZip(self, z, compression=None):
  1394      ZipWriteStr(z, self.name, self.data, compress_type=compression)
  1395  
  1396  DIFF_PROGRAM_BY_EXT = {
  1397      ".gz" : "imgdiff",
  1398      ".zip" : ["imgdiff", "-z"],
  1399      ".jar" : ["imgdiff", "-z"],
  1400      ".apk" : ["imgdiff", "-z"],
  1401      ".img" : "imgdiff",
  1402      }
  1403  
  1404  class Difference(object):
  1405    def __init__(self, tf, sf, diff_program=None):
  1406      self.tf = tf
  1407      self.sf = sf
  1408      self.patch = None
  1409      self.diff_program = diff_program
  1410  
  1411    def ComputePatch(self):
  1412      """Compute the patch (as a string of data) needed to turn sf into
  1413      tf.  Returns the same tuple as GetPatch()."""
  1414  
  1415      tf = self.tf
  1416      sf = self.sf
  1417  
  1418      if self.diff_program:
  1419        diff_program = self.diff_program
  1420      else:
  1421        ext = os.path.splitext(tf.name)[1]
  1422        diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
  1423  
  1424      ttemp = tf.WriteToTemp()
  1425      stemp = sf.WriteToTemp()
  1426  
  1427      ext = os.path.splitext(tf.name)[1]
  1428  
  1429      try:
  1430        ptemp = tempfile.NamedTemporaryFile()
  1431        if isinstance(diff_program, list):
  1432          cmd = copy.copy(diff_program)
  1433        else:
  1434          cmd = [diff_program]
  1435        cmd.append(stemp.name)
  1436        cmd.append(ttemp.name)
  1437        cmd.append(ptemp.name)
  1438        p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  1439        err = []
  1440        def run():
  1441          _, e = p.communicate()
  1442          if e:
  1443            err.append(e)
  1444        th = threading.Thread(target=run)
  1445        th.start()
  1446        th.join(timeout=300)   # 5 mins
  1447        if th.is_alive():
  1448          print("WARNING: diff command timed out")
  1449          p.terminate()
  1450          th.join(5)
  1451          if th.is_alive():
  1452            p.kill()
  1453            th.join()
  1454  
  1455        if p.returncode != 0:
  1456          print("WARNING: failure running %s:\n%s\n" % (
  1457              diff_program, "".join(err)))
  1458          self.patch = None
  1459          return None, None, None
  1460        diff = ptemp.read()
  1461      finally:
  1462        ptemp.close()
  1463        stemp.close()
  1464        ttemp.close()
  1465  
  1466      self.patch = diff
  1467      return self.tf, self.sf, self.patch
  1468  
  1469  
  1470    def GetPatch(self):
  1471      """Return a tuple (target_file, source_file, patch_data).
  1472      patch_data may be None if ComputePatch hasn't been called, or if
  1473      computing the patch failed."""
  1474      return self.tf, self.sf, self.patch
  1475  
  1476  
  1477  def ComputeDifferences(diffs):
  1478    """Call ComputePatch on all the Difference objects in 'diffs'."""
  1479    print(len(diffs), "diffs to compute")
  1480  
  1481    # Do the largest files first, to try and reduce the long-pole effect.
  1482    by_size = [(i.tf.size, i) for i in diffs]
  1483    by_size.sort(reverse=True)
  1484    by_size = [i[1] for i in by_size]
  1485  
  1486    lock = threading.Lock()
  1487    diff_iter = iter(by_size)   # accessed under lock
  1488  
  1489    def worker():
  1490      try:
  1491        lock.acquire()
  1492        for d in diff_iter:
  1493          lock.release()
  1494          start = time.time()
  1495          d.ComputePatch()
  1496          dur = time.time() - start
  1497          lock.acquire()
  1498  
  1499          tf, sf, patch = d.GetPatch()
  1500          if sf.name == tf.name:
  1501            name = tf.name
  1502          else:
  1503            name = "%s (%s)" % (tf.name, sf.name)
  1504          if patch is None:
  1505            print("patching failed!                                  %s" % (name,))
  1506          else:
  1507            print("%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
  1508                dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name))
  1509        lock.release()
  1510      except Exception as e:
  1511        print(e)
  1512        raise
  1513  
  1514    # start worker threads; wait for them all to finish.
  1515    threads = [threading.Thread(target=worker)
  1516               for i in range(OPTIONS.worker_threads)]
  1517    for th in threads:
  1518      th.start()
  1519    while threads:
  1520      threads.pop().join()
  1521  
  1522  
  1523  class BlockDifference(object):
  1524    def __init__(self, partition, tgt, src=None, check_first_block=False,
  1525                 version=None, disable_imgdiff=False):
  1526      self.tgt = tgt
  1527      self.src = src
  1528      self.partition = partition
  1529      self.check_first_block = check_first_block
  1530      self.disable_imgdiff = disable_imgdiff
  1531  
  1532      if version is None:
  1533        version = max(
  1534            int(i) for i in
  1535            OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
  1536      assert version >= 3
  1537      self.version = version
  1538  
  1539      b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
  1540                                      version=self.version,
  1541                                      disable_imgdiff=self.disable_imgdiff)
  1542      self.path = os.path.join(MakeTempDir(), partition)
  1543      b.Compute(self.path)
  1544      self._required_cache = b.max_stashed_size
  1545      self.touched_src_ranges = b.touched_src_ranges
  1546      self.touched_src_sha1 = b.touched_src_sha1
  1547  
  1548      if src is None:
  1549        _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
  1550      else:
  1551        _, self.device = GetTypeAndDevice("/" + partition,
  1552                                          OPTIONS.source_info_dict)
  1553  
  1554    @property
  1555    def required_cache(self):
  1556      return self._required_cache
  1557  
  1558    def WriteScript(self, script, output_zip, progress=None):
  1559      if not self.src:
  1560        # write the output unconditionally
  1561        script.Print("Patching %s image unconditionally..." % (self.partition,))
  1562      else:
  1563        script.Print("Patching %s image after verification." % (self.partition,))
  1564  
  1565      if progress:
  1566        script.ShowProgress(progress, 0)
  1567      self._WriteUpdate(script, output_zip)
  1568      if OPTIONS.verify:
  1569        self._WritePostInstallVerifyScript(script)
  1570  
  1571    def WriteStrictVerifyScript(self, script):
  1572      """Verify all the blocks in the care_map, including clobbered blocks.
  1573  
  1574      This differs from the WriteVerifyScript() function: a) it prints different
  1575      error messages; b) it doesn't allow half-way updated images to pass the
  1576      verification."""
  1577  
  1578      partition = self.partition
  1579      script.Print("Verifying %s..." % (partition,))
  1580      ranges = self.tgt.care_map
  1581      ranges_str = ranges.to_string_raw()
  1582      script.AppendExtra('range_sha1("%s", "%s") == "%s" && '
  1583                         'ui_print("    Verified.") || '
  1584                         'ui_print("\\"%s\\" has unexpected contents.");' % (
  1585                         self.device, ranges_str,
  1586                         self.tgt.TotalSha1(include_clobbered_blocks=True),
  1587                         self.device))
  1588      script.AppendExtra("")
  1589  
  1590    def WriteVerifyScript(self, script, touched_blocks_only=False):
  1591      partition = self.partition
  1592  
  1593      # full OTA
  1594      if not self.src:
  1595        script.Print("Image %s will be patched unconditionally." % (partition,))
  1596  
  1597      # incremental OTA
  1598      else:
  1599        if touched_blocks_only:
  1600          ranges = self.touched_src_ranges
  1601          expected_sha1 = self.touched_src_sha1
  1602        else:
  1603          ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
  1604          expected_sha1 = self.src.TotalSha1()
  1605  
  1606        # No blocks to be checked, skipping.
  1607        if not ranges:
  1608          return
  1609  
  1610        ranges_str = ranges.to_string_raw()
  1611        script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
  1612                            'block_image_verify("%s", '
  1613                            'package_extract_file("%s.transfer.list"), '
  1614                            '"%s.new.dat", "%s.patch.dat")) then') % (
  1615                            self.device, ranges_str, expected_sha1,
  1616                            self.device, partition, partition, partition))
  1617        script.Print('Verified %s image...' % (partition,))
  1618        script.AppendExtra('else')
  1619  
  1620        if self.version >= 4:
  1621  
  1622          # Bug: 21124327
  1623          # When generating incrementals for the system and vendor partitions in
  1624          # version 4 or newer, explicitly check the first block (which contains
  1625          # the superblock) of the partition to see if it's what we expect. If
  1626          # this check fails, give an explicit log message about the partition
  1627          # having been remounted R/W (the most likely explanation).
  1628          if self.check_first_block:
  1629            script.AppendExtra('check_first_block("%s");' % (self.device,))
  1630  
  1631          # If version >= 4, try block recovery before abort update
  1632          if partition == "system":
  1633            code = ErrorCode.SYSTEM_RECOVER_FAILURE
  1634          else:
  1635            code = ErrorCode.VENDOR_RECOVER_FAILURE
  1636          script.AppendExtra((
  1637              'ifelse (block_image_recover("{device}", "{ranges}") && '
  1638              'block_image_verify("{device}", '
  1639              'package_extract_file("{partition}.transfer.list"), '
  1640              '"{partition}.new.dat", "{partition}.patch.dat"), '
  1641              'ui_print("{partition} recovered successfully."), '
  1642              'abort("E{code}: {partition} partition fails to recover"));\n'
  1643              'endif;').format(device=self.device, ranges=ranges_str,
  1644                               partition=partition, code=code))
  1645  
  1646        # Abort the OTA update. Note that the incremental OTA cannot be applied
  1647        # even if it may match the checksum of the target partition.
  1648        # a) If version < 3, operations like move and erase will make changes
  1649        #    unconditionally and damage the partition.
  1650        # b) If version >= 3, it won't even reach here.
  1651        else:
  1652          if partition == "system":
  1653            code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
  1654          else:
  1655            code = ErrorCode.VENDOR_VERIFICATION_FAILURE
  1656          script.AppendExtra((
  1657              'abort("E%d: %s partition has unexpected contents");\n'
  1658              'endif;') % (code, partition))
  1659  
  1660    def _WritePostInstallVerifyScript(self, script):
  1661      partition = self.partition
  1662      script.Print('Verifying the updated %s image...' % (partition,))
  1663      # Unlike pre-install verification, clobbered_blocks should not be ignored.
  1664      ranges = self.tgt.care_map
  1665      ranges_str = ranges.to_string_raw()
  1666      script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
  1667                         self.device, ranges_str,
  1668                         self.tgt.TotalSha1(include_clobbered_blocks=True)))
  1669  
  1670      # Bug: 20881595
  1671      # Verify that extended blocks are really zeroed out.
  1672      if self.tgt.extended:
  1673        ranges_str = self.tgt.extended.to_string_raw()
  1674        script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
  1675                           self.device, ranges_str,
  1676                           self._HashZeroBlocks(self.tgt.extended.size())))
  1677        script.Print('Verified the updated %s image.' % (partition,))
  1678        if partition == "system":
  1679          code = ErrorCode.SYSTEM_NONZERO_CONTENTS
  1680        else:
  1681          code = ErrorCode.VENDOR_NONZERO_CONTENTS
  1682        script.AppendExtra(
  1683            'else\n'
  1684            '  abort("E%d: %s partition has unexpected non-zero contents after '
  1685            'OTA update");\n'
  1686            'endif;' % (code, partition))
  1687      else:
  1688        script.Print('Verified the updated %s image.' % (partition,))
  1689  
  1690      if partition == "system":
  1691        code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
  1692      else:
  1693        code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
  1694  
  1695      script.AppendExtra(
  1696          'else\n'
  1697          '  abort("E%d: %s partition has unexpected contents after OTA '
  1698          'update");\n'
  1699          'endif;' % (code, partition))
  1700  
  1701    def _WriteUpdate(self, script, output_zip):
  1702      ZipWrite(output_zip,
  1703               '{}.transfer.list'.format(self.path),
  1704               '{}.transfer.list'.format(self.partition))
  1705  
  1706      # For full OTA, compress the new.dat with brotli with quality 6 to reduce its size. Quailty 9
  1707      # almost triples the compression time but doesn't further reduce the size too much.
  1708      # For a typical 1.8G system.new.dat
  1709      #                       zip  | brotli(quality 6)  | brotli(quality 9)
  1710      #   compressed_size:    942M | 869M (~8% reduced) | 854M
  1711      #   compression_time:   75s  | 265s               | 719s
  1712      #   decompression_time: 15s  | 25s                | 25s
  1713  
  1714      if not self.src:
  1715        brotli_cmd = ['brotli', '--quality=6',
  1716                      '--output={}.new.dat.br'.format(self.path),
  1717                      '{}.new.dat'.format(self.path)]
  1718        print("Compressing {}.new.dat with brotli".format(self.partition))
  1719        p = Run(brotli_cmd, stdout=subprocess.PIPE)
  1720        p.communicate()
  1721        assert p.returncode == 0,\
  1722            'compression of {}.new.dat failed'.format(self.partition)
  1723  
  1724        new_data_name = '{}.new.dat.br'.format(self.partition)
  1725        ZipWrite(output_zip,
  1726                 '{}.new.dat.br'.format(self.path),
  1727                 new_data_name,
  1728                 compress_type=zipfile.ZIP_STORED)
  1729      else:
  1730        new_data_name = '{}.new.dat'.format(self.partition)
  1731        ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
  1732  
  1733      ZipWrite(output_zip,
  1734               '{}.patch.dat'.format(self.path),
  1735               '{}.patch.dat'.format(self.partition),
  1736               compress_type=zipfile.ZIP_STORED)
  1737  
  1738      if self.partition == "system":
  1739        code = ErrorCode.SYSTEM_UPDATE_FAILURE
  1740      else:
  1741        code = ErrorCode.VENDOR_UPDATE_FAILURE
  1742  
  1743      call = ('block_image_update("{device}", '
  1744              'package_extract_file("{partition}.transfer.list"), '
  1745              '"{new_data_name}", "{partition}.patch.dat") ||\n'
  1746              '  abort("E{code}: Failed to update {partition} image.");'.format(
  1747                  device=self.device, partition=self.partition,
  1748                  new_data_name=new_data_name, code=code))
  1749      script.AppendExtra(script.WordWrap(call))
  1750  
  1751    def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
  1752      data = source.ReadRangeSet(ranges)
  1753      ctx = sha1()
  1754  
  1755      for p in data:
  1756        ctx.update(p)
  1757  
  1758      return ctx.hexdigest()
  1759  
  1760    def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
  1761      """Return the hash value for all zero blocks."""
  1762      zero_block = '\x00' * 4096
  1763      ctx = sha1()
  1764      for _ in range(num_blocks):
  1765        ctx.update(zero_block)
  1766  
  1767      return ctx.hexdigest()
  1768  
  1769  
  1770  DataImage = blockimgdiff.DataImage
  1771  
  1772  # map recovery.fstab's fs_types to mount/format "partition types"
  1773  PARTITION_TYPES = {
  1774      "ext4": "EMMC",
  1775      "emmc": "EMMC",
  1776      "f2fs": "EMMC",
  1777      "squashfs": "EMMC"
  1778  }
  1779  
  1780  def GetTypeAndDevice(mount_point, info):
  1781    fstab = info["fstab"]
  1782    if fstab:
  1783      return (PARTITION_TYPES[fstab[mount_point].fs_type],
  1784              fstab[mount_point].device)
  1785    else:
  1786      raise KeyError
  1787  
  1788  
  1789  def ParseCertificate(data):
  1790    """Parses and converts a PEM-encoded certificate into DER-encoded.
  1791  
  1792    This gives the same result as `openssl x509 -in <filename> -outform DER`.
  1793  
  1794    Returns:
  1795      The decoded certificate string.
  1796    """
  1797    cert_buffer = []
  1798    save = False
  1799    for line in data.split("\n"):
  1800      if "--END CERTIFICATE--" in line:
  1801        break
  1802      if save:
  1803        cert_buffer.append(line)
  1804      if "--BEGIN CERTIFICATE--" in line:
  1805        save = True
  1806    cert = "".join(cert_buffer).decode('base64')
  1807    return cert
  1808  
  1809  
  1810  def ExtractPublicKey(cert):
  1811    """Extracts the public key (PEM-encoded) from the given certificate file.
  1812  
  1813    Args:
  1814      cert: The certificate filename.
  1815  
  1816    Returns:
  1817      The public key string.
  1818  
  1819    Raises:
  1820      AssertionError: On non-zero return from 'openssl'.
  1821    """
  1822    # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
  1823    # While openssl 1.1 writes the key into the given filename followed by '-out',
  1824    # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
  1825    # stdout instead.
  1826    cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
  1827    proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  1828    pubkey, stderrdata = proc.communicate()
  1829    assert proc.returncode == 0, \
  1830        'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
  1831    return pubkey
  1832  
  1833  
  1834  def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
  1835                        info_dict=None):
  1836    """Generates the recovery-from-boot patch and writes the script to output.
  1837  
  1838    Most of the space in the boot and recovery images is just the kernel, which is
  1839    identical for the two, so the resulting patch should be efficient. Add it to
  1840    the output zip, along with a shell script that is run from init.rc on first
  1841    boot to actually do the patching and install the new recovery image.
  1842  
  1843    Args:
  1844      input_dir: The top-level input directory of the target-files.zip.
  1845      output_sink: The callback function that writes the result.
  1846      recovery_img: File object for the recovery image.
  1847      boot_img: File objects for the boot image.
  1848      info_dict: A dict returned by common.LoadInfoDict() on the input
  1849          target_files. Will use OPTIONS.info_dict if None has been given.
  1850    """
  1851    if info_dict is None:
  1852      info_dict = OPTIONS.info_dict
  1853  
  1854    full_recovery_image = info_dict.get("full_recovery_image") == "true"
  1855  
  1856    if full_recovery_image:
  1857      output_sink("etc/recovery.img", recovery_img.data)
  1858  
  1859    else:
  1860      system_root_image = info_dict.get("system_root_image") == "true"
  1861      path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
  1862      # With system-root-image, boot and recovery images will have mismatching
  1863      # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
  1864      # to handle such a case.
  1865      if system_root_image:
  1866        diff_program = ["bsdiff"]
  1867        bonus_args = ""
  1868        assert not os.path.exists(path)
  1869      else:
  1870        diff_program = ["imgdiff"]
  1871        if os.path.exists(path):
  1872          diff_program.append("-b")
  1873          diff_program.append(path)
  1874          bonus_args = "-b /system/etc/recovery-resource.dat"
  1875        else:
  1876          bonus_args = ""
  1877  
  1878      d = Difference(recovery_img, boot_img, diff_program=diff_program)
  1879      _, _, patch = d.ComputePatch()
  1880      output_sink("recovery-from-boot.p", patch)
  1881  
  1882    try:
  1883      # The following GetTypeAndDevice()s need to use the path in the target
  1884      # info_dict instead of source_info_dict.
  1885      boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
  1886      recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
  1887    except KeyError:
  1888      return
  1889  
  1890    if full_recovery_image:
  1891      sh = """#!/system/bin/sh
  1892  if ! applypatch -c %(type)s:%(device)s:%(size)d:%(sha1)s; then
  1893    applypatch /system/etc/recovery.img %(type)s:%(device)s %(sha1)s %(size)d && log -t recovery "Installing new recovery image: succeeded" || log -t recovery "Installing new recovery image: failed"
  1894  else
  1895    log -t recovery "Recovery image already installed"
  1896  fi
  1897  """ % {'type': recovery_type,
  1898         'device': recovery_device,
  1899         'sha1': recovery_img.sha1,
  1900         'size': recovery_img.size}
  1901    else:
  1902      sh = """#!/system/bin/sh
  1903  if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
  1904    applypatch %(bonus_args)s %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s %(recovery_type)s:%(recovery_device)s %(recovery_sha1)s %(recovery_size)d %(boot_sha1)s:/system/recovery-from-boot.p && log -t recovery "Installing new recovery image: succeeded" || log -t recovery "Installing new recovery image: failed"
  1905  else
  1906    log -t recovery "Recovery image already installed"
  1907  fi
  1908  """ % {'boot_size': boot_img.size,
  1909         'boot_sha1': boot_img.sha1,
  1910         'recovery_size': recovery_img.size,
  1911         'recovery_sha1': recovery_img.sha1,
  1912         'boot_type': boot_type,
  1913         'boot_device': boot_device,
  1914         'recovery_type': recovery_type,
  1915         'recovery_device': recovery_device,
  1916         'bonus_args': bonus_args}
  1917  
  1918    # The install script location moved from /system/etc to /system/bin
  1919    # in the L release.
  1920    sh_location = "bin/install-recovery.sh"
  1921  
  1922    print("putting script in", sh_location)
  1923  
  1924    output_sink(sh_location, sh)