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

     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()