Home | History | Annotate | Download | only in releasetools
      1 #!/usr/bin/env python
      2 #
      3 # Copyright (C) 2014 The Android Open Source Project
      4 #
      5 # Licensed under the Apache License, Version 2.0 (the "License");
      6 # you may not use this file except in compliance with the License.
      7 # You may obtain a copy of the License at
      8 #
      9 #      http://www.apache.org/licenses/LICENSE-2.0
     10 #
     11 # Unless required by applicable law or agreed to in writing, software
     12 # distributed under the License is distributed on an "AS IS" BASIS,
     13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14 # See the License for the specific language governing permissions and
     15 # limitations under the License.
     16 
     17 """
     18 Given a target-files zipfile that does not contain images (ie, does
     19 not have an IMAGES/ top-level subdirectory), produce the images and
     20 add them to the zipfile.
     21 
     22 Usage:  add_img_to_target_files [flag] target_files
     23 
     24   -a  (--add_missing)
     25       Build and add missing images to "IMAGES/". If this option is
     26       not specified, this script will simply exit when "IMAGES/"
     27       directory exists in the target file.
     28 
     29   -r  (--rebuild_recovery)
     30       Rebuild the recovery patch and write it to the system image. Only
     31       meaningful when system image needs to be rebuilt.
     32 
     33   --replace_verity_private_key
     34       Replace the private key used for verity signing. (same as the option
     35       in sign_target_files_apks)
     36 
     37   --replace_verity_public_key
     38        Replace the certificate (public key) used for verity verification. (same
     39        as the option in sign_target_files_apks)
     40 
     41   --is_signing
     42       Skip building & adding the images for "userdata" and "cache" if we
     43       are signing the target files.
     44 """
     45 
     46 from __future__ import print_function
     47 
     48 import datetime
     49 import os
     50 import shlex
     51 import shutil
     52 import subprocess
     53 import sys
     54 import uuid
     55 import zipfile
     56 
     57 import build_image
     58 import common
     59 import rangelib
     60 import sparse_img
     61 
     62 if sys.hexversion < 0x02070000:
     63   print("Python 2.7 or newer is required.", file=sys.stderr)
     64   sys.exit(1)
     65 
     66 OPTIONS = common.OPTIONS
     67 
     68 OPTIONS.add_missing = False
     69 OPTIONS.rebuild_recovery = False
     70 OPTIONS.replace_updated_files_list = []
     71 OPTIONS.replace_verity_public_key = False
     72 OPTIONS.replace_verity_private_key = False
     73 OPTIONS.is_signing = False
     74 
     75 
     76 # Partitions that should have their care_map added to META/care_map.txt.
     77 PARTITIONS_WITH_CARE_MAP = ('system', 'vendor', 'product')
     78 
     79 
     80 class OutputFile(object):
     81   def __init__(self, output_zip, input_dir, prefix, name):
     82     self._output_zip = output_zip
     83     self.input_name = os.path.join(input_dir, prefix, name)
     84 
     85     if self._output_zip:
     86       self._zip_name = os.path.join(prefix, name)
     87 
     88       root, suffix = os.path.splitext(name)
     89       self.name = common.MakeTempFile(prefix=root + '-', suffix=suffix)
     90     else:
     91       self.name = self.input_name
     92 
     93   def Write(self):
     94     if self._output_zip:
     95       common.ZipWrite(self._output_zip, self.name, self._zip_name)
     96 
     97 
     98 def GetCareMap(which, imgname):
     99   """Returns the care_map string for the given partition.
    100 
    101   Args:
    102     which: The partition name, must be listed in PARTITIONS_WITH_CARE_MAP.
    103     imgname: The filename of the image.
    104 
    105   Returns:
    106     (which, care_map_ranges): care_map_ranges is the raw string of the care_map
    107     RangeSet.
    108   """
    109   assert which in PARTITIONS_WITH_CARE_MAP
    110 
    111   simg = sparse_img.SparseImage(imgname)
    112   care_map_ranges = simg.care_map
    113   key = which + "_adjusted_partition_size"
    114   adjusted_blocks = OPTIONS.info_dict.get(key)
    115   if adjusted_blocks:
    116     assert adjusted_blocks > 0, "blocks should be positive for " + which
    117     care_map_ranges = care_map_ranges.intersect(rangelib.RangeSet(
    118         "0-%d" % (adjusted_blocks,)))
    119 
    120   return [which, care_map_ranges.to_string_raw()]
    121 
    122 
    123 def AddSystem(output_zip, recovery_img=None, boot_img=None):
    124   """Turn the contents of SYSTEM into a system image and store it in
    125   output_zip. Returns the name of the system image file."""
    126 
    127   img = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "system.img")
    128   if os.path.exists(img.input_name):
    129     print("system.img already exists; no need to rebuild...")
    130     return img.input_name
    131 
    132   def output_sink(fn, data):
    133     ofile = open(os.path.join(OPTIONS.input_tmp, "SYSTEM", fn), "w")
    134     ofile.write(data)
    135     ofile.close()
    136 
    137     arc_name = "SYSTEM/" + fn
    138     if arc_name in output_zip.namelist():
    139       OPTIONS.replace_updated_files_list.append(arc_name)
    140     else:
    141       common.ZipWrite(output_zip, ofile.name, arc_name)
    142 
    143   if OPTIONS.rebuild_recovery:
    144     print("Building new recovery patch")
    145     common.MakeRecoveryPatch(OPTIONS.input_tmp, output_sink, recovery_img,
    146                              boot_img, info_dict=OPTIONS.info_dict)
    147 
    148   block_list = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "system.map")
    149   CreateImage(OPTIONS.input_tmp, OPTIONS.info_dict, "system", img,
    150               block_list=block_list)
    151 
    152   return img.name
    153 
    154 
    155 def AddSystemOther(output_zip):
    156   """Turn the contents of SYSTEM_OTHER into a system_other image
    157   and store it in output_zip."""
    158 
    159   img = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "system_other.img")
    160   if os.path.exists(img.input_name):
    161     print("system_other.img already exists; no need to rebuild...")
    162     return
    163 
    164   CreateImage(OPTIONS.input_tmp, OPTIONS.info_dict, "system_other", img)
    165 
    166 
    167 def AddVendor(output_zip):
    168   """Turn the contents of VENDOR into a vendor image and store in it
    169   output_zip."""
    170 
    171   img = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "vendor.img")
    172   if os.path.exists(img.input_name):
    173     print("vendor.img already exists; no need to rebuild...")
    174     return img.input_name
    175 
    176   block_list = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "vendor.map")
    177   CreateImage(OPTIONS.input_tmp, OPTIONS.info_dict, "vendor", img,
    178               block_list=block_list)
    179   return img.name
    180 
    181 
    182 def AddProduct(output_zip):
    183   """Turn the contents of PRODUCT into a product image and store it in
    184   output_zip."""
    185 
    186   img = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "product.img")
    187   if os.path.exists(img.input_name):
    188     print("product.img already exists; no need to rebuild...")
    189     return img.input_name
    190 
    191   block_list = OutputFile(
    192       output_zip, OPTIONS.input_tmp, "IMAGES", "product.map")
    193   CreateImage(
    194       OPTIONS.input_tmp, OPTIONS.info_dict, "product", img,
    195       block_list=block_list)
    196   return img.name
    197 
    198 
    199 def AddDtbo(output_zip):
    200   """Adds the DTBO image.
    201 
    202   Uses the image under IMAGES/ if it already exists. Otherwise looks for the
    203   image under PREBUILT_IMAGES/, signs it as needed, and returns the image name.
    204   """
    205   img = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "dtbo.img")
    206   if os.path.exists(img.input_name):
    207     print("dtbo.img already exists; no need to rebuild...")
    208     return img.input_name
    209 
    210   dtbo_prebuilt_path = os.path.join(
    211       OPTIONS.input_tmp, "PREBUILT_IMAGES", "dtbo.img")
    212   assert os.path.exists(dtbo_prebuilt_path)
    213   shutil.copy(dtbo_prebuilt_path, img.name)
    214 
    215   # AVB-sign the image as needed.
    216   if OPTIONS.info_dict.get("avb_enable") == "true":
    217     avbtool = os.getenv('AVBTOOL') or OPTIONS.info_dict["avb_avbtool"]
    218     part_size = OPTIONS.info_dict["dtbo_size"]
    219     # The AVB hash footer will be replaced if already present.
    220     cmd = [avbtool, "add_hash_footer", "--image", img.name,
    221            "--partition_size", str(part_size), "--partition_name", "dtbo"]
    222     common.AppendAVBSigningArgs(cmd, "dtbo")
    223     args = OPTIONS.info_dict.get("avb_dtbo_add_hash_footer_args")
    224     if args and args.strip():
    225       cmd.extend(shlex.split(args))
    226     p = common.Run(cmd, stdout=subprocess.PIPE)
    227     p.communicate()
    228     assert p.returncode == 0, \
    229         "avbtool add_hash_footer of %s failed" % (img.name,)
    230 
    231   img.Write()
    232   return img.name
    233 
    234 
    235 def CreateImage(input_dir, info_dict, what, output_file, block_list=None):
    236   print("creating " + what + ".img...")
    237 
    238   image_props = build_image.ImagePropFromGlobalDict(info_dict, what)
    239   fstab = info_dict["fstab"]
    240   mount_point = "/" + what
    241   if fstab and mount_point in fstab:
    242     image_props["fs_type"] = fstab[mount_point].fs_type
    243 
    244   # Use a fixed timestamp (01/01/2009) when packaging the image.
    245   # Bug: 24377993
    246   epoch = datetime.datetime.fromtimestamp(0)
    247   timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
    248   image_props["timestamp"] = int(timestamp)
    249 
    250   if what == "system":
    251     fs_config_prefix = ""
    252   else:
    253     fs_config_prefix = what + "_"
    254 
    255   fs_config = os.path.join(
    256       input_dir, "META/" + fs_config_prefix + "filesystem_config.txt")
    257   if not os.path.exists(fs_config):
    258     fs_config = None
    259 
    260   # Override values loaded from info_dict.
    261   if fs_config:
    262     image_props["fs_config"] = fs_config
    263   if block_list:
    264     image_props["block_list"] = block_list.name
    265 
    266   # Use repeatable ext4 FS UUID and hash_seed UUID (based on partition name and
    267   # build fingerprint).
    268   uuid_seed = what + "-"
    269   if "build.prop" in info_dict:
    270     build_prop = info_dict["build.prop"]
    271     if "ro.build.fingerprint" in build_prop:
    272       uuid_seed += build_prop["ro.build.fingerprint"]
    273     elif "ro.build.thumbprint" in build_prop:
    274       uuid_seed += build_prop["ro.build.thumbprint"]
    275   image_props["uuid"] = str(uuid.uuid5(uuid.NAMESPACE_URL, uuid_seed))
    276   hash_seed = "hash_seed-" + uuid_seed
    277   image_props["hash_seed"] = str(uuid.uuid5(uuid.NAMESPACE_URL, hash_seed))
    278 
    279   succ = build_image.BuildImage(os.path.join(input_dir, what.upper()),
    280                                 image_props, output_file.name)
    281   assert succ, "build " + what + ".img image failed"
    282 
    283   output_file.Write()
    284   if block_list:
    285     block_list.Write()
    286 
    287   # Set the 'adjusted_partition_size' that excludes the verity blocks of the
    288   # given image. When avb is enabled, this size is the max image size returned
    289   # by the avb tool.
    290   is_verity_partition = "verity_block_device" in image_props
    291   verity_supported = (image_props.get("verity") == "true" or
    292                       image_props.get("avb_enable") == "true")
    293   is_avb_enable = image_props.get("avb_hashtree_enable") == "true"
    294   if verity_supported and (is_verity_partition or is_avb_enable):
    295     adjusted_blocks_value = image_props.get("partition_size")
    296     if adjusted_blocks_value:
    297       adjusted_blocks_key = what + "_adjusted_partition_size"
    298       info_dict[adjusted_blocks_key] = int(adjusted_blocks_value)/4096 - 1
    299 
    300 
    301 def AddUserdata(output_zip):
    302   """Create a userdata image and store it in output_zip.
    303 
    304   In most case we just create and store an empty userdata.img;
    305   But the invoker can also request to create userdata.img with real
    306   data from the target files, by setting "userdata_img_with_data=true"
    307   in OPTIONS.info_dict.
    308   """
    309 
    310   img = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "userdata.img")
    311   if os.path.exists(img.input_name):
    312     print("userdata.img already exists; no need to rebuild...")
    313     return
    314 
    315   # Skip userdata.img if no size.
    316   image_props = build_image.ImagePropFromGlobalDict(OPTIONS.info_dict, "data")
    317   if not image_props.get("partition_size"):
    318     return
    319 
    320   print("creating userdata.img...")
    321 
    322   # Use a fixed timestamp (01/01/2009) when packaging the image.
    323   # Bug: 24377993
    324   epoch = datetime.datetime.fromtimestamp(0)
    325   timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
    326   image_props["timestamp"] = int(timestamp)
    327 
    328   if OPTIONS.info_dict.get("userdata_img_with_data") == "true":
    329     user_dir = os.path.join(OPTIONS.input_tmp, "DATA")
    330   else:
    331     user_dir = common.MakeTempDir()
    332 
    333   fstab = OPTIONS.info_dict["fstab"]
    334   if fstab:
    335     image_props["fs_type"] = fstab["/data"].fs_type
    336   succ = build_image.BuildImage(user_dir, image_props, img.name)
    337   assert succ, "build userdata.img image failed"
    338 
    339   common.CheckSize(img.name, "userdata.img", OPTIONS.info_dict)
    340   img.Write()
    341 
    342 
    343 def AppendVBMetaArgsForPartition(cmd, partition, img_path, public_key_dir):
    344   if not img_path:
    345     return
    346 
    347   # Check if chain partition is used.
    348   key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
    349   if key_path:
    350     # extract public key in AVB format to be included in vbmeta.img
    351     avbtool = os.getenv('AVBTOOL') or OPTIONS.info_dict["avb_avbtool"]
    352     public_key_path = os.path.join(public_key_dir, "%s.avbpubkey" % partition)
    353     p = common.Run([avbtool, "extract_public_key", "--key", key_path,
    354                     "--output", public_key_path],
    355                    stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    356     p.communicate()
    357     assert p.returncode == 0, \
    358         "avbtool extract_public_key fail for partition: %r" % partition
    359 
    360     rollback_index_location = OPTIONS.info_dict[
    361         "avb_" + partition + "_rollback_index_location"]
    362     cmd.extend(["--chain_partition", "%s:%s:%s" % (
    363         partition, rollback_index_location, public_key_path)])
    364   else:
    365     cmd.extend(["--include_descriptors_from_image", img_path])
    366 
    367 
    368 def AddVBMeta(output_zip, partitions):
    369   """Creates a VBMeta image and store it in output_zip.
    370 
    371   Args:
    372     output_zip: The output zip file, which needs to be already open.
    373     partitions: A dict that's keyed by partition names with image paths as
    374         values. Only valid partition names are accepted, which include 'boot',
    375         'recovery', 'system', 'vendor', 'dtbo'.
    376   """
    377   img = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "vbmeta.img")
    378   if os.path.exists(img.input_name):
    379     print("vbmeta.img already exists; not rebuilding...")
    380     return img.input_name
    381 
    382   avbtool = os.getenv('AVBTOOL') or OPTIONS.info_dict["avb_avbtool"]
    383   cmd = [avbtool, "make_vbmeta_image", "--output", img.name]
    384   common.AppendAVBSigningArgs(cmd, "vbmeta")
    385 
    386   public_key_dir = common.MakeTempDir(prefix="avbpubkey-")
    387   for partition, path in partitions.items():
    388     assert partition in common.AVB_PARTITIONS, 'Unknown partition: %s' % (
    389         partition,)
    390     assert os.path.exists(path), 'Failed to find %s for partition %s' % (
    391         path, partition)
    392     AppendVBMetaArgsForPartition(cmd, partition, path, public_key_dir)
    393 
    394   args = OPTIONS.info_dict.get("avb_vbmeta_args")
    395   if args and args.strip():
    396     split_args = shlex.split(args)
    397     for index, arg in enumerate(split_args[:-1]):
    398       # Sanity check that the image file exists. Some images might be defined
    399       # as a path relative to source tree, which may not be available at the
    400       # same location when running this script (we have the input target_files
    401       # zip only). For such cases, we additionally scan other locations (e.g.
    402       # IMAGES/, RADIO/, etc) before bailing out.
    403       if arg == '--include_descriptors_from_image':
    404         image_path = split_args[index + 1]
    405         if os.path.exists(image_path):
    406           continue
    407         found = False
    408         for dir_name in ['IMAGES', 'RADIO', 'VENDOR_IMAGES', 'PREBUILT_IMAGES']:
    409           alt_path = os.path.join(
    410               OPTIONS.input_tmp, dir_name, os.path.basename(image_path))
    411           if os.path.exists(alt_path):
    412             split_args[index + 1] = alt_path
    413             found = True
    414             break
    415         assert found, 'failed to find %s' % (image_path,)
    416     cmd.extend(split_args)
    417 
    418   p = common.Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    419   p.communicate()
    420   assert p.returncode == 0, "avbtool make_vbmeta_image failed"
    421   img.Write()
    422 
    423 
    424 def AddPartitionTable(output_zip):
    425   """Create a partition table image and store it in output_zip."""
    426 
    427   img = OutputFile(
    428       output_zip, OPTIONS.input_tmp, "IMAGES", "partition-table.img")
    429   bpt = OutputFile(
    430       output_zip, OPTIONS.input_tmp, "IMAGES", "partition-table.bpt")
    431 
    432   # use BPTTOOL from environ, or "bpttool" if empty or not set.
    433   bpttool = os.getenv("BPTTOOL") or "bpttool"
    434   cmd = [bpttool, "make_table", "--output_json", bpt.name,
    435          "--output_gpt", img.name]
    436   input_files_str = OPTIONS.info_dict["board_bpt_input_files"]
    437   input_files = input_files_str.split(" ")
    438   for i in input_files:
    439     cmd.extend(["--input", i])
    440   disk_size = OPTIONS.info_dict.get("board_bpt_disk_size")
    441   if disk_size:
    442     cmd.extend(["--disk_size", disk_size])
    443   args = OPTIONS.info_dict.get("board_bpt_make_table_args")
    444   if args:
    445     cmd.extend(shlex.split(args))
    446 
    447   p = common.Run(cmd, stdout=subprocess.PIPE)
    448   p.communicate()
    449   assert p.returncode == 0, "bpttool make_table failed"
    450 
    451   img.Write()
    452   bpt.Write()
    453 
    454 
    455 def AddCache(output_zip):
    456   """Create an empty cache image and store it in output_zip."""
    457 
    458   img = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "cache.img")
    459   if os.path.exists(img.input_name):
    460     print("cache.img already exists; no need to rebuild...")
    461     return
    462 
    463   image_props = build_image.ImagePropFromGlobalDict(OPTIONS.info_dict, "cache")
    464   # The build system has to explicitly request for cache.img.
    465   if "fs_type" not in image_props:
    466     return
    467 
    468   print("creating cache.img...")
    469 
    470   # Use a fixed timestamp (01/01/2009) when packaging the image.
    471   # Bug: 24377993
    472   epoch = datetime.datetime.fromtimestamp(0)
    473   timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
    474   image_props["timestamp"] = int(timestamp)
    475 
    476   user_dir = common.MakeTempDir()
    477 
    478   fstab = OPTIONS.info_dict["fstab"]
    479   if fstab:
    480     image_props["fs_type"] = fstab["/cache"].fs_type
    481   succ = build_image.BuildImage(user_dir, image_props, img.name)
    482   assert succ, "build cache.img image failed"
    483 
    484   common.CheckSize(img.name, "cache.img", OPTIONS.info_dict)
    485   img.Write()
    486 
    487 
    488 def AddRadioImagesForAbOta(output_zip, ab_partitions):
    489   """Adds the radio images needed for A/B OTA to the output file.
    490 
    491   It parses the list of A/B partitions, looks for the missing ones from RADIO/
    492   or VENDOR_IMAGES/ dirs, and copies them to IMAGES/ of the output file (or
    493   dir).
    494 
    495   It also ensures that on returning from the function all the listed A/B
    496   partitions must have their images available under IMAGES/.
    497 
    498   Args:
    499     output_zip: The output zip file (needs to be already open), or None to
    500         write images to OPTIONS.input_tmp/.
    501     ab_partitions: The list of A/B partitions.
    502 
    503   Raises:
    504     AssertionError: If it can't find an image.
    505   """
    506   for partition in ab_partitions:
    507     img_name = partition.strip() + ".img"
    508     prebuilt_path = os.path.join(OPTIONS.input_tmp, "IMAGES", img_name)
    509     if os.path.exists(prebuilt_path):
    510       print("%s already exists, no need to overwrite..." % (img_name,))
    511       continue
    512 
    513     img_radio_path = os.path.join(OPTIONS.input_tmp, "RADIO", img_name)
    514     if os.path.exists(img_radio_path):
    515       if output_zip:
    516         common.ZipWrite(output_zip, img_radio_path, "IMAGES/" + img_name)
    517       else:
    518         shutil.copy(img_radio_path, prebuilt_path)
    519       continue
    520 
    521     # Walk through VENDOR_IMAGES/ since files could be under subdirs.
    522     img_vendor_dir = os.path.join(OPTIONS.input_tmp, "VENDOR_IMAGES")
    523     for root, _, files in os.walk(img_vendor_dir):
    524       if img_name in files:
    525         if output_zip:
    526           common.ZipWrite(output_zip, os.path.join(root, img_name),
    527                           "IMAGES/" + img_name)
    528         else:
    529           shutil.copy(os.path.join(root, img_name), prebuilt_path)
    530         break
    531 
    532     # Assert that the image is present under IMAGES/ now.
    533     if output_zip:
    534       # Zip spec says: All slashes MUST be forward slashes.
    535       img_path = 'IMAGES/' + img_name
    536       assert img_path in output_zip.namelist(), "cannot find " + img_name
    537     else:
    538       img_path = os.path.join(OPTIONS.input_tmp, "IMAGES", img_name)
    539       assert os.path.exists(img_path), "cannot find " + img_name
    540 
    541 
    542 def AddCareMapTxtForAbOta(output_zip, ab_partitions, image_paths):
    543   """Generates and adds care_map.txt for system and vendor partitions.
    544 
    545   Args:
    546     output_zip: The output zip file (needs to be already open), or None to
    547         write images to OPTIONS.input_tmp/.
    548     ab_partitions: The list of A/B partitions.
    549     image_paths: A map from the partition name to the image path.
    550   """
    551   care_map_list = []
    552   for partition in ab_partitions:
    553     partition = partition.strip()
    554     if partition not in PARTITIONS_WITH_CARE_MAP:
    555       continue
    556 
    557     verity_block_device = "{}_verity_block_device".format(partition)
    558     avb_hashtree_enable = "avb_{}_hashtree_enable".format(partition)
    559     if (verity_block_device in OPTIONS.info_dict or
    560         OPTIONS.info_dict.get(avb_hashtree_enable) == "true"):
    561       image_path = image_paths[partition]
    562       assert os.path.exists(image_path)
    563       care_map_list += GetCareMap(partition, image_path)
    564 
    565   if care_map_list:
    566     care_map_path = "META/care_map.txt"
    567     if output_zip and care_map_path not in output_zip.namelist():
    568       common.ZipWriteStr(output_zip, care_map_path, '\n'.join(care_map_list))
    569     else:
    570       with open(os.path.join(OPTIONS.input_tmp, care_map_path), 'w') as fp:
    571         fp.write('\n'.join(care_map_list))
    572       if output_zip:
    573         OPTIONS.replace_updated_files_list.append(care_map_path)
    574 
    575 
    576 def AddPackRadioImages(output_zip, images):
    577   """Copies images listed in META/pack_radioimages.txt from RADIO/ to IMAGES/.
    578 
    579   Args:
    580     output_zip: The output zip file (needs to be already open), or None to
    581         write images to OPTIONS.input_tmp/.
    582     images: A list of image names.
    583 
    584   Raises:
    585     AssertionError: If a listed image can't be found.
    586   """
    587   for image in images:
    588     img_name = image.strip()
    589     _, ext = os.path.splitext(img_name)
    590     if not ext:
    591       img_name += ".img"
    592 
    593     prebuilt_path = os.path.join(OPTIONS.input_tmp, "IMAGES", img_name)
    594     if os.path.exists(prebuilt_path):
    595       print("%s already exists, no need to overwrite..." % (img_name,))
    596       continue
    597 
    598     img_radio_path = os.path.join(OPTIONS.input_tmp, "RADIO", img_name)
    599     assert os.path.exists(img_radio_path), \
    600         "Failed to find %s at %s" % (img_name, img_radio_path)
    601 
    602     if output_zip:
    603       common.ZipWrite(output_zip, img_radio_path, "IMAGES/" + img_name)
    604     else:
    605       shutil.copy(img_radio_path, prebuilt_path)
    606 
    607 
    608 def ReplaceUpdatedFiles(zip_filename, files_list):
    609   """Updates all the ZIP entries listed in files_list.
    610 
    611   For now the list includes META/care_map.txt, and the related files under
    612   SYSTEM/ after rebuilding recovery.
    613   """
    614   common.ZipDelete(zip_filename, files_list)
    615   output_zip = zipfile.ZipFile(zip_filename, "a",
    616                                compression=zipfile.ZIP_DEFLATED,
    617                                allowZip64=True)
    618   for item in files_list:
    619     file_path = os.path.join(OPTIONS.input_tmp, item)
    620     assert os.path.exists(file_path)
    621     common.ZipWrite(output_zip, file_path, arcname=item)
    622   common.ZipClose(output_zip)
    623 
    624 
    625 def AddImagesToTargetFiles(filename):
    626   """Creates and adds images (boot/recovery/system/...) to a target_files.zip.
    627 
    628   It works with either a zip file (zip mode), or a directory that contains the
    629   files to be packed into a target_files.zip (dir mode). The latter is used when
    630   being called from build/make/core/Makefile.
    631 
    632   The images will be created under IMAGES/ in the input target_files.zip.
    633 
    634   Args:
    635     filename: the target_files.zip, or the zip root directory.
    636   """
    637   if os.path.isdir(filename):
    638     OPTIONS.input_tmp = os.path.abspath(filename)
    639   else:
    640     OPTIONS.input_tmp = common.UnzipTemp(filename)
    641 
    642   if not OPTIONS.add_missing:
    643     if os.path.isdir(os.path.join(OPTIONS.input_tmp, "IMAGES")):
    644       print("target_files appears to already contain images.")
    645       sys.exit(1)
    646 
    647   OPTIONS.info_dict = common.LoadInfoDict(OPTIONS.input_tmp, OPTIONS.input_tmp)
    648 
    649   has_recovery = OPTIONS.info_dict.get("no_recovery") != "true"
    650 
    651   # {vendor,product}.img is unlike system.img or system_other.img. Because it
    652   # could be built from source, or dropped into target_files.zip as a prebuilt
    653   # blob. We consider either of them as {vendor,product}.img being available,
    654   # which could be used when generating vbmeta.img for AVB.
    655   has_vendor = (os.path.isdir(os.path.join(OPTIONS.input_tmp, "VENDOR")) or
    656                 os.path.exists(os.path.join(OPTIONS.input_tmp, "IMAGES",
    657                                             "vendor.img")))
    658   has_product = (os.path.isdir(os.path.join(OPTIONS.input_tmp, "PRODUCT")) or
    659                  os.path.exists(os.path.join(OPTIONS.input_tmp, "IMAGES",
    660                                              "product.img")))
    661   has_system_other = os.path.isdir(os.path.join(OPTIONS.input_tmp,
    662                                                 "SYSTEM_OTHER"))
    663 
    664   # Set up the output destination. It writes to the given directory for dir
    665   # mode; otherwise appends to the given ZIP.
    666   if os.path.isdir(filename):
    667     output_zip = None
    668   else:
    669     output_zip = zipfile.ZipFile(filename, "a",
    670                                  compression=zipfile.ZIP_DEFLATED,
    671                                  allowZip64=True)
    672 
    673   # Always make input_tmp/IMAGES available, since we may stage boot / recovery
    674   # images there even under zip mode. The directory will be cleaned up as part
    675   # of OPTIONS.input_tmp.
    676   images_dir = os.path.join(OPTIONS.input_tmp, "IMAGES")
    677   if not os.path.isdir(images_dir):
    678     os.makedirs(images_dir)
    679 
    680   # A map between partition names and their paths, which could be used when
    681   # generating AVB vbmeta image.
    682   partitions = dict()
    683 
    684   def banner(s):
    685     print("\n\n++++ " + s + " ++++\n\n")
    686 
    687   banner("boot")
    688   # common.GetBootableImage() returns the image directly if present.
    689   boot_image = common.GetBootableImage(
    690       "IMAGES/boot.img", "boot.img", OPTIONS.input_tmp, "BOOT")
    691   # boot.img may be unavailable in some targets (e.g. aosp_arm64).
    692   if boot_image:
    693     partitions['boot'] = os.path.join(OPTIONS.input_tmp, "IMAGES", "boot.img")
    694     if not os.path.exists(partitions['boot']):
    695       boot_image.WriteToDir(OPTIONS.input_tmp)
    696       if output_zip:
    697         boot_image.AddToZip(output_zip)
    698 
    699   recovery_image = None
    700   if has_recovery:
    701     banner("recovery")
    702     recovery_image = common.GetBootableImage(
    703         "IMAGES/recovery.img", "recovery.img", OPTIONS.input_tmp, "RECOVERY")
    704     assert recovery_image, "Failed to create recovery.img."
    705     partitions['recovery'] = os.path.join(
    706         OPTIONS.input_tmp, "IMAGES", "recovery.img")
    707     if not os.path.exists(partitions['recovery']):
    708       recovery_image.WriteToDir(OPTIONS.input_tmp)
    709       if output_zip:
    710         recovery_image.AddToZip(output_zip)
    711 
    712       banner("recovery (two-step image)")
    713       # The special recovery.img for two-step package use.
    714       recovery_two_step_image = common.GetBootableImage(
    715           "IMAGES/recovery-two-step.img", "recovery-two-step.img",
    716           OPTIONS.input_tmp, "RECOVERY", two_step_image=True)
    717       assert recovery_two_step_image, "Failed to create recovery-two-step.img."
    718       recovery_two_step_image_path = os.path.join(
    719           OPTIONS.input_tmp, "IMAGES", "recovery-two-step.img")
    720       if not os.path.exists(recovery_two_step_image_path):
    721         recovery_two_step_image.WriteToDir(OPTIONS.input_tmp)
    722         if output_zip:
    723           recovery_two_step_image.AddToZip(output_zip)
    724 
    725   banner("system")
    726   partitions['system'] = AddSystem(
    727       output_zip, recovery_img=recovery_image, boot_img=boot_image)
    728 
    729   if has_vendor:
    730     banner("vendor")
    731     partitions['vendor'] = AddVendor(output_zip)
    732 
    733   if has_product:
    734     banner("product")
    735     partitions['product'] = AddProduct(output_zip)
    736 
    737   if has_system_other:
    738     banner("system_other")
    739     AddSystemOther(output_zip)
    740 
    741   if not OPTIONS.is_signing:
    742     banner("userdata")
    743     AddUserdata(output_zip)
    744     banner("cache")
    745     AddCache(output_zip)
    746 
    747   if OPTIONS.info_dict.get("board_bpt_enable") == "true":
    748     banner("partition-table")
    749     AddPartitionTable(output_zip)
    750 
    751   if OPTIONS.info_dict.get("has_dtbo") == "true":
    752     banner("dtbo")
    753     partitions['dtbo'] = AddDtbo(output_zip)
    754 
    755   if OPTIONS.info_dict.get("avb_enable") == "true":
    756     banner("vbmeta")
    757     AddVBMeta(output_zip, partitions)
    758 
    759   banner("radio")
    760   ab_partitions_txt = os.path.join(OPTIONS.input_tmp, "META",
    761                                    "ab_partitions.txt")
    762   if os.path.exists(ab_partitions_txt):
    763     with open(ab_partitions_txt, 'r') as f:
    764       ab_partitions = f.readlines()
    765 
    766     # For devices using A/B update, copy over images from RADIO/ and/or
    767     # VENDOR_IMAGES/ to IMAGES/ and make sure we have all the needed
    768     # images ready under IMAGES/. All images should have '.img' as extension.
    769     AddRadioImagesForAbOta(output_zip, ab_partitions)
    770 
    771     # Generate care_map.txt for system and vendor partitions (if present), then
    772     # write this file to target_files package.
    773     AddCareMapTxtForAbOta(output_zip, ab_partitions, partitions)
    774 
    775   # Radio images that need to be packed into IMAGES/, and product-img.zip.
    776   pack_radioimages_txt = os.path.join(
    777       OPTIONS.input_tmp, "META", "pack_radioimages.txt")
    778   if os.path.exists(pack_radioimages_txt):
    779     with open(pack_radioimages_txt, 'r') as f:
    780       AddPackRadioImages(output_zip, f.readlines())
    781 
    782   if output_zip:
    783     common.ZipClose(output_zip)
    784     if OPTIONS.replace_updated_files_list:
    785       ReplaceUpdatedFiles(output_zip.filename,
    786                           OPTIONS.replace_updated_files_list)
    787 
    788 
    789 def main(argv):
    790   def option_handler(o, a):
    791     if o in ("-a", "--add_missing"):
    792       OPTIONS.add_missing = True
    793     elif o in ("-r", "--rebuild_recovery",):
    794       OPTIONS.rebuild_recovery = True
    795     elif o == "--replace_verity_private_key":
    796       OPTIONS.replace_verity_private_key = (True, a)
    797     elif o == "--replace_verity_public_key":
    798       OPTIONS.replace_verity_public_key = (True, a)
    799     elif o == "--is_signing":
    800       OPTIONS.is_signing = True
    801     else:
    802       return False
    803     return True
    804 
    805   args = common.ParseOptions(
    806       argv, __doc__, extra_opts="ar",
    807       extra_long_opts=["add_missing", "rebuild_recovery",
    808                        "replace_verity_public_key=",
    809                        "replace_verity_private_key=",
    810                        "is_signing"],
    811       extra_option_handler=option_handler)
    812 
    813 
    814   if len(args) != 1:
    815     common.Usage(__doc__)
    816     sys.exit(1)
    817 
    818   AddImagesToTargetFiles(args[0])
    819   print("done.")
    820 
    821 if __name__ == '__main__':
    822   try:
    823     common.CloseInheritedPipes()
    824     main(sys.argv[1:])
    825   except common.ExternalError as e:
    826     print("\n   ERROR: %s\n" % (e,))
    827     sys.exit(1)
    828   finally:
    829     common.Cleanup()
    830