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 and properties_file.
     19 
     20 Usage:  build_image input_directory properties_file output_image_file
     21 
     22 """
     23 import os
     24 import os.path
     25 import subprocess
     26 import sys
     27 import commands
     28 import shutil
     29 import tempfile
     30 
     31 FIXED_SALT = "aee087a5be3b982978c923f566a94613496b417f2af592639bc80d141e34dfe7"
     32 
     33 def RunCommand(cmd):
     34   """ Echo and run the given command
     35 
     36   Args:
     37     cmd: the command represented as a list of strings.
     38   Returns:
     39     The exit code.
     40   """
     41   print "Running: ", " ".join(cmd)
     42   p = subprocess.Popen(cmd)
     43   p.communicate()
     44   return p.returncode
     45 
     46 def GetVerityTreeSize(partition_size):
     47   cmd = "build_verity_tree -s %d"
     48   cmd %= partition_size
     49   status, output = commands.getstatusoutput(cmd)
     50   if status:
     51     print output
     52     return False, 0
     53   return True, int(output)
     54 
     55 def GetVerityMetadataSize(partition_size):
     56   cmd = "system/extras/verity/build_verity_metadata.py -s %d"
     57   cmd %= partition_size
     58   status, output = commands.getstatusoutput(cmd)
     59   if status:
     60     print output
     61     return False, 0
     62   return True, int(output)
     63 
     64 def AdjustPartitionSizeForVerity(partition_size):
     65   """Modifies the provided partition size to account for the verity metadata.
     66 
     67   This information is used to size the created image appropriately.
     68   Args:
     69     partition_size: the size of the partition to be verified.
     70   Returns:
     71     The size of the partition adjusted for verity metadata.
     72   """
     73   success, verity_tree_size = GetVerityTreeSize(partition_size)
     74   if not success:
     75     return 0;
     76   success, verity_metadata_size = GetVerityMetadataSize(partition_size)
     77   if not success:
     78     return 0
     79   return partition_size - verity_tree_size - verity_metadata_size
     80 
     81 def BuildVerityTree(sparse_image_path, verity_image_path, prop_dict):
     82   cmd = ("build_verity_tree -A %s %s %s" % (FIXED_SALT, sparse_image_path, verity_image_path))
     83   print cmd
     84   status, output = commands.getstatusoutput(cmd)
     85   if status:
     86     print "Could not build verity tree! Error: %s" % output
     87     return False
     88   root, salt = output.split()
     89   prop_dict["verity_root_hash"] = root
     90   prop_dict["verity_salt"] = salt
     91   return True
     92 
     93 def BuildVerityMetadata(image_size, verity_metadata_path, root_hash, salt,
     94                         block_device, signer_path, key):
     95   cmd = ("system/extras/verity/build_verity_metadata.py %s %s %s %s %s %s %s" %
     96               (image_size,
     97               verity_metadata_path,
     98               root_hash,
     99               salt,
    100               block_device,
    101               signer_path,
    102               key))
    103   print cmd
    104   status, output = commands.getstatusoutput(cmd)
    105   if status:
    106     print "Could not build verity metadata! Error: %s" % output
    107     return False
    108   return True
    109 
    110 def Append2Simg(sparse_image_path, unsparse_image_path, error_message):
    111   """Appends the unsparse image to the given sparse image.
    112 
    113   Args:
    114     sparse_image_path: the path to the (sparse) image
    115     unsparse_image_path: the path to the (unsparse) image
    116   Returns:
    117     True on success, False on failure.
    118   """
    119   cmd = "append2simg %s %s"
    120   cmd %= (sparse_image_path, unsparse_image_path)
    121   print cmd
    122   status, output = commands.getstatusoutput(cmd)
    123   if status:
    124     print "%s: %s" % (error_message, output)
    125     return False
    126   return True
    127 
    128 def BuildVerifiedImage(data_image_path, verity_image_path, verity_metadata_path):
    129   if not Append2Simg(data_image_path, verity_metadata_path, "Could not append verity metadata!"):
    130     return False
    131   if not Append2Simg(data_image_path, verity_image_path, "Could not append verity tree!"):
    132     return False
    133   return True
    134 
    135 def UnsparseImage(sparse_image_path, replace=True):
    136   img_dir = os.path.dirname(sparse_image_path)
    137   unsparse_image_path = "unsparse_" + os.path.basename(sparse_image_path)
    138   unsparse_image_path = os.path.join(img_dir, unsparse_image_path)
    139   if os.path.exists(unsparse_image_path):
    140     if replace:
    141       os.unlink(unsparse_image_path)
    142     else:
    143       return True, unsparse_image_path
    144   inflate_command = ["simg2img", sparse_image_path, unsparse_image_path]
    145   exit_code = RunCommand(inflate_command)
    146   if exit_code != 0:
    147     os.remove(unsparse_image_path)
    148     return False, None
    149   return True, unsparse_image_path
    150 
    151 def MakeVerityEnabledImage(out_file, prop_dict):
    152   """Creates an image that is verifiable using dm-verity.
    153 
    154   Args:
    155     out_file: the location to write the verifiable image at
    156     prop_dict: a dictionary of properties required for image creation and verification
    157   Returns:
    158     True on success, False otherwise.
    159   """
    160   # get properties
    161   image_size = prop_dict["partition_size"]
    162   block_dev = prop_dict["verity_block_device"]
    163   signer_key = prop_dict["verity_key"]
    164   signer_path = prop_dict["verity_signer_cmd"]
    165 
    166   # make a tempdir
    167   tempdir_name = tempfile.mkdtemp(suffix="_verity_images")
    168 
    169   # get partial image paths
    170   verity_image_path = os.path.join(tempdir_name, "verity.img")
    171   verity_metadata_path = os.path.join(tempdir_name, "verity_metadata.img")
    172 
    173   # build the verity tree and get the root hash and salt
    174   if not BuildVerityTree(out_file, verity_image_path, prop_dict):
    175     shutil.rmtree(tempdir_name, ignore_errors=True)
    176     return False
    177 
    178   # build the metadata blocks
    179   root_hash = prop_dict["verity_root_hash"]
    180   salt = prop_dict["verity_salt"]
    181   if not BuildVerityMetadata(image_size,
    182                               verity_metadata_path,
    183                               root_hash,
    184                               salt,
    185                               block_dev,
    186                               signer_path,
    187                               signer_key):
    188     shutil.rmtree(tempdir_name, ignore_errors=True)
    189     return False
    190 
    191   # build the full verified image
    192   if not BuildVerifiedImage(out_file,
    193                             verity_image_path,
    194                             verity_metadata_path):
    195     shutil.rmtree(tempdir_name, ignore_errors=True)
    196     return False
    197 
    198   shutil.rmtree(tempdir_name, ignore_errors=True)
    199   return True
    200 
    201 def BuildImage(in_dir, prop_dict, out_file,
    202                fs_config=None,
    203                fc_config=None,
    204                block_list=None):
    205   """Build an image to out_file from in_dir with property prop_dict.
    206 
    207   Args:
    208     in_dir: path of input directory.
    209     prop_dict: property dictionary.
    210     out_file: path of the output image file.
    211     fs_config: path to the fs_config file (typically
    212       META/filesystem_config.txt).  If None then the configuration in
    213       the local client will be used.
    214     fc_config: path to the SELinux file_contexts file.  If None then
    215       the value from prop_dict['selinux_fc'] will be used.
    216 
    217   Returns:
    218     True iff the image is built successfully.
    219   """
    220   build_command = []
    221   fs_type = prop_dict.get("fs_type", "")
    222   run_fsck = False
    223 
    224   is_verity_partition = "verity_block_device" in prop_dict
    225   verity_supported = prop_dict.get("verity") == "true"
    226   # adjust the partition size to make room for the hashes if this is to be verified
    227   if verity_supported and is_verity_partition:
    228     partition_size = int(prop_dict.get("partition_size"))
    229     adjusted_size = AdjustPartitionSizeForVerity(partition_size)
    230     if not adjusted_size:
    231       return False
    232     prop_dict["partition_size"] = str(adjusted_size)
    233     prop_dict["original_partition_size"] = str(partition_size)
    234 
    235   if fs_type.startswith("ext"):
    236     build_command = ["mkuserimg.sh"]
    237     if "extfs_sparse_flag" in prop_dict:
    238       build_command.append(prop_dict["extfs_sparse_flag"])
    239       run_fsck = True
    240     build_command.extend([in_dir, out_file, fs_type,
    241                           prop_dict["mount_point"]])
    242     build_command.append(prop_dict["partition_size"])
    243     if "timestamp" in prop_dict:
    244       build_command.extend(["-T", str(prop_dict["timestamp"])])
    245     if fs_config is not None:
    246       build_command.extend(["-C", fs_config])
    247     if block_list is not None:
    248       build_command.extend(["-B", block_list])
    249     if fc_config is not None:
    250       build_command.append(fc_config)
    251     elif "selinux_fc" in prop_dict:
    252       build_command.append(prop_dict["selinux_fc"])
    253   elif fs_type.startswith("f2fs"):
    254     build_command = ["mkf2fsuserimg.sh"]
    255     build_command.extend([out_file, prop_dict["partition_size"]])
    256   else:
    257     build_command = ["mkyaffs2image", "-f"]
    258     if prop_dict.get("mkyaffs2_extra_flags", None):
    259       build_command.extend(prop_dict["mkyaffs2_extra_flags"].split())
    260     build_command.append(in_dir)
    261     build_command.append(out_file)
    262     if "selinux_fc" in prop_dict:
    263       build_command.append(prop_dict["selinux_fc"])
    264       build_command.append(prop_dict["mount_point"])
    265 
    266   exit_code = RunCommand(build_command)
    267   if exit_code != 0:
    268     return False
    269 
    270   # create the verified image if this is to be verified
    271   if verity_supported and is_verity_partition:
    272     if not MakeVerityEnabledImage(out_file, prop_dict):
    273       return False
    274 
    275   if run_fsck and prop_dict.get("skip_fsck") != "true":
    276     success, unsparse_image = UnsparseImage(out_file, replace=False)
    277     if not success:
    278       return False
    279 
    280     # Run e2fsck on the inflated image file
    281     e2fsck_command = ["e2fsck", "-f", "-n", unsparse_image]
    282     exit_code = RunCommand(e2fsck_command)
    283 
    284     os.remove(unsparse_image)
    285 
    286   return exit_code == 0
    287 
    288 
    289 def ImagePropFromGlobalDict(glob_dict, mount_point):
    290   """Build an image property dictionary from the global dictionary.
    291 
    292   Args:
    293     glob_dict: the global dictionary from the build system.
    294     mount_point: such as "system", "data" etc.
    295   """
    296   d = {}
    297   if "build.prop" in glob_dict:
    298     bp = glob_dict["build.prop"]
    299     if "ro.build.date.utc" in bp:
    300       d["timestamp"] = bp["ro.build.date.utc"]
    301 
    302   def copy_prop(src_p, dest_p):
    303     if src_p in glob_dict:
    304       d[dest_p] = str(glob_dict[src_p])
    305 
    306   common_props = (
    307       "extfs_sparse_flag",
    308       "mkyaffs2_extra_flags",
    309       "selinux_fc",
    310       "skip_fsck",
    311       "verity",
    312       "verity_key",
    313       "verity_signer_cmd"
    314       )
    315   for p in common_props:
    316     copy_prop(p, p)
    317 
    318   d["mount_point"] = mount_point
    319   if mount_point == "system":
    320     copy_prop("fs_type", "fs_type")
    321     copy_prop("system_size", "partition_size")
    322     copy_prop("system_verity_block_device", "verity_block_device")
    323   elif mount_point == "data":
    324     # Copy the generic fs type first, override with specific one if available.
    325     copy_prop("fs_type", "fs_type")
    326     copy_prop("userdata_fs_type", "fs_type")
    327     copy_prop("userdata_size", "partition_size")
    328   elif mount_point == "cache":
    329     copy_prop("cache_fs_type", "fs_type")
    330     copy_prop("cache_size", "partition_size")
    331   elif mount_point == "vendor":
    332     copy_prop("vendor_fs_type", "fs_type")
    333     copy_prop("vendor_size", "partition_size")
    334     copy_prop("vendor_verity_block_device", "verity_block_device")
    335   elif mount_point == "oem":
    336     copy_prop("fs_type", "fs_type")
    337     copy_prop("oem_size", "partition_size")
    338 
    339   return d
    340 
    341 
    342 def LoadGlobalDict(filename):
    343   """Load "name=value" pairs from filename"""
    344   d = {}
    345   f = open(filename)
    346   for line in f:
    347     line = line.strip()
    348     if not line or line.startswith("#"):
    349       continue
    350     k, v = line.split("=", 1)
    351     d[k] = v
    352   f.close()
    353   return d
    354 
    355 
    356 def main(argv):
    357   if len(argv) != 3:
    358     print __doc__
    359     sys.exit(1)
    360 
    361   in_dir = argv[0]
    362   glob_dict_file = argv[1]
    363   out_file = argv[2]
    364 
    365   glob_dict = LoadGlobalDict(glob_dict_file)
    366   image_filename = os.path.basename(out_file)
    367   mount_point = ""
    368   if image_filename == "system.img":
    369     mount_point = "system"
    370   elif image_filename == "userdata.img":
    371     mount_point = "data"
    372   elif image_filename == "cache.img":
    373     mount_point = "cache"
    374   elif image_filename == "vendor.img":
    375     mount_point = "vendor"
    376   elif image_filename == "oem.img":
    377     mount_point = "oem"
    378   else:
    379     print >> sys.stderr, "error: unknown image file name ", image_filename
    380     exit(1)
    381 
    382   image_properties = ImagePropFromGlobalDict(glob_dict, mount_point)
    383   if not BuildImage(in_dir, image_properties, out_file):
    384     print >> sys.stderr, "error: failed to build %s from %s" % (out_file, in_dir)
    385     exit(1)
    386 
    387 
    388 if __name__ == '__main__':
    389   main(sys.argv[1:])
    390