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""" 18Builds output_image from the given input_directory, properties_file, 19and writes the image to target_output_directory. 20 21Usage: build_image.py input_directory properties_file output_image \\ 22 target_output_directory 23""" 24 25from __future__ import print_function 26 27import os 28import os.path 29import re 30import shlex 31import shutil 32import subprocess 33import sys 34 35import common 36import sparse_img 37 38 39OPTIONS = common.OPTIONS 40 41FIXED_SALT = "aee087a5be3b982978c923f566a94613496b417f2af592639bc80d141e34dfe7" 42BLOCK_SIZE = 4096 43 44 45def 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 66def 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 74def 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 82def 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 91def 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 107def GetSimgSize(image_file): 108 simg = sparse_img.SparseImage(image_file, build_map=False) 109 return simg.blocksize * simg.total_blocks 110 111 112def 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 119def 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 142def 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 178def 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 223AdjustPartitionSizeForVerity.results = {} 224 225 226def 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 237def 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 250def 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 267def 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 284def 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 296def 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 318def 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 337def 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 396def 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 403def 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 445def 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 676def 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 833def 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 847def 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 891if __name__ == '__main__': 892 try: 893 main(sys.argv[1:]) 894 finally: 895 common.Cleanup() 896