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