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