Home | History | Annotate | Download | only in releasetools
      1 #!/usr/bin/env python
      2 #
      3 # Copyright (C) 2011 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 Builds output_image from the given input_directory, properties_file,
     19 and writes the image to target_output_directory.
     20 
     21 Usage:  build_image.py input_directory properties_file output_image \\
     22             target_output_directory
     23 """
     24 
     25 from __future__ import print_function
     26 
     27 import os
     28 import os.path
     29 import re
     30 import shlex
     31 import shutil
     32 import subprocess
     33 import sys
     34 
     35 import common
     36 import sparse_img
     37 
     38 
     39 OPTIONS = common.OPTIONS
     40 
     41 FIXED_SALT = "aee087a5be3b982978c923f566a94613496b417f2af592639bc80d141e34dfe7"
     42 BLOCK_SIZE = 4096
     43 
     44 
     45 def RunCommand(cmd, verbose=None):
     46   """Echo and run the given command.
     47 
     48   Args:
     49     cmd: the command represented as a list of strings.
     50     verbose: show commands being executed.
     51   Returns:
     52     A tuple of the output and the exit code.
     53   """
     54   if verbose is None:
     55     verbose = OPTIONS.verbose
     56   if verbose:
     57     print("Running: " + " ".join(cmd))
     58   p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
     59   output, _ = p.communicate()
     60 
     61   if verbose:
     62     print(output.rstrip())
     63   return (output, p.returncode)
     64 
     65 
     66 def GetVerityFECSize(partition_size):
     67   cmd = ["fec", "-s", str(partition_size)]
     68   output, exit_code = RunCommand(cmd, False)
     69   if exit_code != 0:
     70     return False, 0
     71   return True, int(output)
     72 
     73 
     74 def GetVerityTreeSize(partition_size):
     75   cmd = ["build_verity_tree", "-s", str(partition_size)]
     76   output, exit_code = RunCommand(cmd, False)
     77   if exit_code != 0:
     78     return False, 0
     79   return True, int(output)
     80 
     81 
     82 def GetVerityMetadataSize(partition_size):
     83   cmd = ["system/extras/verity/build_verity_metadata.py", "size",
     84          str(partition_size)]
     85   output, exit_code = RunCommand(cmd, False)
     86   if exit_code != 0:
     87     return False, 0
     88   return True, int(output)
     89 
     90 
     91 def GetVeritySize(partition_size, fec_supported):
     92   success, verity_tree_size = GetVerityTreeSize(partition_size)
     93   if not success:
     94     return 0
     95   success, verity_metadata_size = GetVerityMetadataSize(partition_size)
     96   if not success:
     97     return 0
     98   verity_size = verity_tree_size + verity_metadata_size
     99   if fec_supported:
    100     success, fec_size = GetVerityFECSize(partition_size + verity_size)
    101     if not success:
    102       return 0
    103     return verity_size + fec_size
    104   return verity_size
    105 
    106 
    107 def GetSimgSize(image_file):
    108   simg = sparse_img.SparseImage(image_file, build_map=False)
    109   return simg.blocksize * simg.total_blocks
    110 
    111 
    112 def ZeroPadSimg(image_file, pad_size):
    113   blocks = pad_size // BLOCK_SIZE
    114   print("Padding %d blocks (%d bytes)" % (blocks, pad_size))
    115   simg = sparse_img.SparseImage(image_file, mode="r+b", build_map=False)
    116   simg.AppendFillChunk(0, blocks)
    117 
    118 
    119 def AVBCalcMaxImageSize(avbtool, footer_type, partition_size, additional_args):
    120   """Calculates max image size for a given partition size.
    121 
    122   Args:
    123     avbtool: String with path to avbtool.
    124     footer_type: 'hash' or 'hashtree' for generating footer.
    125     partition_size: The size of the partition in question.
    126     additional_args: Additional arguments to pass to 'avbtool
    127       add_hashtree_image'.
    128   Returns:
    129     The maximum image size or 0 if an error occurred.
    130   """
    131   cmd = [avbtool, "add_%s_footer" % footer_type,
    132          "--partition_size", partition_size, "--calc_max_image_size"]
    133   cmd.extend(shlex.split(additional_args))
    134 
    135   (output, exit_code) = RunCommand(cmd)
    136   if exit_code != 0:
    137     return 0
    138   else:
    139     return int(output)
    140 
    141 
    142 def AVBAddFooter(image_path, avbtool, footer_type, partition_size,
    143                  partition_name, key_path, algorithm, salt,
    144                  additional_args):
    145   """Adds dm-verity hashtree and AVB metadata to an image.
    146 
    147   Args:
    148     image_path: Path to image to modify.
    149     avbtool: String with path to avbtool.
    150     footer_type: 'hash' or 'hashtree' for generating footer.
    151     partition_size: The size of the partition in question.
    152     partition_name: The name of the partition - will be embedded in metadata.
    153     key_path: Path to key to use or None.
    154     algorithm: Name of algorithm to use or None.
    155     salt: The salt to use (a hexadecimal string) or None.
    156     additional_args: Additional arguments to pass to 'avbtool
    157         add_hashtree_image'.
    158 
    159   Returns:
    160     True if the operation succeeded.
    161   """
    162   cmd = [avbtool, "add_%s_footer" % footer_type,
    163          "--partition_size", partition_size,
    164          "--partition_name", partition_name,
    165          "--image", image_path]
    166 
    167   if key_path and algorithm:
    168     cmd.extend(["--key", key_path, "--algorithm", algorithm])
    169   if salt:
    170     cmd.extend(["--salt", salt])
    171 
    172   cmd.extend(shlex.split(additional_args))
    173 
    174   (_, exit_code) = RunCommand(cmd)
    175   return exit_code == 0
    176 
    177 
    178 def AdjustPartitionSizeForVerity(partition_size, fec_supported):
    179   """Modifies the provided partition size to account for the verity metadata.
    180 
    181   This information is used to size the created image appropriately.
    182 
    183   Args:
    184     partition_size: the size of the partition to be verified.
    185 
    186   Returns:
    187     A tuple of the size of the partition adjusted for verity metadata, and
    188     the size of verity metadata.
    189   """
    190   key = "%d %d" % (partition_size, fec_supported)
    191   if key in AdjustPartitionSizeForVerity.results:
    192     return AdjustPartitionSizeForVerity.results[key]
    193 
    194   hi = partition_size
    195   if hi % BLOCK_SIZE != 0:
    196     hi = (hi // BLOCK_SIZE) * BLOCK_SIZE
    197 
    198   # verity tree and fec sizes depend on the partition size, which
    199   # means this estimate is always going to be unnecessarily small
    200   verity_size = GetVeritySize(hi, fec_supported)
    201   lo = partition_size - verity_size
    202   result = lo
    203 
    204   # do a binary search for the optimal size
    205   while lo < hi:
    206     i = ((lo + hi) // (2 * BLOCK_SIZE)) * BLOCK_SIZE
    207     v = GetVeritySize(i, fec_supported)
    208     if i + v <= partition_size:
    209       if result < i:
    210         result = i
    211         verity_size = v
    212       lo = i + BLOCK_SIZE
    213     else:
    214       hi = i
    215 
    216   if OPTIONS.verbose:
    217     print("Adjusted partition size for verity, partition_size: {},"
    218           " verity_size: {}".format(result, verity_size))
    219   AdjustPartitionSizeForVerity.results[key] = (result, verity_size)
    220   return (result, verity_size)
    221 
    222 
    223 AdjustPartitionSizeForVerity.results = {}
    224 
    225 
    226 def BuildVerityFEC(sparse_image_path, verity_path, verity_fec_path,
    227                    padding_size):
    228   cmd = ["fec", "-e", "-p", str(padding_size), sparse_image_path,
    229          verity_path, verity_fec_path]
    230   output, exit_code = RunCommand(cmd)
    231   if exit_code != 0:
    232     print("Could not build FEC data! Error: %s" % output)
    233     return False
    234   return True
    235 
    236 
    237 def BuildVerityTree(sparse_image_path, verity_image_path, prop_dict):
    238   cmd = ["build_verity_tree", "-A", FIXED_SALT, sparse_image_path,
    239          verity_image_path]
    240   output, exit_code = RunCommand(cmd)
    241   if exit_code != 0:
    242     print("Could not build verity tree! Error: %s" % output)
    243     return False
    244   root, salt = output.split()
    245   prop_dict["verity_root_hash"] = root
    246   prop_dict["verity_salt"] = salt
    247   return True
    248 
    249 
    250 def BuildVerityMetadata(image_size, verity_metadata_path, root_hash, salt,
    251                         block_device, signer_path, key, signer_args,
    252                         verity_disable):
    253   cmd = ["system/extras/verity/build_verity_metadata.py", "build",
    254          str(image_size), verity_metadata_path, root_hash, salt, block_device,
    255          signer_path, key]
    256   if signer_args:
    257     cmd.append("--signer_args=\"%s\"" % (' '.join(signer_args),))
    258   if verity_disable:
    259     cmd.append("--verity_disable")
    260   output, exit_code = RunCommand(cmd)
    261   if exit_code != 0:
    262     print("Could not build verity metadata! Error: %s" % output)
    263     return False
    264   return True
    265 
    266 
    267 def Append2Simg(sparse_image_path, unsparse_image_path, error_message):
    268   """Appends the unsparse image to the given sparse image.
    269 
    270   Args:
    271     sparse_image_path: the path to the (sparse) image
    272     unsparse_image_path: the path to the (unsparse) image
    273   Returns:
    274     True on success, False on failure.
    275   """
    276   cmd = ["append2simg", sparse_image_path, unsparse_image_path]
    277   output, exit_code = RunCommand(cmd)
    278   if exit_code != 0:
    279     print("%s: %s" % (error_message, output))
    280     return False
    281   return True
    282 
    283 
    284 def Append(target, file_to_append, error_message):
    285   """Appends file_to_append to target."""
    286   try:
    287     with open(target, "a") as out_file, open(file_to_append, "r") as input_file:
    288       for line in input_file:
    289         out_file.write(line)
    290   except IOError:
    291     print(error_message)
    292     return False
    293   return True
    294 
    295 
    296 def BuildVerifiedImage(data_image_path, verity_image_path,
    297                        verity_metadata_path, verity_fec_path,
    298                        padding_size, fec_supported):
    299   if not Append(verity_image_path, verity_metadata_path,
    300                 "Could not append verity metadata!"):
    301     return False
    302 
    303   if fec_supported:
    304     # build FEC for the entire partition, including metadata
    305     if not BuildVerityFEC(data_image_path, verity_image_path,
    306                           verity_fec_path, padding_size):
    307       return False
    308 
    309     if not Append(verity_image_path, verity_fec_path, "Could not append FEC!"):
    310       return False
    311 
    312   if not Append2Simg(data_image_path, verity_image_path,
    313                      "Could not append verity data!"):
    314     return False
    315   return True
    316 
    317 
    318 def UnsparseImage(sparse_image_path, replace=True):
    319   img_dir = os.path.dirname(sparse_image_path)
    320   unsparse_image_path = "unsparse_" + os.path.basename(sparse_image_path)
    321   unsparse_image_path = os.path.join(img_dir, unsparse_image_path)
    322   if os.path.exists(unsparse_image_path):
    323     if replace:
    324       os.unlink(unsparse_image_path)
    325     else:
    326       return True, unsparse_image_path
    327   inflate_command = ["simg2img", sparse_image_path, unsparse_image_path]
    328   (inflate_output, exit_code) = RunCommand(inflate_command)
    329   if exit_code != 0:
    330     print("Error: '%s' failed with exit code %d:\n%s" % (
    331         inflate_command, exit_code, inflate_output))
    332     os.remove(unsparse_image_path)
    333     return False, None
    334   return True, unsparse_image_path
    335 
    336 
    337 def MakeVerityEnabledImage(out_file, fec_supported, prop_dict):
    338   """Creates an image that is verifiable using dm-verity.
    339 
    340   Args:
    341     out_file: the location to write the verifiable image at
    342     prop_dict: a dictionary of properties required for image creation and
    343                verification
    344   Returns:
    345     True on success, False otherwise.
    346   """
    347   # get properties
    348   image_size = int(prop_dict["partition_size"])
    349   block_dev = prop_dict["verity_block_device"]
    350   signer_key = prop_dict["verity_key"] + ".pk8"
    351   if OPTIONS.verity_signer_path is not None:
    352     signer_path = OPTIONS.verity_signer_path
    353   else:
    354     signer_path = prop_dict["verity_signer_cmd"]
    355   signer_args = OPTIONS.verity_signer_args
    356 
    357   # make a tempdir
    358   tempdir_name = common.MakeTempDir(suffix="_verity_images")
    359 
    360   # get partial image paths
    361   verity_image_path = os.path.join(tempdir_name, "verity.img")
    362   verity_metadata_path = os.path.join(tempdir_name, "verity_metadata.img")
    363   verity_fec_path = os.path.join(tempdir_name, "verity_fec.img")
    364 
    365   # build the verity tree and get the root hash and salt
    366   if not BuildVerityTree(out_file, verity_image_path, prop_dict):
    367     return False
    368 
    369   # build the metadata blocks
    370   root_hash = prop_dict["verity_root_hash"]
    371   salt = prop_dict["verity_salt"]
    372   verity_disable = "verity_disable" in prop_dict
    373   if not BuildVerityMetadata(image_size, verity_metadata_path, root_hash, salt,
    374                              block_dev, signer_path, signer_key, signer_args,
    375                              verity_disable):
    376     return False
    377 
    378   # build the full verified image
    379   target_size = int(prop_dict["original_partition_size"])
    380   verity_size = int(prop_dict["verity_size"])
    381 
    382   padding_size = target_size - image_size - verity_size
    383   assert padding_size >= 0
    384 
    385   if not BuildVerifiedImage(out_file,
    386                             verity_image_path,
    387                             verity_metadata_path,
    388                             verity_fec_path,
    389                             padding_size,
    390                             fec_supported):
    391     return False
    392 
    393   return True
    394 
    395 
    396 def ConvertBlockMapToBaseFs(block_map_file):
    397   base_fs_file = common.MakeTempFile(prefix="script_gen_", suffix=".base_fs")
    398   convert_command = ["blk_alloc_to_base_fs", block_map_file, base_fs_file]
    399   (_, exit_code) = RunCommand(convert_command)
    400   return base_fs_file if exit_code == 0 else None
    401 
    402 
    403 def CheckHeadroom(ext4fs_output, prop_dict):
    404   """Checks if there's enough headroom space available.
    405 
    406   Headroom is the reserved space on system image (via PRODUCT_SYSTEM_HEADROOM),
    407   which is useful for devices with low disk space that have system image
    408   variation between builds. The 'partition_headroom' in prop_dict is the size
    409   in bytes, while the numbers in 'ext4fs_output' are for 4K-blocks.
    410 
    411   Args:
    412     ext4fs_output: The output string from mke2fs command.
    413     prop_dict: The property dict.
    414 
    415   Returns:
    416     The check result.
    417 
    418   Raises:
    419     AssertionError: On invalid input.
    420   """
    421   assert ext4fs_output is not None
    422   assert prop_dict.get('fs_type', '').startswith('ext4')
    423   assert 'partition_headroom' in prop_dict
    424   assert 'mount_point' in prop_dict
    425 
    426   ext4fs_stats = re.compile(
    427       r'Created filesystem with .* (?P<used_blocks>[0-9]+)/'
    428       r'(?P<total_blocks>[0-9]+) blocks')
    429   last_line = ext4fs_output.strip().split('\n')[-1]
    430   m = ext4fs_stats.match(last_line)
    431   used_blocks = int(m.groupdict().get('used_blocks'))
    432   total_blocks = int(m.groupdict().get('total_blocks'))
    433   headroom_blocks = int(prop_dict['partition_headroom']) / BLOCK_SIZE
    434   adjusted_blocks = total_blocks - headroom_blocks
    435   if used_blocks > adjusted_blocks:
    436     mount_point = prop_dict["mount_point"]
    437     print("Error: Not enough room on %s (total: %d blocks, used: %d blocks, "
    438           "headroom: %d blocks, available: %d blocks)" % (
    439               mount_point, total_blocks, used_blocks, headroom_blocks,
    440               adjusted_blocks))
    441     return False
    442   return True
    443 
    444 
    445 def BuildImage(in_dir, prop_dict, out_file, target_out=None):
    446   """Build an image to out_file from in_dir with property prop_dict.
    447 
    448   Args:
    449     in_dir: path of input directory.
    450     prop_dict: property dictionary.
    451     out_file: path of the output image file.
    452     target_out: path of the product out directory to read device specific FS
    453         config files.
    454 
    455   Returns:
    456     True iff the image is built successfully.
    457   """
    458   # system_root_image=true: build a system.img that combines the contents of
    459   # /system and the ramdisk, and can be mounted at the root of the file system.
    460   origin_in = in_dir
    461   fs_config = prop_dict.get("fs_config")
    462   if (prop_dict.get("system_root_image") == "true" and
    463       prop_dict["mount_point"] == "system"):
    464     in_dir = common.MakeTempDir()
    465     # Change the mount point to "/".
    466     prop_dict["mount_point"] = "/"
    467     if fs_config:
    468       # We need to merge the fs_config files of system and ramdisk.
    469       merged_fs_config = common.MakeTempFile(prefix="root_fs_config",
    470                                              suffix=".txt")
    471       with open(merged_fs_config, "w") as fw:
    472         if "ramdisk_fs_config" in prop_dict:
    473           with open(prop_dict["ramdisk_fs_config"]) as fr:
    474             fw.writelines(fr.readlines())
    475         with open(fs_config) as fr:
    476           fw.writelines(fr.readlines())
    477       fs_config = merged_fs_config
    478 
    479   build_command = []
    480   fs_type = prop_dict.get("fs_type", "")
    481   run_e2fsck = False
    482 
    483   fs_spans_partition = True
    484   if fs_type.startswith("squash"):
    485     fs_spans_partition = False
    486 
    487   is_verity_partition = "verity_block_device" in prop_dict
    488   verity_supported = prop_dict.get("verity") == "true"
    489   verity_fec_supported = prop_dict.get("verity_fec") == "true"
    490 
    491   # Adjust the partition size to make room for the hashes if this is to be
    492   # verified.
    493   if verity_supported and is_verity_partition:
    494     partition_size = int(prop_dict.get("partition_size"))
    495     (adjusted_size, verity_size) = AdjustPartitionSizeForVerity(
    496         partition_size, verity_fec_supported)
    497     if not adjusted_size:
    498       return False
    499     prop_dict["partition_size"] = str(adjusted_size)
    500     prop_dict["original_partition_size"] = str(partition_size)
    501     prop_dict["verity_size"] = str(verity_size)
    502 
    503   # Adjust partition size for AVB hash footer or AVB hashtree footer.
    504   avb_footer_type = ''
    505   if prop_dict.get("avb_hash_enable") == "true":
    506     avb_footer_type = 'hash'
    507   elif prop_dict.get("avb_hashtree_enable") == "true":
    508     avb_footer_type = 'hashtree'
    509 
    510   if avb_footer_type:
    511     avbtool = prop_dict["avb_avbtool"]
    512     partition_size = prop_dict["partition_size"]
    513     # avb_add_hash_footer_args or avb_add_hashtree_footer_args.
    514     additional_args = prop_dict["avb_add_" + avb_footer_type + "_footer_args"]
    515     max_image_size = AVBCalcMaxImageSize(avbtool, avb_footer_type,
    516                                          partition_size, additional_args)
    517     if max_image_size == 0:
    518       return False
    519     prop_dict["partition_size"] = str(max_image_size)
    520     prop_dict["original_partition_size"] = partition_size
    521 
    522   if fs_type.startswith("ext"):
    523     build_command = [prop_dict["ext_mkuserimg"]]
    524     if "extfs_sparse_flag" in prop_dict:
    525       build_command.append(prop_dict["extfs_sparse_flag"])
    526       run_e2fsck = True
    527     build_command.extend([in_dir, out_file, fs_type,
    528                           prop_dict["mount_point"]])
    529     build_command.append(prop_dict["partition_size"])
    530     if "journal_size" in prop_dict:
    531       build_command.extend(["-j", prop_dict["journal_size"]])
    532     if "timestamp" in prop_dict:
    533       build_command.extend(["-T", str(prop_dict["timestamp"])])
    534     if fs_config:
    535       build_command.extend(["-C", fs_config])
    536     if target_out:
    537       build_command.extend(["-D", target_out])
    538     if "block_list" in prop_dict:
    539       build_command.extend(["-B", prop_dict["block_list"]])
    540     if "base_fs_file" in prop_dict:
    541       base_fs_file = ConvertBlockMapToBaseFs(prop_dict["base_fs_file"])
    542       if base_fs_file is None:
    543         return False
    544       build_command.extend(["-d", base_fs_file])
    545     build_command.extend(["-L", prop_dict["mount_point"]])
    546     if "extfs_inode_count" in prop_dict:
    547       build_command.extend(["-i", prop_dict["extfs_inode_count"]])
    548     if "extfs_rsv_pct" in prop_dict:
    549       build_command.extend(["-M", prop_dict["extfs_rsv_pct"]])
    550     if "flash_erase_block_size" in prop_dict:
    551       build_command.extend(["-e", prop_dict["flash_erase_block_size"]])
    552     if "flash_logical_block_size" in prop_dict:
    553       build_command.extend(["-o", prop_dict["flash_logical_block_size"]])
    554     # Specify UUID and hash_seed if using mke2fs.
    555     if prop_dict["ext_mkuserimg"] == "mkuserimg_mke2fs.sh":
    556       if "uuid" in prop_dict:
    557         build_command.extend(["-U", prop_dict["uuid"]])
    558       if "hash_seed" in prop_dict:
    559         build_command.extend(["-S", prop_dict["hash_seed"]])
    560     if "ext4_share_dup_blocks" in prop_dict:
    561       build_command.append("-c")
    562     if "selinux_fc" in prop_dict:
    563       build_command.append(prop_dict["selinux_fc"])
    564   elif fs_type.startswith("squash"):
    565     build_command = ["mksquashfsimage.sh"]
    566     build_command.extend([in_dir, out_file])
    567     if "squashfs_sparse_flag" in prop_dict:
    568       build_command.extend([prop_dict["squashfs_sparse_flag"]])
    569     build_command.extend(["-m", prop_dict["mount_point"]])
    570     if target_out:
    571       build_command.extend(["-d", target_out])
    572     if fs_config:
    573       build_command.extend(["-C", fs_config])
    574     if "selinux_fc" in prop_dict:
    575       build_command.extend(["-c", prop_dict["selinux_fc"]])
    576     if "block_list" in prop_dict:
    577       build_command.extend(["-B", prop_dict["block_list"]])
    578     if "squashfs_block_size" in prop_dict:
    579       build_command.extend(["-b", prop_dict["squashfs_block_size"]])
    580     if "squashfs_compressor" in prop_dict:
    581       build_command.extend(["-z", prop_dict["squashfs_compressor"]])
    582     if "squashfs_compressor_opt" in prop_dict:
    583       build_command.extend(["-zo", prop_dict["squashfs_compressor_opt"]])
    584     if prop_dict.get("squashfs_disable_4k_align") == "true":
    585       build_command.extend(["-a"])
    586   elif fs_type.startswith("f2fs"):
    587     build_command = ["mkf2fsuserimg.sh"]
    588     build_command.extend([out_file, prop_dict["partition_size"]])
    589     if fs_config:
    590       build_command.extend(["-C", fs_config])
    591     build_command.extend(["-f", in_dir])
    592     if target_out:
    593       build_command.extend(["-D", target_out])
    594     if "selinux_fc" in prop_dict:
    595       build_command.extend(["-s", prop_dict["selinux_fc"]])
    596     build_command.extend(["-t", prop_dict["mount_point"]])
    597     if "timestamp" in prop_dict:
    598       build_command.extend(["-T", str(prop_dict["timestamp"])])
    599     build_command.extend(["-L", prop_dict["mount_point"]])
    600   else:
    601     print("Error: unknown filesystem type '%s'" % (fs_type))
    602     return False
    603 
    604   if in_dir != origin_in:
    605     # Construct a staging directory of the root file system.
    606     ramdisk_dir = prop_dict.get("ramdisk_dir")
    607     if ramdisk_dir:
    608       shutil.rmtree(in_dir)
    609       shutil.copytree(ramdisk_dir, in_dir, symlinks=True)
    610     staging_system = os.path.join(in_dir, "system")
    611     shutil.rmtree(staging_system, ignore_errors=True)
    612     shutil.copytree(origin_in, staging_system, symlinks=True)
    613 
    614   (mkfs_output, exit_code) = RunCommand(build_command)
    615   if exit_code != 0:
    616     print("Error: '%s' failed with exit code %d:\n%s" % (
    617         build_command, exit_code, mkfs_output))
    618     return False
    619 
    620   # Check if there's enough headroom space available for ext4 image.
    621   if "partition_headroom" in prop_dict and fs_type.startswith("ext4"):
    622     if not CheckHeadroom(mkfs_output, prop_dict):
    623       return False
    624 
    625   if not fs_spans_partition:
    626     mount_point = prop_dict.get("mount_point")
    627     partition_size = int(prop_dict.get("partition_size"))
    628     image_size = GetSimgSize(out_file)
    629     if image_size > partition_size:
    630       print("Error: %s image size of %d is larger than partition size of "
    631             "%d" % (mount_point, image_size, partition_size))
    632       return False
    633     if verity_supported and is_verity_partition:
    634       ZeroPadSimg(out_file, partition_size - image_size)
    635 
    636   # Create the verified image if this is to be verified.
    637   if verity_supported and is_verity_partition:
    638     if not MakeVerityEnabledImage(out_file, verity_fec_supported, prop_dict):
    639       return False
    640 
    641   # Add AVB HASH or HASHTREE footer (metadata).
    642   if avb_footer_type:
    643     avbtool = prop_dict["avb_avbtool"]
    644     original_partition_size = prop_dict["original_partition_size"]
    645     partition_name = prop_dict["partition_name"]
    646     # key_path and algorithm are only available when chain partition is used.
    647     key_path = prop_dict.get("avb_key_path")
    648     algorithm = prop_dict.get("avb_algorithm")
    649     salt = prop_dict.get("avb_salt")
    650     # avb_add_hash_footer_args or avb_add_hashtree_footer_args
    651     additional_args = prop_dict["avb_add_" + avb_footer_type + "_footer_args"]
    652     if not AVBAddFooter(out_file, avbtool, avb_footer_type,
    653                         original_partition_size, partition_name, key_path,
    654                         algorithm, salt, additional_args):
    655       return False
    656 
    657   if run_e2fsck and prop_dict.get("skip_fsck") != "true":
    658     success, unsparse_image = UnsparseImage(out_file, replace=False)
    659     if not success:
    660       return False
    661 
    662     # Run e2fsck on the inflated image file
    663     e2fsck_command = ["e2fsck", "-f", "-n", unsparse_image]
    664     (e2fsck_output, exit_code) = RunCommand(e2fsck_command)
    665 
    666     os.remove(unsparse_image)
    667 
    668     if exit_code != 0:
    669       print("Error: '%s' failed with exit code %d:\n%s" % (
    670           e2fsck_command, exit_code, e2fsck_output))
    671       return False
    672 
    673   return True
    674 
    675 
    676 def ImagePropFromGlobalDict(glob_dict, mount_point):
    677   """Build an image property dictionary from the global dictionary.
    678 
    679   Args:
    680     glob_dict: the global dictionary from the build system.
    681     mount_point: such as "system", "data" etc.
    682   """
    683   d = {}
    684 
    685   if "build.prop" in glob_dict:
    686     bp = glob_dict["build.prop"]
    687     if "ro.build.date.utc" in bp:
    688       d["timestamp"] = bp["ro.build.date.utc"]
    689 
    690   def copy_prop(src_p, dest_p):
    691     """Copy a property from the global dictionary.
    692 
    693     Args:
    694       src_p: The source property in the global dictionary.
    695       dest_p: The destination property.
    696     Returns:
    697       True if property was found and copied, False otherwise.
    698     """
    699     if src_p in glob_dict:
    700       d[dest_p] = str(glob_dict[src_p])
    701       return True
    702     return False
    703 
    704   common_props = (
    705       "extfs_sparse_flag",
    706       "squashfs_sparse_flag",
    707       "selinux_fc",
    708       "skip_fsck",
    709       "ext_mkuserimg",
    710       "verity",
    711       "verity_key",
    712       "verity_signer_cmd",
    713       "verity_fec",
    714       "verity_disable",
    715       "avb_enable",
    716       "avb_avbtool",
    717       "avb_salt",
    718   )
    719   for p in common_props:
    720     copy_prop(p, p)
    721 
    722   d["mount_point"] = mount_point
    723   if mount_point == "system":
    724     copy_prop("avb_system_hashtree_enable", "avb_hashtree_enable")
    725     copy_prop("avb_system_add_hashtree_footer_args",
    726               "avb_add_hashtree_footer_args")
    727     copy_prop("avb_system_key_path", "avb_key_path")
    728     copy_prop("avb_system_algorithm", "avb_algorithm")
    729     copy_prop("fs_type", "fs_type")
    730     # Copy the generic system fs type first, override with specific one if
    731     # available.
    732     copy_prop("system_fs_type", "fs_type")
    733     copy_prop("system_headroom", "partition_headroom")
    734     copy_prop("system_size", "partition_size")
    735     if not copy_prop("system_journal_size", "journal_size"):
    736       d["journal_size"] = "0"
    737     copy_prop("system_verity_block_device", "verity_block_device")
    738     copy_prop("system_root_image", "system_root_image")
    739     copy_prop("ramdisk_dir", "ramdisk_dir")
    740     copy_prop("ramdisk_fs_config", "ramdisk_fs_config")
    741     copy_prop("ext4_share_dup_blocks", "ext4_share_dup_blocks")
    742     copy_prop("system_squashfs_compressor", "squashfs_compressor")
    743     copy_prop("system_squashfs_compressor_opt", "squashfs_compressor_opt")
    744     copy_prop("system_squashfs_block_size", "squashfs_block_size")
    745     copy_prop("system_squashfs_disable_4k_align", "squashfs_disable_4k_align")
    746     copy_prop("system_base_fs_file", "base_fs_file")
    747     copy_prop("system_extfs_inode_count", "extfs_inode_count")
    748     if not copy_prop("system_extfs_rsv_pct", "extfs_rsv_pct"):
    749       d["extfs_rsv_pct"] = "0"
    750   elif mount_point == "system_other":
    751     # We inherit the selinux policies of /system since we contain some of its
    752     # files.
    753     d["mount_point"] = "system"
    754     copy_prop("avb_system_hashtree_enable", "avb_hashtree_enable")
    755     copy_prop("avb_system_add_hashtree_footer_args",
    756               "avb_add_hashtree_footer_args")
    757     copy_prop("avb_system_key_path", "avb_key_path")
    758     copy_prop("avb_system_algorithm", "avb_algorithm")
    759     copy_prop("fs_type", "fs_type")
    760     copy_prop("system_fs_type", "fs_type")
    761     copy_prop("system_size", "partition_size")
    762     if not copy_prop("system_journal_size", "journal_size"):
    763       d["journal_size"] = "0"
    764     copy_prop("system_verity_block_device", "verity_block_device")
    765     copy_prop("system_squashfs_compressor", "squashfs_compressor")
    766     copy_prop("system_squashfs_compressor_opt", "squashfs_compressor_opt")
    767     copy_prop("system_squashfs_block_size", "squashfs_block_size")
    768     copy_prop("system_base_fs_file", "base_fs_file")
    769     copy_prop("system_extfs_inode_count", "extfs_inode_count")
    770     if not copy_prop("system_extfs_rsv_pct", "extfs_rsv_pct"):
    771       d["extfs_rsv_pct"] = "0"
    772   elif mount_point == "data":
    773     # Copy the generic fs type first, override with specific one if available.
    774     copy_prop("fs_type", "fs_type")
    775     copy_prop("userdata_fs_type", "fs_type")
    776     copy_prop("userdata_size", "partition_size")
    777     copy_prop("flash_logical_block_size", "flash_logical_block_size")
    778     copy_prop("flash_erase_block_size", "flash_erase_block_size")
    779   elif mount_point == "cache":
    780     copy_prop("cache_fs_type", "fs_type")
    781     copy_prop("cache_size", "partition_size")
    782   elif mount_point == "vendor":
    783     copy_prop("avb_vendor_hashtree_enable", "avb_hashtree_enable")
    784     copy_prop("avb_vendor_add_hashtree_footer_args",
    785               "avb_add_hashtree_footer_args")
    786     copy_prop("avb_vendor_key_path", "avb_key_path")
    787     copy_prop("avb_vendor_algorithm", "avb_algorithm")
    788     copy_prop("vendor_fs_type", "fs_type")
    789     copy_prop("vendor_size", "partition_size")
    790     if not copy_prop("vendor_journal_size", "journal_size"):
    791       d["journal_size"] = "0"
    792     copy_prop("vendor_verity_block_device", "verity_block_device")
    793     copy_prop("ext4_share_dup_blocks", "ext4_share_dup_blocks")
    794     copy_prop("vendor_squashfs_compressor", "squashfs_compressor")
    795     copy_prop("vendor_squashfs_compressor_opt", "squashfs_compressor_opt")
    796     copy_prop("vendor_squashfs_block_size", "squashfs_block_size")
    797     copy_prop("vendor_squashfs_disable_4k_align", "squashfs_disable_4k_align")
    798     copy_prop("vendor_base_fs_file", "base_fs_file")
    799     copy_prop("vendor_extfs_inode_count", "extfs_inode_count")
    800     if not copy_prop("vendor_extfs_rsv_pct", "extfs_rsv_pct"):
    801       d["extfs_rsv_pct"] = "0"
    802   elif mount_point == "product":
    803     copy_prop("avb_product_hashtree_enable", "avb_hashtree_enable")
    804     copy_prop("avb_product_add_hashtree_footer_args",
    805               "avb_add_hashtree_footer_args")
    806     copy_prop("avb_product_key_path", "avb_key_path")
    807     copy_prop("avb_product_algorithm", "avb_algorithm")
    808     copy_prop("product_fs_type", "fs_type")
    809     copy_prop("product_size", "partition_size")
    810     if not copy_prop("product_journal_size", "journal_size"):
    811       d["journal_size"] = "0"
    812     copy_prop("product_verity_block_device", "verity_block_device")
    813     copy_prop("product_squashfs_compressor", "squashfs_compressor")
    814     copy_prop("product_squashfs_compressor_opt", "squashfs_compressor_opt")
    815     copy_prop("product_squashfs_block_size", "squashfs_block_size")
    816     copy_prop("product_squashfs_disable_4k_align", "squashfs_disable_4k_align")
    817     copy_prop("product_base_fs_file", "base_fs_file")
    818     copy_prop("product_extfs_inode_count", "extfs_inode_count")
    819     if not copy_prop("product_extfs_rsv_pct", "extfs_rsv_pct"):
    820       d["extfs_rsv_pct"] = "0"
    821   elif mount_point == "oem":
    822     copy_prop("fs_type", "fs_type")
    823     copy_prop("oem_size", "partition_size")
    824     if not copy_prop("oem_journal_size", "journal_size"):
    825       d["journal_size"] = "0"
    826     copy_prop("oem_extfs_inode_count", "extfs_inode_count")
    827     if not copy_prop("oem_extfs_rsv_pct", "extfs_rsv_pct"):
    828       d["extfs_rsv_pct"] = "0"
    829   d["partition_name"] = mount_point
    830   return d
    831 
    832 
    833 def LoadGlobalDict(filename):
    834   """Load "name=value" pairs from filename"""
    835   d = {}
    836   f = open(filename)
    837   for line in f:
    838     line = line.strip()
    839     if not line or line.startswith("#"):
    840       continue
    841     k, v = line.split("=", 1)
    842     d[k] = v
    843   f.close()
    844   return d
    845 
    846 
    847 def main(argv):
    848   if len(argv) != 4:
    849     print(__doc__)
    850     sys.exit(1)
    851 
    852   in_dir = argv[0]
    853   glob_dict_file = argv[1]
    854   out_file = argv[2]
    855   target_out = argv[3]
    856 
    857   glob_dict = LoadGlobalDict(glob_dict_file)
    858   if "mount_point" in glob_dict:
    859     # The caller knows the mount point and provides a dictionay needed by
    860     # BuildImage().
    861     image_properties = glob_dict
    862   else:
    863     image_filename = os.path.basename(out_file)
    864     mount_point = ""
    865     if image_filename == "system.img":
    866       mount_point = "system"
    867     elif image_filename == "system_other.img":
    868       mount_point = "system_other"
    869     elif image_filename == "userdata.img":
    870       mount_point = "data"
    871     elif image_filename == "cache.img":
    872       mount_point = "cache"
    873     elif image_filename == "vendor.img":
    874       mount_point = "vendor"
    875     elif image_filename == "oem.img":
    876       mount_point = "oem"
    877     elif image_filename == "product.img":
    878       mount_point = "product"
    879     else:
    880       print("error: unknown image file name ", image_filename, file=sys.stderr)
    881       sys.exit(1)
    882 
    883     image_properties = ImagePropFromGlobalDict(glob_dict, mount_point)
    884 
    885   if not BuildImage(in_dir, image_properties, out_file, target_out):
    886     print("error: failed to build %s from %s" % (out_file, in_dir),
    887           file=sys.stderr)
    888     sys.exit(1)
    889 
    890 
    891 if __name__ == '__main__':
    892   try:
    893     main(sys.argv[1:])
    894   finally:
    895     common.Cleanup()
    896