1#!/usr/bin/env python 2# 3# Copyright (C) 2014 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""" 18Given a target-files zipfile that does not contain images (ie, does 19not have an IMAGES/ top-level subdirectory), produce the images and 20add them to the zipfile. 21 22Usage: add_img_to_target_files [flag] target_files 23 24 -a (--add_missing) 25 Build and add missing images to "IMAGES/". If this option is 26 not specified, this script will simply exit when "IMAGES/" 27 directory exists in the target file. 28 29 -r (--rebuild_recovery) 30 Rebuild the recovery patch and write it to the system image. Only 31 meaningful when system image needs to be rebuilt and there're separate 32 boot / recovery images. 33 34 --replace_verity_private_key 35 Replace the private key used for verity signing. (same as the option 36 in sign_target_files_apks) 37 38 --replace_verity_public_key 39 Replace the certificate (public key) used for verity verification. (same 40 as the option in sign_target_files_apks) 41 42 --is_signing 43 Skip building & adding the images for "userdata" and "cache" if we 44 are signing the target files. 45""" 46 47from __future__ import print_function 48 49import datetime 50import logging 51import os 52import shlex 53import shutil 54import sys 55import uuid 56import zipfile 57 58import build_image 59import build_super_image 60import common 61import rangelib 62import sparse_img 63import verity_utils 64 65if sys.hexversion < 0x02070000: 66 print("Python 2.7 or newer is required.", file=sys.stderr) 67 sys.exit(1) 68 69logger = logging.getLogger(__name__) 70 71OPTIONS = common.OPTIONS 72OPTIONS.add_missing = False 73OPTIONS.rebuild_recovery = False 74OPTIONS.replace_updated_files_list = [] 75OPTIONS.replace_verity_public_key = False 76OPTIONS.replace_verity_private_key = False 77OPTIONS.is_signing = False 78 79# Use a fixed timestamp (01/01/2009 00:00:00 UTC) for files when packaging 80# images. (b/24377993, b/80600931) 81FIXED_FILE_TIMESTAMP = int(( 82 datetime.datetime(2009, 1, 1, 0, 0, 0, 0, None) - 83 datetime.datetime.utcfromtimestamp(0)).total_seconds()) 84 85 86class OutputFile(object): 87 """A helper class to write a generated file to the given dir or zip. 88 89 When generating images, we want the outputs to go into the given zip file, or 90 the given dir. 91 92 Attributes: 93 name: The name of the output file, regardless of the final destination. 94 """ 95 96 def __init__(self, output_zip, input_dir, prefix, name): 97 # We write the intermediate output file under the given input_dir, even if 98 # the final destination is a zip archive. 99 self.name = os.path.join(input_dir, prefix, name) 100 self._output_zip = output_zip 101 if self._output_zip: 102 self._zip_name = os.path.join(prefix, name) 103 104 def Write(self): 105 if self._output_zip: 106 common.ZipWrite(self._output_zip, self.name, self._zip_name) 107 108 109def GetCareMap(which, imgname): 110 """Returns the care_map string for the given partition. 111 112 Args: 113 which: The partition name, must be listed in PARTITIONS_WITH_CARE_MAP. 114 imgname: The filename of the image. 115 116 Returns: 117 (which, care_map_ranges): care_map_ranges is the raw string of the care_map 118 RangeSet; or None. 119 """ 120 assert which in common.PARTITIONS_WITH_CARE_MAP 121 122 # which + "_image_size" contains the size that the actual filesystem image 123 # resides in, which is all that needs to be verified. The additional blocks in 124 # the image file contain verity metadata, by reading which would trigger 125 # invalid reads. 126 image_size = OPTIONS.info_dict.get(which + "_image_size") 127 if not image_size: 128 return None 129 130 image_blocks = int(image_size) // 4096 - 1 131 assert image_blocks > 0, "blocks for {} must be positive".format(which) 132 133 # For sparse images, we will only check the blocks that are listed in the care 134 # map, i.e. the ones with meaningful data. 135 if "extfs_sparse_flag" in OPTIONS.info_dict: 136 simg = sparse_img.SparseImage(imgname) 137 care_map_ranges = simg.care_map.intersect( 138 rangelib.RangeSet("0-{}".format(image_blocks))) 139 140 # Otherwise for non-sparse images, we read all the blocks in the filesystem 141 # image. 142 else: 143 care_map_ranges = rangelib.RangeSet("0-{}".format(image_blocks)) 144 145 return [which, care_map_ranges.to_string_raw()] 146 147 148def AddSystem(output_zip, recovery_img=None, boot_img=None): 149 """Turn the contents of SYSTEM into a system image and store it in 150 output_zip. Returns the name of the system image file.""" 151 152 img = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "system.img") 153 if os.path.exists(img.name): 154 logger.info("system.img already exists; no need to rebuild...") 155 return img.name 156 157 def output_sink(fn, data): 158 output_file = os.path.join(OPTIONS.input_tmp, "SYSTEM", fn) 159 with open(output_file, "wb") as ofile: 160 ofile.write(data) 161 162 if output_zip: 163 arc_name = "SYSTEM/" + fn 164 if arc_name in output_zip.namelist(): 165 OPTIONS.replace_updated_files_list.append(arc_name) 166 else: 167 common.ZipWrite(output_zip, output_file, arc_name) 168 169 board_uses_vendorimage = OPTIONS.info_dict.get( 170 "board_uses_vendorimage") == "true" 171 172 if (OPTIONS.rebuild_recovery and not board_uses_vendorimage and 173 recovery_img is not None and boot_img is not None): 174 logger.info("Building new recovery patch on system at system/vendor") 175 common.MakeRecoveryPatch(OPTIONS.input_tmp, output_sink, recovery_img, 176 boot_img, info_dict=OPTIONS.info_dict) 177 178 block_list = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "system.map") 179 CreateImage(OPTIONS.input_tmp, OPTIONS.info_dict, "system", img, 180 block_list=block_list) 181 182 return img.name 183 184 185def AddSystemOther(output_zip): 186 """Turn the contents of SYSTEM_OTHER into a system_other image 187 and store it in output_zip.""" 188 189 img = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "system_other.img") 190 if os.path.exists(img.name): 191 logger.info("system_other.img already exists; no need to rebuild...") 192 return 193 194 CreateImage(OPTIONS.input_tmp, OPTIONS.info_dict, "system_other", img) 195 196 197def AddVendor(output_zip, recovery_img=None, boot_img=None): 198 """Turn the contents of VENDOR into a vendor image and store in it 199 output_zip.""" 200 201 img = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "vendor.img") 202 if os.path.exists(img.name): 203 logger.info("vendor.img already exists; no need to rebuild...") 204 return img.name 205 206 def output_sink(fn, data): 207 ofile = open(os.path.join(OPTIONS.input_tmp, "VENDOR", fn), "w") 208 ofile.write(data) 209 ofile.close() 210 211 if output_zip: 212 arc_name = "VENDOR/" + fn 213 if arc_name in output_zip.namelist(): 214 OPTIONS.replace_updated_files_list.append(arc_name) 215 else: 216 common.ZipWrite(output_zip, ofile.name, arc_name) 217 218 board_uses_vendorimage = OPTIONS.info_dict.get( 219 "board_uses_vendorimage") == "true" 220 221 if (OPTIONS.rebuild_recovery and board_uses_vendorimage and 222 recovery_img is not None and boot_img is not None): 223 logger.info("Building new recovery patch on vendor") 224 common.MakeRecoveryPatch(OPTIONS.input_tmp, output_sink, recovery_img, 225 boot_img, info_dict=OPTIONS.info_dict) 226 227 block_list = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "vendor.map") 228 CreateImage(OPTIONS.input_tmp, OPTIONS.info_dict, "vendor", img, 229 block_list=block_list) 230 return img.name 231 232 233def AddProduct(output_zip): 234 """Turn the contents of PRODUCT into a product image and store it in 235 output_zip.""" 236 237 img = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "product.img") 238 if os.path.exists(img.name): 239 logger.info("product.img already exists; no need to rebuild...") 240 return img.name 241 242 block_list = OutputFile( 243 output_zip, OPTIONS.input_tmp, "IMAGES", "product.map") 244 CreateImage( 245 OPTIONS.input_tmp, OPTIONS.info_dict, "product", img, 246 block_list=block_list) 247 return img.name 248 249 250def AddSystemExt(output_zip): 251 """Turn the contents of SYSTEM_EXT into a system_ext image and store it in 252 output_zip.""" 253 254 img = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", 255 "system_ext.img") 256 if os.path.exists(img.name): 257 logger.info("system_ext.img already exists; no need to rebuild...") 258 return img.name 259 260 block_list = OutputFile( 261 output_zip, OPTIONS.input_tmp, "IMAGES", "system_ext.map") 262 CreateImage( 263 OPTIONS.input_tmp, OPTIONS.info_dict, "system_ext", img, 264 block_list=block_list) 265 return img.name 266 267 268def AddOdm(output_zip): 269 """Turn the contents of ODM into an odm image and store it in output_zip.""" 270 271 img = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "odm.img") 272 if os.path.exists(img.name): 273 logger.info("odm.img already exists; no need to rebuild...") 274 return img.name 275 276 block_list = OutputFile( 277 output_zip, OPTIONS.input_tmp, "IMAGES", "odm.map") 278 CreateImage( 279 OPTIONS.input_tmp, OPTIONS.info_dict, "odm", img, 280 block_list=block_list) 281 return img.name 282 283 284def AddDtbo(output_zip): 285 """Adds the DTBO image. 286 287 Uses the image under IMAGES/ if it already exists. Otherwise looks for the 288 image under PREBUILT_IMAGES/, signs it as needed, and returns the image name. 289 """ 290 img = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "dtbo.img") 291 if os.path.exists(img.name): 292 logger.info("dtbo.img already exists; no need to rebuild...") 293 return img.name 294 295 dtbo_prebuilt_path = os.path.join( 296 OPTIONS.input_tmp, "PREBUILT_IMAGES", "dtbo.img") 297 assert os.path.exists(dtbo_prebuilt_path) 298 shutil.copy(dtbo_prebuilt_path, img.name) 299 300 # AVB-sign the image as needed. 301 if OPTIONS.info_dict.get("avb_enable") == "true": 302 avbtool = OPTIONS.info_dict["avb_avbtool"] 303 part_size = OPTIONS.info_dict["dtbo_size"] 304 # The AVB hash footer will be replaced if already present. 305 cmd = [avbtool, "add_hash_footer", "--image", img.name, 306 "--partition_size", str(part_size), "--partition_name", "dtbo"] 307 common.AppendAVBSigningArgs(cmd, "dtbo") 308 args = OPTIONS.info_dict.get("avb_dtbo_add_hash_footer_args") 309 if args and args.strip(): 310 cmd.extend(shlex.split(args)) 311 common.RunAndCheckOutput(cmd) 312 313 img.Write() 314 return img.name 315 316def AddCustomImages(output_zip, partition_name): 317 """Adds and signs custom images in IMAGES/. 318 319 Args: 320 output_zip: The output zip file (needs to be already open), or None to 321 write images to OPTIONS.input_tmp/. 322 323 Uses the image under IMAGES/ if it already exists. Otherwise looks for the 324 image under PREBUILT_IMAGES/, signs it as needed, and returns the image name. 325 326 Raises: 327 AssertionError: If image can't be found. 328 """ 329 330 partition_size = OPTIONS.info_dict.get( 331 "avb_{}_partition_size".format(partition_name)) 332 key_path = OPTIONS.info_dict.get("avb_{}_key_path".format(partition_name)) 333 algorithm = OPTIONS.info_dict.get("avb_{}_algorithm".format(partition_name)) 334 extra_args = OPTIONS.info_dict.get( 335 "avb_{}_add_hashtree_footer_args".format(partition_name)) 336 partition_size = OPTIONS.info_dict.get( 337 "avb_{}_partition_size".format(partition_name)) 338 339 builder = verity_utils.CreateCustomImageBuilder( 340 OPTIONS.info_dict, partition_name, partition_size, 341 key_path, algorithm, extra_args) 342 343 for img_name in OPTIONS.info_dict.get( 344 "avb_{}_image_list".format(partition_name)).split(): 345 custom_image = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", img_name) 346 if os.path.exists(custom_image.name): 347 continue 348 349 custom_image_prebuilt_path = os.path.join( 350 OPTIONS.input_tmp, "PREBUILT_IMAGES", img_name) 351 assert os.path.exists(custom_image_prebuilt_path), \ 352 "Failed to find %s at %s" % (img_name, custom_image_prebuilt_path) 353 354 shutil.copy(custom_image_prebuilt_path, custom_image.name) 355 356 if builder is not None: 357 builder.Build(custom_image.name) 358 359 custom_image.Write() 360 361 default = os.path.join(OPTIONS.input_tmp, "IMAGES", partition_name + ".img") 362 assert os.path.exists(default), \ 363 "There should be one %s.img" % (partition_name) 364 return default 365 366 367def CreateImage(input_dir, info_dict, what, output_file, block_list=None): 368 logger.info("creating %s.img...", what) 369 370 image_props = build_image.ImagePropFromGlobalDict(info_dict, what) 371 image_props["timestamp"] = FIXED_FILE_TIMESTAMP 372 373 if what == "system": 374 fs_config_prefix = "" 375 else: 376 fs_config_prefix = what + "_" 377 378 fs_config = os.path.join( 379 input_dir, "META/" + fs_config_prefix + "filesystem_config.txt") 380 if not os.path.exists(fs_config): 381 fs_config = None 382 383 # Override values loaded from info_dict. 384 if fs_config: 385 image_props["fs_config"] = fs_config 386 if block_list: 387 image_props["block_list"] = block_list.name 388 389 # Use repeatable ext4 FS UUID and hash_seed UUID (based on partition name and 390 # build fingerprint). 391 build_info = common.BuildInfo(info_dict) 392 uuid_seed = what + "-" + build_info.GetPartitionFingerprint(what) 393 image_props["uuid"] = str(uuid.uuid5(uuid.NAMESPACE_URL, uuid_seed)) 394 hash_seed = "hash_seed-" + uuid_seed 395 image_props["hash_seed"] = str(uuid.uuid5(uuid.NAMESPACE_URL, hash_seed)) 396 397 build_image.BuildImage( 398 os.path.join(input_dir, what.upper()), image_props, output_file.name) 399 400 output_file.Write() 401 if block_list: 402 block_list.Write() 403 404 # Set the '_image_size' for given image size. 405 is_verity_partition = "verity_block_device" in image_props 406 verity_supported = (image_props.get("verity") == "true" or 407 image_props.get("avb_enable") == "true") 408 is_avb_enable = image_props.get("avb_hashtree_enable") == "true" 409 if verity_supported and (is_verity_partition or is_avb_enable): 410 image_size = image_props.get("image_size") 411 if image_size: 412 image_size_key = what + "_image_size" 413 info_dict[image_size_key] = int(image_size) 414 415 use_dynamic_size = ( 416 info_dict.get("use_dynamic_partition_size") == "true" and 417 what in shlex.split(info_dict.get("dynamic_partition_list", "").strip())) 418 if use_dynamic_size: 419 info_dict.update(build_image.GlobalDictFromImageProp(image_props, what)) 420 421 422def AddUserdata(output_zip): 423 """Create a userdata image and store it in output_zip. 424 425 In most case we just create and store an empty userdata.img; 426 But the invoker can also request to create userdata.img with real 427 data from the target files, by setting "userdata_img_with_data=true" 428 in OPTIONS.info_dict. 429 """ 430 431 img = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "userdata.img") 432 if os.path.exists(img.name): 433 logger.info("userdata.img already exists; no need to rebuild...") 434 return 435 436 # Skip userdata.img if no size. 437 image_props = build_image.ImagePropFromGlobalDict(OPTIONS.info_dict, "data") 438 if not image_props.get("partition_size"): 439 return 440 441 logger.info("creating userdata.img...") 442 443 image_props["timestamp"] = FIXED_FILE_TIMESTAMP 444 445 if OPTIONS.info_dict.get("userdata_img_with_data") == "true": 446 user_dir = os.path.join(OPTIONS.input_tmp, "DATA") 447 else: 448 user_dir = common.MakeTempDir() 449 450 build_image.BuildImage(user_dir, image_props, img.name) 451 452 common.CheckSize(img.name, "userdata.img", OPTIONS.info_dict) 453 img.Write() 454 455 456def AddVBMeta(output_zip, partitions, name, needed_partitions): 457 """Creates a VBMeta image and stores it in output_zip. 458 459 It generates the requested VBMeta image. The requested image could be for 460 top-level or chained VBMeta image, which is determined based on the name. 461 462 Args: 463 output_zip: The output zip file, which needs to be already open. 464 partitions: A dict that's keyed by partition names with image paths as 465 values. Only valid partition names are accepted, as partitions listed 466 in common.AVB_PARTITIONS and custom partitions listed in 467 OPTIONS.info_dict.get("avb_custom_images_partition_list") 468 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'. 469 needed_partitions: Partitions whose descriptors should be included into the 470 generated VBMeta image. 471 472 Returns: 473 Path to the created image. 474 475 Raises: 476 AssertionError: On invalid input args. 477 """ 478 assert needed_partitions, "Needed partitions must be specified" 479 480 img = OutputFile( 481 output_zip, OPTIONS.input_tmp, "IMAGES", "{}.img".format(name)) 482 if os.path.exists(img.name): 483 logger.info("%s.img already exists; not rebuilding...", name) 484 return img.name 485 486 common.BuildVBMeta(img.name, partitions, name, needed_partitions) 487 img.Write() 488 return img.name 489 490 491def AddPartitionTable(output_zip): 492 """Create a partition table image and store it in output_zip.""" 493 494 img = OutputFile( 495 output_zip, OPTIONS.input_tmp, "IMAGES", "partition-table.img") 496 bpt = OutputFile( 497 output_zip, OPTIONS.input_tmp, "META", "partition-table.bpt") 498 499 # use BPTTOOL from environ, or "bpttool" if empty or not set. 500 bpttool = os.getenv("BPTTOOL") or "bpttool" 501 cmd = [bpttool, "make_table", "--output_json", bpt.name, 502 "--output_gpt", img.name] 503 input_files_str = OPTIONS.info_dict["board_bpt_input_files"] 504 input_files = input_files_str.split(" ") 505 for i in input_files: 506 cmd.extend(["--input", i]) 507 disk_size = OPTIONS.info_dict.get("board_bpt_disk_size") 508 if disk_size: 509 cmd.extend(["--disk_size", disk_size]) 510 args = OPTIONS.info_dict.get("board_bpt_make_table_args") 511 if args: 512 cmd.extend(shlex.split(args)) 513 common.RunAndCheckOutput(cmd) 514 515 img.Write() 516 bpt.Write() 517 518 519def AddCache(output_zip): 520 """Create an empty cache image and store it in output_zip.""" 521 522 img = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "cache.img") 523 if os.path.exists(img.name): 524 logger.info("cache.img already exists; no need to rebuild...") 525 return 526 527 image_props = build_image.ImagePropFromGlobalDict(OPTIONS.info_dict, "cache") 528 # The build system has to explicitly request for cache.img. 529 if "fs_type" not in image_props: 530 return 531 532 logger.info("creating cache.img...") 533 534 image_props["timestamp"] = FIXED_FILE_TIMESTAMP 535 536 user_dir = common.MakeTempDir() 537 build_image.BuildImage(user_dir, image_props, img.name) 538 539 common.CheckSize(img.name, "cache.img", OPTIONS.info_dict) 540 img.Write() 541 542 543def CheckAbOtaImages(output_zip, ab_partitions): 544 """Checks that all the listed A/B partitions have their images available. 545 546 The images need to be available under IMAGES/ or RADIO/, with the former takes 547 a priority. 548 549 Args: 550 output_zip: The output zip file (needs to be already open), or None to 551 find images in OPTIONS.input_tmp/. 552 ab_partitions: The list of A/B partitions. 553 554 Raises: 555 AssertionError: If it can't find an image. 556 """ 557 for partition in ab_partitions: 558 img_name = partition.strip() + ".img" 559 560 # Assert that the image is present under IMAGES/ now. 561 if output_zip: 562 # Zip spec says: All slashes MUST be forward slashes. 563 images_path = "IMAGES/" + img_name 564 radio_path = "RADIO/" + img_name 565 available = (images_path in output_zip.namelist() or 566 radio_path in output_zip.namelist()) 567 else: 568 images_path = os.path.join(OPTIONS.input_tmp, "IMAGES", img_name) 569 radio_path = os.path.join(OPTIONS.input_tmp, "RADIO", img_name) 570 available = os.path.exists(images_path) or os.path.exists(radio_path) 571 572 assert available, "Failed to find " + img_name 573 574 575def AddCareMapForAbOta(output_zip, ab_partitions, image_paths): 576 """Generates and adds care_map.pb for a/b partition that has care_map. 577 578 Args: 579 output_zip: The output zip file (needs to be already open), or None to 580 write care_map.pb to OPTIONS.input_tmp/. 581 ab_partitions: The list of A/B partitions. 582 image_paths: A map from the partition name to the image path. 583 """ 584 care_map_list = [] 585 for partition in ab_partitions: 586 partition = partition.strip() 587 if partition not in common.PARTITIONS_WITH_CARE_MAP: 588 continue 589 590 verity_block_device = "{}_verity_block_device".format(partition) 591 avb_hashtree_enable = "avb_{}_hashtree_enable".format(partition) 592 if (verity_block_device in OPTIONS.info_dict or 593 OPTIONS.info_dict.get(avb_hashtree_enable) == "true"): 594 image_path = image_paths[partition] 595 assert os.path.exists(image_path) 596 597 care_map = GetCareMap(partition, image_path) 598 if not care_map: 599 continue 600 care_map_list += care_map 601 602 # adds fingerprint field to the care_map 603 # TODO(xunchang) revisit the fingerprint calculation for care_map. 604 partition_props = OPTIONS.info_dict.get(partition + ".build.prop") 605 prop_name_list = ["ro.{}.build.fingerprint".format(partition), 606 "ro.{}.build.thumbprint".format(partition)] 607 608 present_props = [x for x in prop_name_list if 609 partition_props and partition_props.GetProp(x)] 610 if not present_props: 611 logger.warning("fingerprint is not present for partition %s", partition) 612 property_id, fingerprint = "unknown", "unknown" 613 else: 614 property_id = present_props[0] 615 fingerprint = partition_props.GetProp(property_id) 616 care_map_list += [property_id, fingerprint] 617 618 if not care_map_list: 619 return 620 621 # Converts the list into proto buf message by calling care_map_generator; and 622 # writes the result to a temp file. 623 temp_care_map_text = common.MakeTempFile(prefix="caremap_text-", 624 suffix=".txt") 625 with open(temp_care_map_text, 'w') as text_file: 626 text_file.write('\n'.join(care_map_list)) 627 628 temp_care_map = common.MakeTempFile(prefix="caremap-", suffix=".pb") 629 care_map_gen_cmd = ["care_map_generator", temp_care_map_text, temp_care_map] 630 common.RunAndCheckOutput(care_map_gen_cmd) 631 632 care_map_path = "META/care_map.pb" 633 if output_zip and care_map_path not in output_zip.namelist(): 634 common.ZipWrite(output_zip, temp_care_map, arcname=care_map_path) 635 else: 636 shutil.copy(temp_care_map, os.path.join(OPTIONS.input_tmp, care_map_path)) 637 if output_zip: 638 OPTIONS.replace_updated_files_list.append(care_map_path) 639 640 641def AddPackRadioImages(output_zip, images): 642 """Copies images listed in META/pack_radioimages.txt from RADIO/ to IMAGES/. 643 644 Args: 645 output_zip: The output zip file (needs to be already open), or None to 646 write images to OPTIONS.input_tmp/. 647 images: A list of image names. 648 649 Raises: 650 AssertionError: If a listed image can't be found. 651 """ 652 for image in images: 653 img_name = image.strip() 654 _, ext = os.path.splitext(img_name) 655 if not ext: 656 img_name += ".img" 657 658 prebuilt_path = os.path.join(OPTIONS.input_tmp, "IMAGES", img_name) 659 if os.path.exists(prebuilt_path): 660 logger.info("%s already exists, no need to overwrite...", img_name) 661 continue 662 663 img_radio_path = os.path.join(OPTIONS.input_tmp, "RADIO", img_name) 664 assert os.path.exists(img_radio_path), \ 665 "Failed to find %s at %s" % (img_name, img_radio_path) 666 667 if output_zip: 668 common.ZipWrite(output_zip, img_radio_path, "IMAGES/" + img_name) 669 else: 670 shutil.copy(img_radio_path, prebuilt_path) 671 672 673def AddSuperEmpty(output_zip): 674 """Create a super_empty.img and store it in output_zip.""" 675 676 img = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "super_empty.img") 677 build_super_image.BuildSuperImage(OPTIONS.info_dict, img.name) 678 img.Write() 679 680 681def AddSuperSplit(output_zip): 682 """Create split super_*.img and store it in output_zip.""" 683 684 outdir = os.path.join(OPTIONS.input_tmp, "OTA") 685 built = build_super_image.BuildSuperImage(OPTIONS.input_tmp, outdir) 686 687 if built: 688 for dev in OPTIONS.info_dict['super_block_devices'].strip().split(): 689 img = OutputFile(output_zip, OPTIONS.input_tmp, "OTA", 690 "super_" + dev + ".img") 691 img.Write() 692 693 694def ReplaceUpdatedFiles(zip_filename, files_list): 695 """Updates all the ZIP entries listed in files_list. 696 697 For now the list includes META/care_map.pb, and the related files under 698 SYSTEM/ after rebuilding recovery. 699 """ 700 common.ZipDelete(zip_filename, files_list) 701 output_zip = zipfile.ZipFile(zip_filename, "a", 702 compression=zipfile.ZIP_DEFLATED, 703 allowZip64=True) 704 for item in files_list: 705 file_path = os.path.join(OPTIONS.input_tmp, item) 706 assert os.path.exists(file_path) 707 common.ZipWrite(output_zip, file_path, arcname=item) 708 common.ZipClose(output_zip) 709 710 711def AddImagesToTargetFiles(filename): 712 """Creates and adds images (boot/recovery/system/...) to a target_files.zip. 713 714 It works with either a zip file (zip mode), or a directory that contains the 715 files to be packed into a target_files.zip (dir mode). The latter is used when 716 being called from build/make/core/Makefile. 717 718 The images will be created under IMAGES/ in the input target_files.zip. 719 720 Args: 721 filename: the target_files.zip, or the zip root directory. 722 """ 723 if os.path.isdir(filename): 724 OPTIONS.input_tmp = os.path.abspath(filename) 725 else: 726 OPTIONS.input_tmp = common.UnzipTemp(filename) 727 728 if not OPTIONS.add_missing: 729 if os.path.isdir(os.path.join(OPTIONS.input_tmp, "IMAGES")): 730 logger.warning("target_files appears to already contain images.") 731 sys.exit(1) 732 733 OPTIONS.info_dict = common.LoadInfoDict(OPTIONS.input_tmp, repacking=True) 734 735 has_recovery = OPTIONS.info_dict.get("no_recovery") != "true" 736 has_boot = OPTIONS.info_dict.get("no_boot") != "true" 737 has_vendor_boot = OPTIONS.info_dict.get("vendor_boot") == "true" 738 739 # {vendor,odm,product,system_ext}.img are unlike system.img or 740 # system_other.img. Because it could be built from source, or dropped into 741 # target_files.zip as a prebuilt blob. We consider either of them as 742 # {vendor,product,system_ext}.img being available, which could be 743 # used when generating vbmeta.img for AVB. 744 has_vendor = (os.path.isdir(os.path.join(OPTIONS.input_tmp, "VENDOR")) or 745 os.path.exists(os.path.join(OPTIONS.input_tmp, "IMAGES", 746 "vendor.img"))) 747 has_odm = (os.path.isdir(os.path.join(OPTIONS.input_tmp, "ODM")) or 748 os.path.exists(os.path.join(OPTIONS.input_tmp, "IMAGES", 749 "odm.img"))) 750 has_product = (os.path.isdir(os.path.join(OPTIONS.input_tmp, "PRODUCT")) or 751 os.path.exists(os.path.join(OPTIONS.input_tmp, "IMAGES", 752 "product.img"))) 753 has_system_ext = (os.path.isdir(os.path.join(OPTIONS.input_tmp, 754 "SYSTEM_EXT")) or 755 os.path.exists(os.path.join(OPTIONS.input_tmp, 756 "IMAGES", 757 "system_ext.img"))) 758 has_system = os.path.isdir(os.path.join(OPTIONS.input_tmp, "SYSTEM")) 759 has_system_other = os.path.isdir(os.path.join(OPTIONS.input_tmp, 760 "SYSTEM_OTHER")) 761 762 # Set up the output destination. It writes to the given directory for dir 763 # mode; otherwise appends to the given ZIP. 764 if os.path.isdir(filename): 765 output_zip = None 766 else: 767 output_zip = zipfile.ZipFile(filename, "a", 768 compression=zipfile.ZIP_DEFLATED, 769 allowZip64=True) 770 771 # Always make input_tmp/IMAGES available, since we may stage boot / recovery 772 # images there even under zip mode. The directory will be cleaned up as part 773 # of OPTIONS.input_tmp. 774 images_dir = os.path.join(OPTIONS.input_tmp, "IMAGES") 775 if not os.path.isdir(images_dir): 776 os.makedirs(images_dir) 777 778 # A map between partition names and their paths, which could be used when 779 # generating AVB vbmeta image. 780 partitions = {} 781 782 def banner(s): 783 logger.info("\n\n++++ %s ++++\n\n", s) 784 785 boot_image = None 786 if has_boot: 787 banner("boot") 788 boot_images = OPTIONS.info_dict.get("boot_images") 789 if boot_images is None: 790 boot_images = "boot.img" 791 for index,b in enumerate(boot_images.split()): 792 # common.GetBootableImage() returns the image directly if present. 793 boot_image = common.GetBootableImage( 794 "IMAGES/" + b, b, OPTIONS.input_tmp, "BOOT") 795 # boot.img may be unavailable in some targets (e.g. aosp_arm64). 796 if boot_image: 797 boot_image_path = os.path.join(OPTIONS.input_tmp, "IMAGES", b) 798 # Although multiple boot images can be generated, include the image 799 # descriptor of only the first boot image in vbmeta 800 if index == 0: 801 partitions['boot'] = boot_image_path 802 if not os.path.exists(boot_image_path): 803 boot_image.WriteToDir(OPTIONS.input_tmp) 804 if output_zip: 805 boot_image.AddToZip(output_zip) 806 807 if has_vendor_boot: 808 banner("vendor_boot") 809 vendor_boot_image = common.GetVendorBootImage( 810 "IMAGES/vendor_boot.img", "vendor_boot.img", OPTIONS.input_tmp, 811 "VENDOR_BOOT") 812 if vendor_boot_image: 813 partitions['vendor_boot'] = os.path.join(OPTIONS.input_tmp, "IMAGES", 814 "vendor_boot.img") 815 if not os.path.exists(partitions['vendor_boot']): 816 vendor_boot_image.WriteToDir(OPTIONS.input_tmp) 817 if output_zip: 818 vendor_boot_image.AddToZip(output_zip) 819 820 recovery_image = None 821 if has_recovery: 822 banner("recovery") 823 recovery_image = common.GetBootableImage( 824 "IMAGES/recovery.img", "recovery.img", OPTIONS.input_tmp, "RECOVERY") 825 assert recovery_image, "Failed to create recovery.img." 826 partitions['recovery'] = os.path.join( 827 OPTIONS.input_tmp, "IMAGES", "recovery.img") 828 if not os.path.exists(partitions['recovery']): 829 recovery_image.WriteToDir(OPTIONS.input_tmp) 830 if output_zip: 831 recovery_image.AddToZip(output_zip) 832 833 banner("recovery (two-step image)") 834 # The special recovery.img for two-step package use. 835 recovery_two_step_image = common.GetBootableImage( 836 "OTA/recovery-two-step.img", "recovery-two-step.img", 837 OPTIONS.input_tmp, "RECOVERY", two_step_image=True) 838 assert recovery_two_step_image, "Failed to create recovery-two-step.img." 839 recovery_two_step_image_path = os.path.join( 840 OPTIONS.input_tmp, "OTA", "recovery-two-step.img") 841 if not os.path.exists(recovery_two_step_image_path): 842 recovery_two_step_image.WriteToDir(OPTIONS.input_tmp) 843 if output_zip: 844 recovery_two_step_image.AddToZip(output_zip) 845 846 if has_system: 847 banner("system") 848 partitions['system'] = AddSystem( 849 output_zip, recovery_img=recovery_image, boot_img=boot_image) 850 851 if has_vendor: 852 banner("vendor") 853 partitions['vendor'] = AddVendor( 854 output_zip, recovery_img=recovery_image, boot_img=boot_image) 855 856 if has_product: 857 banner("product") 858 partitions['product'] = AddProduct(output_zip) 859 860 if has_system_ext: 861 banner("system_ext") 862 partitions['system_ext'] = AddSystemExt(output_zip) 863 864 if has_odm: 865 banner("odm") 866 partitions['odm'] = AddOdm(output_zip) 867 868 if has_system_other: 869 banner("system_other") 870 AddSystemOther(output_zip) 871 872 if not OPTIONS.is_signing: 873 banner("userdata") 874 AddUserdata(output_zip) 875 banner("cache") 876 AddCache(output_zip) 877 878 if OPTIONS.info_dict.get("board_bpt_enable") == "true": 879 banner("partition-table") 880 AddPartitionTable(output_zip) 881 882 if OPTIONS.info_dict.get("has_dtbo") == "true": 883 banner("dtbo") 884 partitions['dtbo'] = AddDtbo(output_zip) 885 886 # Custom images. 887 custom_partitions = OPTIONS.info_dict.get( 888 "avb_custom_images_partition_list", "").strip().split() 889 for partition_name in custom_partitions: 890 partition_name = partition_name.strip() 891 banner("custom images for " + partition_name) 892 partitions[partition_name] = AddCustomImages(output_zip, partition_name) 893 894 if OPTIONS.info_dict.get("avb_enable") == "true": 895 # vbmeta_partitions includes the partitions that should be included into 896 # top-level vbmeta.img, which are the ones that are not included in any 897 # chained VBMeta image plus the chained VBMeta images themselves. 898 # Currently custom_partitions are all chained to VBMeta image. 899 vbmeta_partitions = common.AVB_PARTITIONS[:] + tuple(custom_partitions) 900 901 vbmeta_system = OPTIONS.info_dict.get("avb_vbmeta_system", "").strip() 902 if vbmeta_system: 903 banner("vbmeta_system") 904 partitions["vbmeta_system"] = AddVBMeta( 905 output_zip, partitions, "vbmeta_system", vbmeta_system.split()) 906 vbmeta_partitions = [ 907 item for item in vbmeta_partitions 908 if item not in vbmeta_system.split()] 909 vbmeta_partitions.append("vbmeta_system") 910 911 vbmeta_vendor = OPTIONS.info_dict.get("avb_vbmeta_vendor", "").strip() 912 if vbmeta_vendor: 913 banner("vbmeta_vendor") 914 partitions["vbmeta_vendor"] = AddVBMeta( 915 output_zip, partitions, "vbmeta_vendor", vbmeta_vendor.split()) 916 vbmeta_partitions = [ 917 item for item in vbmeta_partitions 918 if item not in vbmeta_vendor.split()] 919 vbmeta_partitions.append("vbmeta_vendor") 920 921 banner("vbmeta") 922 AddVBMeta(output_zip, partitions, "vbmeta", vbmeta_partitions) 923 924 if OPTIONS.info_dict.get("use_dynamic_partitions") == "true": 925 banner("super_empty") 926 AddSuperEmpty(output_zip) 927 928 if OPTIONS.info_dict.get("build_super_partition") == "true": 929 if OPTIONS.info_dict.get( 930 "build_retrofit_dynamic_partitions_ota_package") == "true": 931 banner("super split images") 932 AddSuperSplit(output_zip) 933 934 banner("radio") 935 ab_partitions_txt = os.path.join(OPTIONS.input_tmp, "META", 936 "ab_partitions.txt") 937 if os.path.exists(ab_partitions_txt): 938 with open(ab_partitions_txt) as f: 939 ab_partitions = f.readlines() 940 941 # For devices using A/B update, make sure we have all the needed images 942 # ready under IMAGES/ or RADIO/. 943 CheckAbOtaImages(output_zip, ab_partitions) 944 945 # Generate care_map.pb for ab_partitions, then write this file to 946 # target_files package. 947 AddCareMapForAbOta(output_zip, ab_partitions, partitions) 948 949 # Radio images that need to be packed into IMAGES/, and product-img.zip. 950 pack_radioimages_txt = os.path.join( 951 OPTIONS.input_tmp, "META", "pack_radioimages.txt") 952 if os.path.exists(pack_radioimages_txt): 953 with open(pack_radioimages_txt) as f: 954 AddPackRadioImages(output_zip, f.readlines()) 955 956 if output_zip: 957 common.ZipClose(output_zip) 958 if OPTIONS.replace_updated_files_list: 959 ReplaceUpdatedFiles(output_zip.filename, 960 OPTIONS.replace_updated_files_list) 961 962 963def main(argv): 964 def option_handler(o, a): 965 if o in ("-a", "--add_missing"): 966 OPTIONS.add_missing = True 967 elif o in ("-r", "--rebuild_recovery",): 968 OPTIONS.rebuild_recovery = True 969 elif o == "--replace_verity_private_key": 970 OPTIONS.replace_verity_private_key = (True, a) 971 elif o == "--replace_verity_public_key": 972 OPTIONS.replace_verity_public_key = (True, a) 973 elif o == "--is_signing": 974 OPTIONS.is_signing = True 975 else: 976 return False 977 return True 978 979 args = common.ParseOptions( 980 argv, __doc__, extra_opts="ar", 981 extra_long_opts=["add_missing", "rebuild_recovery", 982 "replace_verity_public_key=", 983 "replace_verity_private_key=", 984 "is_signing"], 985 extra_option_handler=option_handler) 986 987 if len(args) != 1: 988 common.Usage(__doc__) 989 sys.exit(1) 990 991 common.InitLogging() 992 993 AddImagesToTargetFiles(args[0]) 994 logger.info("done.") 995 996if __name__ == '__main__': 997 try: 998 common.CloseInheritedPipes() 999 main(sys.argv[1:]) 1000 except common.ExternalError: 1001 logger.exception("\n ERROR:\n") 1002 sys.exit(1) 1003 finally: 1004 common.Cleanup() 1005