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