build_image.py revision fd6f7513f82a4b2ec9810d6cb6c33a94060d14e9
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): 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 return True, unsparse_image_path 139 inflate_command = ["simg2img", sparse_image_path, unsparse_image_path] 140 exit_code = RunCommand(inflate_command) 141 if exit_code != 0: 142 os.remove(unsparse_image_path) 143 return False, None 144 return True, unsparse_image_path 145 146def MakeVerityEnabledImage(out_file, prop_dict): 147 """Creates an image that is verifiable using dm-verity. 148 149 Args: 150 out_file: the location to write the verifiable image at 151 prop_dict: a dictionary of properties required for image creation and verification 152 Returns: 153 True on success, False otherwise. 154 """ 155 # get properties 156 image_size = prop_dict["partition_size"] 157 part_size = int(prop_dict["original_partition_size"]) 158 block_dev = prop_dict["verity_block_device"] 159 signer_key = prop_dict["verity_key"] 160 signer_path = prop_dict["verity_signer_cmd"] 161 162 # make a tempdir 163 tempdir_name = os.path.join(os.path.dirname(out_file), "verity_images") 164 if os.path.exists(tempdir_name): 165 shutil.rmtree(tempdir_name) 166 os.mkdir(tempdir_name) 167 168 # get partial image paths 169 verity_image_path = os.path.join(tempdir_name, "verity.img") 170 verity_metadata_path = os.path.join(tempdir_name, "verity_metadata.img") 171 success, unsparse_image_path = UnsparseImage(out_file) 172 if not success: 173 shutil.rmtree(tempdir_name) 174 return False 175 176 # build the verity tree and get the root hash and salt 177 if not BuildVerityTree(unsparse_image_path, verity_image_path, part_size, prop_dict): 178 shutil.rmtree(tempdir_name) 179 return False 180 181 # build the metadata blocks 182 root_hash = prop_dict["verity_root_hash"] 183 salt = prop_dict["verity_salt"] 184 if not BuildVerityMetadata(image_size, 185 verity_metadata_path, 186 root_hash, 187 salt, 188 block_dev, 189 signer_path, 190 signer_key): 191 shutil.rmtree(tempdir_name) 192 return False 193 194 # build the full verified image 195 if not BuildVerifiedImage(out_file, 196 verity_image_path, 197 verity_metadata_path): 198 shutil.rmtree(tempdir_name) 199 return False 200 201 shutil.rmtree(tempdir_name) 202 return True 203 204def BuildImage(in_dir, prop_dict, out_file): 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 212 Returns: 213 True iff the image is built successfully. 214 """ 215 build_command = [] 216 fs_type = prop_dict.get("fs_type", "") 217 run_fsck = False 218 219 # adjust the partition size to make room for the hashes if this is to be verified 220 if prop_dict.get("verity") == "true": 221 partition_size = int(prop_dict.get("partition_size")) 222 adjusted_size = AdjustPartitionSizeForVerity(partition_size) 223 if not adjusted_size: 224 return False 225 prop_dict["partition_size"] = str(adjusted_size) 226 prop_dict["original_partition_size"] = str(partition_size) 227 228 if fs_type.startswith("ext"): 229 build_command = ["mkuserimg.sh"] 230 if "extfs_sparse_flag" in prop_dict: 231 build_command.append(prop_dict["extfs_sparse_flag"]) 232 run_fsck = True 233 build_command.extend([in_dir, out_file, fs_type, 234 prop_dict["mount_point"]]) 235 if "partition_size" in prop_dict: 236 build_command.append(prop_dict["partition_size"]) 237 if "selinux_fc" in prop_dict: 238 build_command.append(prop_dict["selinux_fc"]) 239 else: 240 build_command = ["mkyaffs2image", "-f"] 241 if prop_dict.get("mkyaffs2_extra_flags", None): 242 build_command.extend(prop_dict["mkyaffs2_extra_flags"].split()) 243 build_command.append(in_dir) 244 build_command.append(out_file) 245 if "selinux_fc" in prop_dict: 246 build_command.append(prop_dict["selinux_fc"]) 247 build_command.append(prop_dict["mount_point"]) 248 249 exit_code = RunCommand(build_command) 250 if exit_code != 0: 251 return False 252 253 # create the verified image if this is to be verified 254 if prop_dict.get("verity") == "true": 255 if not MakeVerityEnabledImage(out_file, prop_dict): 256 return False 257 258 if run_fsck and prop_dict.get("skip_fsck") != "true": 259 success, unsparse_image_path = UnsparseImage(out_file) 260 if not success: 261 return False 262 263 # Run e2fsck on the inflated image file 264 e2fsck_command = ["e2fsck", "-f", "-n", unsparse_image] 265 exit_code = RunCommand(e2fsck_command) 266 267 os.remove(unsparse_image) 268 269 return exit_code == 0 270 271 272def ImagePropFromGlobalDict(glob_dict, mount_point): 273 """Build an image property dictionary from the global dictionary. 274 275 Args: 276 glob_dict: the global dictionary from the build system. 277 mount_point: such as "system", "data" etc. 278 """ 279 d = {} 280 281 def copy_prop(src_p, dest_p): 282 if src_p in glob_dict: 283 d[dest_p] = str(glob_dict[src_p]) 284 285 common_props = ( 286 "extfs_sparse_flag", 287 "mkyaffs2_extra_flags", 288 "selinux_fc", 289 "skip_fsck", 290 "verity", 291 "verity_block_device", 292 "verity_key", 293 "verity_signer_cmd" 294 ) 295 for p in common_props: 296 copy_prop(p, p) 297 298 d["mount_point"] = mount_point 299 if mount_point == "system": 300 copy_prop("fs_type", "fs_type") 301 copy_prop("system_size", "partition_size") 302 elif mount_point == "data": 303 copy_prop("fs_type", "fs_type") 304 copy_prop("userdata_size", "partition_size") 305 elif mount_point == "cache": 306 copy_prop("cache_fs_type", "fs_type") 307 copy_prop("cache_size", "partition_size") 308 elif mount_point == "vendor": 309 copy_prop("vendor_fs_type", "fs_type") 310 copy_prop("vendor_size", "partition_size") 311 312 return d 313 314 315def LoadGlobalDict(filename): 316 """Load "name=value" pairs from filename""" 317 d = {} 318 f = open(filename) 319 for line in f: 320 line = line.strip() 321 if not line or line.startswith("#"): 322 continue 323 k, v = line.split("=", 1) 324 d[k] = v 325 f.close() 326 return d 327 328 329def main(argv): 330 if len(argv) != 3: 331 print __doc__ 332 sys.exit(1) 333 334 in_dir = argv[0] 335 glob_dict_file = argv[1] 336 out_file = argv[2] 337 338 glob_dict = LoadGlobalDict(glob_dict_file) 339 image_filename = os.path.basename(out_file) 340 mount_point = "" 341 if image_filename == "system.img": 342 mount_point = "system" 343 elif image_filename == "userdata.img": 344 mount_point = "data" 345 elif image_filename == "cache.img": 346 mount_point = "cache" 347 elif image_filename == "vendor.img": 348 mount_point = "vendor" 349 else: 350 print >> sys.stderr, "error: unknown image file name ", image_filename 351 exit(1) 352 353 image_properties = ImagePropFromGlobalDict(glob_dict, mount_point) 354 if not BuildImage(in_dir, image_properties, out_file): 355 print >> sys.stderr, "error: failed to build %s from %s" % (out_file, in_dir) 356 exit(1) 357 358 359if __name__ == '__main__': 360 main(sys.argv[1:]) 361