build_image.py revision 1ad7adeaa760577d505022820f8240952f7fb5fc
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 build_command.append(prop_dict["partition_size"]) 239 if "timestamp" in prop_dict: 240 build_command.extend(["-T", str(prop_dict["timestamp"])]) 241 if "selinux_fc" in prop_dict: 242 build_command.append(prop_dict["selinux_fc"]) 243 else: 244 build_command = ["mkyaffs2image", "-f"] 245 if prop_dict.get("mkyaffs2_extra_flags", None): 246 build_command.extend(prop_dict["mkyaffs2_extra_flags"].split()) 247 build_command.append(in_dir) 248 build_command.append(out_file) 249 if "selinux_fc" in prop_dict: 250 build_command.append(prop_dict["selinux_fc"]) 251 build_command.append(prop_dict["mount_point"]) 252 253 exit_code = RunCommand(build_command) 254 if exit_code != 0: 255 return False 256 257 # create the verified image if this is to be verified 258 if prop_dict.get("verity") == "true": 259 if not MakeVerityEnabledImage(out_file, prop_dict): 260 return False 261 262 if run_fsck and prop_dict.get("skip_fsck") != "true": 263 success, unsparse_image = UnsparseImage(out_file, replace=False) 264 if not success: 265 return False 266 267 # Run e2fsck on the inflated image file 268 e2fsck_command = ["e2fsck", "-f", "-n", unsparse_image] 269 exit_code = RunCommand(e2fsck_command) 270 271 os.remove(unsparse_image) 272 273 return exit_code == 0 274 275 276def ImagePropFromGlobalDict(glob_dict, mount_point): 277 """Build an image property dictionary from the global dictionary. 278 279 Args: 280 glob_dict: the global dictionary from the build system. 281 mount_point: such as "system", "data" etc. 282 """ 283 d = {} 284 if "build.prop" in glob_dict: 285 bp = glob_dict["build.prop"] 286 if "ro.build.date.utc" in bp: 287 d["timestamp"] = bp["ro.build.date.utc"] 288 289 def copy_prop(src_p, dest_p): 290 if src_p in glob_dict: 291 d[dest_p] = str(glob_dict[src_p]) 292 293 common_props = ( 294 "extfs_sparse_flag", 295 "mkyaffs2_extra_flags", 296 "selinux_fc", 297 "skip_fsck", 298 "verity", 299 "verity_block_device", 300 "verity_key", 301 "verity_signer_cmd" 302 ) 303 for p in common_props: 304 copy_prop(p, p) 305 306 d["mount_point"] = mount_point 307 if mount_point == "system": 308 copy_prop("fs_type", "fs_type") 309 copy_prop("system_size", "partition_size") 310 elif mount_point == "data": 311 copy_prop("fs_type", "fs_type") 312 copy_prop("userdata_size", "partition_size") 313 elif mount_point == "cache": 314 copy_prop("cache_fs_type", "fs_type") 315 copy_prop("cache_size", "partition_size") 316 elif mount_point == "vendor": 317 copy_prop("vendor_fs_type", "fs_type") 318 copy_prop("vendor_size", "partition_size") 319 320 return d 321 322 323def LoadGlobalDict(filename): 324 """Load "name=value" pairs from filename""" 325 d = {} 326 f = open(filename) 327 for line in f: 328 line = line.strip() 329 if not line or line.startswith("#"): 330 continue 331 k, v = line.split("=", 1) 332 d[k] = v 333 f.close() 334 return d 335 336 337def main(argv): 338 if len(argv) != 3: 339 print __doc__ 340 sys.exit(1) 341 342 in_dir = argv[0] 343 glob_dict_file = argv[1] 344 out_file = argv[2] 345 346 glob_dict = LoadGlobalDict(glob_dict_file) 347 image_filename = os.path.basename(out_file) 348 mount_point = "" 349 if image_filename == "system.img": 350 mount_point = "system" 351 elif image_filename == "userdata.img": 352 mount_point = "data" 353 elif image_filename == "cache.img": 354 mount_point = "cache" 355 elif image_filename == "vendor.img": 356 mount_point = "vendor" 357 else: 358 print >> sys.stderr, "error: unknown image file name ", image_filename 359 exit(1) 360 361 image_properties = ImagePropFromGlobalDict(glob_dict, mount_point) 362 if not BuildImage(in_dir, image_properties, out_file): 363 print >> sys.stderr, "error: failed to build %s from %s" % (out_file, in_dir) 364 exit(1) 365 366 367if __name__ == '__main__': 368 main(sys.argv[1:]) 369