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 """ 18 Given a target-files zipfile that does not contain images (ie, does 19 not have an IMAGES/ top-level subdirectory), produce the images and 20 add them to the zipfile. 21 22 Usage: 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. 32 33 --replace_verity_private_key 34 Replace the private key used for verity signing. (same as the option 35 in sign_target_files_apks) 36 37 --replace_verity_public_key 38 Replace the certificate (public key) used for verity verification. (same 39 as the option in sign_target_files_apks) 40 41 --is_signing 42 Skip building & adding the images for "userdata" and "cache" if we 43 are signing the target files. 44 """ 45 46 from __future__ import print_function 47 48 import datetime 49 import os 50 import shlex 51 import shutil 52 import subprocess 53 import sys 54 import uuid 55 import zipfile 56 57 import build_image 58 import common 59 import rangelib 60 import sparse_img 61 62 if sys.hexversion < 0x02070000: 63 print("Python 2.7 or newer is required.", file=sys.stderr) 64 sys.exit(1) 65 66 OPTIONS = common.OPTIONS 67 68 OPTIONS.add_missing = False 69 OPTIONS.rebuild_recovery = False 70 OPTIONS.replace_updated_files_list = [] 71 OPTIONS.replace_verity_public_key = False 72 OPTIONS.replace_verity_private_key = False 73 OPTIONS.is_signing = False 74 75 76 # Partitions that should have their care_map added to META/care_map.txt. 77 PARTITIONS_WITH_CARE_MAP = ('system', 'vendor', 'product') 78 79 80 class OutputFile(object): 81 def __init__(self, output_zip, input_dir, prefix, name): 82 self._output_zip = output_zip 83 self.input_name = os.path.join(input_dir, prefix, name) 84 85 if self._output_zip: 86 self._zip_name = os.path.join(prefix, name) 87 88 root, suffix = os.path.splitext(name) 89 self.name = common.MakeTempFile(prefix=root + '-', suffix=suffix) 90 else: 91 self.name = self.input_name 92 93 def Write(self): 94 if self._output_zip: 95 common.ZipWrite(self._output_zip, self.name, self._zip_name) 96 97 98 def GetCareMap(which, imgname): 99 """Returns the care_map string for the given partition. 100 101 Args: 102 which: The partition name, must be listed in PARTITIONS_WITH_CARE_MAP. 103 imgname: The filename of the image. 104 105 Returns: 106 (which, care_map_ranges): care_map_ranges is the raw string of the care_map 107 RangeSet. 108 """ 109 assert which in PARTITIONS_WITH_CARE_MAP 110 111 simg = sparse_img.SparseImage(imgname) 112 care_map_ranges = simg.care_map 113 key = which + "_adjusted_partition_size" 114 adjusted_blocks = OPTIONS.info_dict.get(key) 115 if adjusted_blocks: 116 assert adjusted_blocks > 0, "blocks should be positive for " + which 117 care_map_ranges = care_map_ranges.intersect(rangelib.RangeSet( 118 "0-%d" % (adjusted_blocks,))) 119 120 return [which, care_map_ranges.to_string_raw()] 121 122 123 def AddSystem(output_zip, recovery_img=None, boot_img=None): 124 """Turn the contents of SYSTEM into a system image and store it in 125 output_zip. Returns the name of the system image file.""" 126 127 img = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "system.img") 128 if os.path.exists(img.input_name): 129 print("system.img already exists; no need to rebuild...") 130 return img.input_name 131 132 def output_sink(fn, data): 133 ofile = open(os.path.join(OPTIONS.input_tmp, "SYSTEM", fn), "w") 134 ofile.write(data) 135 ofile.close() 136 137 arc_name = "SYSTEM/" + fn 138 if arc_name in output_zip.namelist(): 139 OPTIONS.replace_updated_files_list.append(arc_name) 140 else: 141 common.ZipWrite(output_zip, ofile.name, arc_name) 142 143 if OPTIONS.rebuild_recovery: 144 print("Building new recovery patch") 145 common.MakeRecoveryPatch(OPTIONS.input_tmp, output_sink, recovery_img, 146 boot_img, info_dict=OPTIONS.info_dict) 147 148 block_list = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "system.map") 149 CreateImage(OPTIONS.input_tmp, OPTIONS.info_dict, "system", img, 150 block_list=block_list) 151 152 return img.name 153 154 155 def AddSystemOther(output_zip): 156 """Turn the contents of SYSTEM_OTHER into a system_other image 157 and store it in output_zip.""" 158 159 img = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "system_other.img") 160 if os.path.exists(img.input_name): 161 print("system_other.img already exists; no need to rebuild...") 162 return 163 164 CreateImage(OPTIONS.input_tmp, OPTIONS.info_dict, "system_other", img) 165 166 167 def AddVendor(output_zip): 168 """Turn the contents of VENDOR into a vendor image and store in it 169 output_zip.""" 170 171 img = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "vendor.img") 172 if os.path.exists(img.input_name): 173 print("vendor.img already exists; no need to rebuild...") 174 return img.input_name 175 176 block_list = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "vendor.map") 177 CreateImage(OPTIONS.input_tmp, OPTIONS.info_dict, "vendor", img, 178 block_list=block_list) 179 return img.name 180 181 182 def AddProduct(output_zip): 183 """Turn the contents of PRODUCT into a product image and store it in 184 output_zip.""" 185 186 img = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "product.img") 187 if os.path.exists(img.input_name): 188 print("product.img already exists; no need to rebuild...") 189 return img.input_name 190 191 block_list = OutputFile( 192 output_zip, OPTIONS.input_tmp, "IMAGES", "product.map") 193 CreateImage( 194 OPTIONS.input_tmp, OPTIONS.info_dict, "product", img, 195 block_list=block_list) 196 return img.name 197 198 199 def AddDtbo(output_zip): 200 """Adds the DTBO image. 201 202 Uses the image under IMAGES/ if it already exists. Otherwise looks for the 203 image under PREBUILT_IMAGES/, signs it as needed, and returns the image name. 204 """ 205 img = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "dtbo.img") 206 if os.path.exists(img.input_name): 207 print("dtbo.img already exists; no need to rebuild...") 208 return img.input_name 209 210 dtbo_prebuilt_path = os.path.join( 211 OPTIONS.input_tmp, "PREBUILT_IMAGES", "dtbo.img") 212 assert os.path.exists(dtbo_prebuilt_path) 213 shutil.copy(dtbo_prebuilt_path, img.name) 214 215 # AVB-sign the image as needed. 216 if OPTIONS.info_dict.get("avb_enable") == "true": 217 avbtool = os.getenv('AVBTOOL') or OPTIONS.info_dict["avb_avbtool"] 218 part_size = OPTIONS.info_dict["dtbo_size"] 219 # The AVB hash footer will be replaced if already present. 220 cmd = [avbtool, "add_hash_footer", "--image", img.name, 221 "--partition_size", str(part_size), "--partition_name", "dtbo"] 222 common.AppendAVBSigningArgs(cmd, "dtbo") 223 args = OPTIONS.info_dict.get("avb_dtbo_add_hash_footer_args") 224 if args and args.strip(): 225 cmd.extend(shlex.split(args)) 226 p = common.Run(cmd, stdout=subprocess.PIPE) 227 p.communicate() 228 assert p.returncode == 0, \ 229 "avbtool add_hash_footer of %s failed" % (img.name,) 230 231 img.Write() 232 return img.name 233 234 235 def CreateImage(input_dir, info_dict, what, output_file, block_list=None): 236 print("creating " + what + ".img...") 237 238 image_props = build_image.ImagePropFromGlobalDict(info_dict, what) 239 fstab = info_dict["fstab"] 240 mount_point = "/" + what 241 if fstab and mount_point in fstab: 242 image_props["fs_type"] = fstab[mount_point].fs_type 243 244 # Use a fixed timestamp (01/01/2009) when packaging the image. 245 # Bug: 24377993 246 epoch = datetime.datetime.fromtimestamp(0) 247 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds() 248 image_props["timestamp"] = int(timestamp) 249 250 if what == "system": 251 fs_config_prefix = "" 252 else: 253 fs_config_prefix = what + "_" 254 255 fs_config = os.path.join( 256 input_dir, "META/" + fs_config_prefix + "filesystem_config.txt") 257 if not os.path.exists(fs_config): 258 fs_config = None 259 260 # Override values loaded from info_dict. 261 if fs_config: 262 image_props["fs_config"] = fs_config 263 if block_list: 264 image_props["block_list"] = block_list.name 265 266 # Use repeatable ext4 FS UUID and hash_seed UUID (based on partition name and 267 # build fingerprint). 268 uuid_seed = what + "-" 269 if "build.prop" in info_dict: 270 build_prop = info_dict["build.prop"] 271 if "ro.build.fingerprint" in build_prop: 272 uuid_seed += build_prop["ro.build.fingerprint"] 273 elif "ro.build.thumbprint" in build_prop: 274 uuid_seed += build_prop["ro.build.thumbprint"] 275 image_props["uuid"] = str(uuid.uuid5(uuid.NAMESPACE_URL, uuid_seed)) 276 hash_seed = "hash_seed-" + uuid_seed 277 image_props["hash_seed"] = str(uuid.uuid5(uuid.NAMESPACE_URL, hash_seed)) 278 279 succ = build_image.BuildImage(os.path.join(input_dir, what.upper()), 280 image_props, output_file.name) 281 assert succ, "build " + what + ".img image failed" 282 283 output_file.Write() 284 if block_list: 285 block_list.Write() 286 287 # Set the 'adjusted_partition_size' that excludes the verity blocks of the 288 # given image. When avb is enabled, this size is the max image size returned 289 # by the avb tool. 290 is_verity_partition = "verity_block_device" in image_props 291 verity_supported = (image_props.get("verity") == "true" or 292 image_props.get("avb_enable") == "true") 293 is_avb_enable = image_props.get("avb_hashtree_enable") == "true" 294 if verity_supported and (is_verity_partition or is_avb_enable): 295 adjusted_blocks_value = image_props.get("partition_size") 296 if adjusted_blocks_value: 297 adjusted_blocks_key = what + "_adjusted_partition_size" 298 info_dict[adjusted_blocks_key] = int(adjusted_blocks_value)/4096 - 1 299 300 301 def AddUserdata(output_zip): 302 """Create a userdata image and store it in output_zip. 303 304 In most case we just create and store an empty userdata.img; 305 But the invoker can also request to create userdata.img with real 306 data from the target files, by setting "userdata_img_with_data=true" 307 in OPTIONS.info_dict. 308 """ 309 310 img = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "userdata.img") 311 if os.path.exists(img.input_name): 312 print("userdata.img already exists; no need to rebuild...") 313 return 314 315 # Skip userdata.img if no size. 316 image_props = build_image.ImagePropFromGlobalDict(OPTIONS.info_dict, "data") 317 if not image_props.get("partition_size"): 318 return 319 320 print("creating userdata.img...") 321 322 # Use a fixed timestamp (01/01/2009) when packaging the image. 323 # Bug: 24377993 324 epoch = datetime.datetime.fromtimestamp(0) 325 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds() 326 image_props["timestamp"] = int(timestamp) 327 328 if OPTIONS.info_dict.get("userdata_img_with_data") == "true": 329 user_dir = os.path.join(OPTIONS.input_tmp, "DATA") 330 else: 331 user_dir = common.MakeTempDir() 332 333 fstab = OPTIONS.info_dict["fstab"] 334 if fstab: 335 image_props["fs_type"] = fstab["/data"].fs_type 336 succ = build_image.BuildImage(user_dir, image_props, img.name) 337 assert succ, "build userdata.img image failed" 338 339 common.CheckSize(img.name, "userdata.img", OPTIONS.info_dict) 340 img.Write() 341 342 343 def AppendVBMetaArgsForPartition(cmd, partition, img_path, public_key_dir): 344 if not img_path: 345 return 346 347 # Check if chain partition is used. 348 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path") 349 if key_path: 350 # extract public key in AVB format to be included in vbmeta.img 351 avbtool = os.getenv('AVBTOOL') or OPTIONS.info_dict["avb_avbtool"] 352 public_key_path = os.path.join(public_key_dir, "%s.avbpubkey" % partition) 353 p = common.Run([avbtool, "extract_public_key", "--key", key_path, 354 "--output", public_key_path], 355 stdout=subprocess.PIPE, stderr=subprocess.PIPE) 356 p.communicate() 357 assert p.returncode == 0, \ 358 "avbtool extract_public_key fail for partition: %r" % partition 359 360 rollback_index_location = OPTIONS.info_dict[ 361 "avb_" + partition + "_rollback_index_location"] 362 cmd.extend(["--chain_partition", "%s:%s:%s" % ( 363 partition, rollback_index_location, public_key_path)]) 364 else: 365 cmd.extend(["--include_descriptors_from_image", img_path]) 366 367 368 def AddVBMeta(output_zip, partitions): 369 """Creates a VBMeta image and store it in output_zip. 370 371 Args: 372 output_zip: The output zip file, which needs to be already open. 373 partitions: A dict that's keyed by partition names with image paths as 374 values. Only valid partition names are accepted, which include 'boot', 375 'recovery', 'system', 'vendor', 'dtbo'. 376 """ 377 img = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "vbmeta.img") 378 if os.path.exists(img.input_name): 379 print("vbmeta.img already exists; not rebuilding...") 380 return img.input_name 381 382 avbtool = os.getenv('AVBTOOL') or OPTIONS.info_dict["avb_avbtool"] 383 cmd = [avbtool, "make_vbmeta_image", "--output", img.name] 384 common.AppendAVBSigningArgs(cmd, "vbmeta") 385 386 public_key_dir = common.MakeTempDir(prefix="avbpubkey-") 387 for partition, path in partitions.items(): 388 assert partition in common.AVB_PARTITIONS, 'Unknown partition: %s' % ( 389 partition,) 390 assert os.path.exists(path), 'Failed to find %s for partition %s' % ( 391 path, partition) 392 AppendVBMetaArgsForPartition(cmd, partition, path, public_key_dir) 393 394 args = OPTIONS.info_dict.get("avb_vbmeta_args") 395 if args and args.strip(): 396 split_args = shlex.split(args) 397 for index, arg in enumerate(split_args[:-1]): 398 # Sanity check that the image file exists. Some images might be defined 399 # as a path relative to source tree, which may not be available at the 400 # same location when running this script (we have the input target_files 401 # zip only). For such cases, we additionally scan other locations (e.g. 402 # IMAGES/, RADIO/, etc) before bailing out. 403 if arg == '--include_descriptors_from_image': 404 image_path = split_args[index + 1] 405 if os.path.exists(image_path): 406 continue 407 found = False 408 for dir_name in ['IMAGES', 'RADIO', 'VENDOR_IMAGES', 'PREBUILT_IMAGES']: 409 alt_path = os.path.join( 410 OPTIONS.input_tmp, dir_name, os.path.basename(image_path)) 411 if os.path.exists(alt_path): 412 split_args[index + 1] = alt_path 413 found = True 414 break 415 assert found, 'failed to find %s' % (image_path,) 416 cmd.extend(split_args) 417 418 p = common.Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 419 p.communicate() 420 assert p.returncode == 0, "avbtool make_vbmeta_image failed" 421 img.Write() 422 423 424 def AddPartitionTable(output_zip): 425 """Create a partition table image and store it in output_zip.""" 426 427 img = OutputFile( 428 output_zip, OPTIONS.input_tmp, "IMAGES", "partition-table.img") 429 bpt = OutputFile( 430 output_zip, OPTIONS.input_tmp, "IMAGES", "partition-table.bpt") 431 432 # use BPTTOOL from environ, or "bpttool" if empty or not set. 433 bpttool = os.getenv("BPTTOOL") or "bpttool" 434 cmd = [bpttool, "make_table", "--output_json", bpt.name, 435 "--output_gpt", img.name] 436 input_files_str = OPTIONS.info_dict["board_bpt_input_files"] 437 input_files = input_files_str.split(" ") 438 for i in input_files: 439 cmd.extend(["--input", i]) 440 disk_size = OPTIONS.info_dict.get("board_bpt_disk_size") 441 if disk_size: 442 cmd.extend(["--disk_size", disk_size]) 443 args = OPTIONS.info_dict.get("board_bpt_make_table_args") 444 if args: 445 cmd.extend(shlex.split(args)) 446 447 p = common.Run(cmd, stdout=subprocess.PIPE) 448 p.communicate() 449 assert p.returncode == 0, "bpttool make_table failed" 450 451 img.Write() 452 bpt.Write() 453 454 455 def AddCache(output_zip): 456 """Create an empty cache image and store it in output_zip.""" 457 458 img = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "cache.img") 459 if os.path.exists(img.input_name): 460 print("cache.img already exists; no need to rebuild...") 461 return 462 463 image_props = build_image.ImagePropFromGlobalDict(OPTIONS.info_dict, "cache") 464 # The build system has to explicitly request for cache.img. 465 if "fs_type" not in image_props: 466 return 467 468 print("creating cache.img...") 469 470 # Use a fixed timestamp (01/01/2009) when packaging the image. 471 # Bug: 24377993 472 epoch = datetime.datetime.fromtimestamp(0) 473 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds() 474 image_props["timestamp"] = int(timestamp) 475 476 user_dir = common.MakeTempDir() 477 478 fstab = OPTIONS.info_dict["fstab"] 479 if fstab: 480 image_props["fs_type"] = fstab["/cache"].fs_type 481 succ = build_image.BuildImage(user_dir, image_props, img.name) 482 assert succ, "build cache.img image failed" 483 484 common.CheckSize(img.name, "cache.img", OPTIONS.info_dict) 485 img.Write() 486 487 488 def AddRadioImagesForAbOta(output_zip, ab_partitions): 489 """Adds the radio images needed for A/B OTA to the output file. 490 491 It parses the list of A/B partitions, looks for the missing ones from RADIO/ 492 or VENDOR_IMAGES/ dirs, and copies them to IMAGES/ of the output file (or 493 dir). 494 495 It also ensures that on returning from the function all the listed A/B 496 partitions must have their images available under IMAGES/. 497 498 Args: 499 output_zip: The output zip file (needs to be already open), or None to 500 write images to OPTIONS.input_tmp/. 501 ab_partitions: The list of A/B partitions. 502 503 Raises: 504 AssertionError: If it can't find an image. 505 """ 506 for partition in ab_partitions: 507 img_name = partition.strip() + ".img" 508 prebuilt_path = os.path.join(OPTIONS.input_tmp, "IMAGES", img_name) 509 if os.path.exists(prebuilt_path): 510 print("%s already exists, no need to overwrite..." % (img_name,)) 511 continue 512 513 img_radio_path = os.path.join(OPTIONS.input_tmp, "RADIO", img_name) 514 if os.path.exists(img_radio_path): 515 if output_zip: 516 common.ZipWrite(output_zip, img_radio_path, "IMAGES/" + img_name) 517 else: 518 shutil.copy(img_radio_path, prebuilt_path) 519 continue 520 521 # Walk through VENDOR_IMAGES/ since files could be under subdirs. 522 img_vendor_dir = os.path.join(OPTIONS.input_tmp, "VENDOR_IMAGES") 523 for root, _, files in os.walk(img_vendor_dir): 524 if img_name in files: 525 if output_zip: 526 common.ZipWrite(output_zip, os.path.join(root, img_name), 527 "IMAGES/" + img_name) 528 else: 529 shutil.copy(os.path.join(root, img_name), prebuilt_path) 530 break 531 532 # Assert that the image is present under IMAGES/ now. 533 if output_zip: 534 # Zip spec says: All slashes MUST be forward slashes. 535 img_path = 'IMAGES/' + img_name 536 assert img_path in output_zip.namelist(), "cannot find " + img_name 537 else: 538 img_path = os.path.join(OPTIONS.input_tmp, "IMAGES", img_name) 539 assert os.path.exists(img_path), "cannot find " + img_name 540 541 542 def AddCareMapTxtForAbOta(output_zip, ab_partitions, image_paths): 543 """Generates and adds care_map.txt for system and vendor partitions. 544 545 Args: 546 output_zip: The output zip file (needs to be already open), or None to 547 write images to OPTIONS.input_tmp/. 548 ab_partitions: The list of A/B partitions. 549 image_paths: A map from the partition name to the image path. 550 """ 551 care_map_list = [] 552 for partition in ab_partitions: 553 partition = partition.strip() 554 if partition not in PARTITIONS_WITH_CARE_MAP: 555 continue 556 557 verity_block_device = "{}_verity_block_device".format(partition) 558 avb_hashtree_enable = "avb_{}_hashtree_enable".format(partition) 559 if (verity_block_device in OPTIONS.info_dict or 560 OPTIONS.info_dict.get(avb_hashtree_enable) == "true"): 561 image_path = image_paths[partition] 562 assert os.path.exists(image_path) 563 care_map_list += GetCareMap(partition, image_path) 564 565 if care_map_list: 566 care_map_path = "META/care_map.txt" 567 if output_zip and care_map_path not in output_zip.namelist(): 568 common.ZipWriteStr(output_zip, care_map_path, '\n'.join(care_map_list)) 569 else: 570 with open(os.path.join(OPTIONS.input_tmp, care_map_path), 'w') as fp: 571 fp.write('\n'.join(care_map_list)) 572 if output_zip: 573 OPTIONS.replace_updated_files_list.append(care_map_path) 574 575 576 def AddPackRadioImages(output_zip, images): 577 """Copies images listed in META/pack_radioimages.txt from RADIO/ to IMAGES/. 578 579 Args: 580 output_zip: The output zip file (needs to be already open), or None to 581 write images to OPTIONS.input_tmp/. 582 images: A list of image names. 583 584 Raises: 585 AssertionError: If a listed image can't be found. 586 """ 587 for image in images: 588 img_name = image.strip() 589 _, ext = os.path.splitext(img_name) 590 if not ext: 591 img_name += ".img" 592 593 prebuilt_path = os.path.join(OPTIONS.input_tmp, "IMAGES", img_name) 594 if os.path.exists(prebuilt_path): 595 print("%s already exists, no need to overwrite..." % (img_name,)) 596 continue 597 598 img_radio_path = os.path.join(OPTIONS.input_tmp, "RADIO", img_name) 599 assert os.path.exists(img_radio_path), \ 600 "Failed to find %s at %s" % (img_name, img_radio_path) 601 602 if output_zip: 603 common.ZipWrite(output_zip, img_radio_path, "IMAGES/" + img_name) 604 else: 605 shutil.copy(img_radio_path, prebuilt_path) 606 607 608 def ReplaceUpdatedFiles(zip_filename, files_list): 609 """Updates all the ZIP entries listed in files_list. 610 611 For now the list includes META/care_map.txt, and the related files under 612 SYSTEM/ after rebuilding recovery. 613 """ 614 common.ZipDelete(zip_filename, files_list) 615 output_zip = zipfile.ZipFile(zip_filename, "a", 616 compression=zipfile.ZIP_DEFLATED, 617 allowZip64=True) 618 for item in files_list: 619 file_path = os.path.join(OPTIONS.input_tmp, item) 620 assert os.path.exists(file_path) 621 common.ZipWrite(output_zip, file_path, arcname=item) 622 common.ZipClose(output_zip) 623 624 625 def AddImagesToTargetFiles(filename): 626 """Creates and adds images (boot/recovery/system/...) to a target_files.zip. 627 628 It works with either a zip file (zip mode), or a directory that contains the 629 files to be packed into a target_files.zip (dir mode). The latter is used when 630 being called from build/make/core/Makefile. 631 632 The images will be created under IMAGES/ in the input target_files.zip. 633 634 Args: 635 filename: the target_files.zip, or the zip root directory. 636 """ 637 if os.path.isdir(filename): 638 OPTIONS.input_tmp = os.path.abspath(filename) 639 else: 640 OPTIONS.input_tmp = common.UnzipTemp(filename) 641 642 if not OPTIONS.add_missing: 643 if os.path.isdir(os.path.join(OPTIONS.input_tmp, "IMAGES")): 644 print("target_files appears to already contain images.") 645 sys.exit(1) 646 647 OPTIONS.info_dict = common.LoadInfoDict(OPTIONS.input_tmp, OPTIONS.input_tmp) 648 649 has_recovery = OPTIONS.info_dict.get("no_recovery") != "true" 650 651 # {vendor,product}.img is unlike system.img or system_other.img. Because it 652 # could be built from source, or dropped into target_files.zip as a prebuilt 653 # blob. We consider either of them as {vendor,product}.img being available, 654 # which could be used when generating vbmeta.img for AVB. 655 has_vendor = (os.path.isdir(os.path.join(OPTIONS.input_tmp, "VENDOR")) or 656 os.path.exists(os.path.join(OPTIONS.input_tmp, "IMAGES", 657 "vendor.img"))) 658 has_product = (os.path.isdir(os.path.join(OPTIONS.input_tmp, "PRODUCT")) or 659 os.path.exists(os.path.join(OPTIONS.input_tmp, "IMAGES", 660 "product.img"))) 661 has_system_other = os.path.isdir(os.path.join(OPTIONS.input_tmp, 662 "SYSTEM_OTHER")) 663 664 # Set up the output destination. It writes to the given directory for dir 665 # mode; otherwise appends to the given ZIP. 666 if os.path.isdir(filename): 667 output_zip = None 668 else: 669 output_zip = zipfile.ZipFile(filename, "a", 670 compression=zipfile.ZIP_DEFLATED, 671 allowZip64=True) 672 673 # Always make input_tmp/IMAGES available, since we may stage boot / recovery 674 # images there even under zip mode. The directory will be cleaned up as part 675 # of OPTIONS.input_tmp. 676 images_dir = os.path.join(OPTIONS.input_tmp, "IMAGES") 677 if not os.path.isdir(images_dir): 678 os.makedirs(images_dir) 679 680 # A map between partition names and their paths, which could be used when 681 # generating AVB vbmeta image. 682 partitions = dict() 683 684 def banner(s): 685 print("\n\n++++ " + s + " ++++\n\n") 686 687 banner("boot") 688 # common.GetBootableImage() returns the image directly if present. 689 boot_image = common.GetBootableImage( 690 "IMAGES/boot.img", "boot.img", OPTIONS.input_tmp, "BOOT") 691 # boot.img may be unavailable in some targets (e.g. aosp_arm64). 692 if boot_image: 693 partitions['boot'] = os.path.join(OPTIONS.input_tmp, "IMAGES", "boot.img") 694 if not os.path.exists(partitions['boot']): 695 boot_image.WriteToDir(OPTIONS.input_tmp) 696 if output_zip: 697 boot_image.AddToZip(output_zip) 698 699 recovery_image = None 700 if has_recovery: 701 banner("recovery") 702 recovery_image = common.GetBootableImage( 703 "IMAGES/recovery.img", "recovery.img", OPTIONS.input_tmp, "RECOVERY") 704 assert recovery_image, "Failed to create recovery.img." 705 partitions['recovery'] = os.path.join( 706 OPTIONS.input_tmp, "IMAGES", "recovery.img") 707 if not os.path.exists(partitions['recovery']): 708 recovery_image.WriteToDir(OPTIONS.input_tmp) 709 if output_zip: 710 recovery_image.AddToZip(output_zip) 711 712 banner("recovery (two-step image)") 713 # The special recovery.img for two-step package use. 714 recovery_two_step_image = common.GetBootableImage( 715 "IMAGES/recovery-two-step.img", "recovery-two-step.img", 716 OPTIONS.input_tmp, "RECOVERY", two_step_image=True) 717 assert recovery_two_step_image, "Failed to create recovery-two-step.img." 718 recovery_two_step_image_path = os.path.join( 719 OPTIONS.input_tmp, "IMAGES", "recovery-two-step.img") 720 if not os.path.exists(recovery_two_step_image_path): 721 recovery_two_step_image.WriteToDir(OPTIONS.input_tmp) 722 if output_zip: 723 recovery_two_step_image.AddToZip(output_zip) 724 725 banner("system") 726 partitions['system'] = AddSystem( 727 output_zip, recovery_img=recovery_image, boot_img=boot_image) 728 729 if has_vendor: 730 banner("vendor") 731 partitions['vendor'] = AddVendor(output_zip) 732 733 if has_product: 734 banner("product") 735 partitions['product'] = AddProduct(output_zip) 736 737 if has_system_other: 738 banner("system_other") 739 AddSystemOther(output_zip) 740 741 if not OPTIONS.is_signing: 742 banner("userdata") 743 AddUserdata(output_zip) 744 banner("cache") 745 AddCache(output_zip) 746 747 if OPTIONS.info_dict.get("board_bpt_enable") == "true": 748 banner("partition-table") 749 AddPartitionTable(output_zip) 750 751 if OPTIONS.info_dict.get("has_dtbo") == "true": 752 banner("dtbo") 753 partitions['dtbo'] = AddDtbo(output_zip) 754 755 if OPTIONS.info_dict.get("avb_enable") == "true": 756 banner("vbmeta") 757 AddVBMeta(output_zip, partitions) 758 759 banner("radio") 760 ab_partitions_txt = os.path.join(OPTIONS.input_tmp, "META", 761 "ab_partitions.txt") 762 if os.path.exists(ab_partitions_txt): 763 with open(ab_partitions_txt, 'r') as f: 764 ab_partitions = f.readlines() 765 766 # For devices using A/B update, copy over images from RADIO/ and/or 767 # VENDOR_IMAGES/ to IMAGES/ and make sure we have all the needed 768 # images ready under IMAGES/. All images should have '.img' as extension. 769 AddRadioImagesForAbOta(output_zip, ab_partitions) 770 771 # Generate care_map.txt for system and vendor partitions (if present), then 772 # write this file to target_files package. 773 AddCareMapTxtForAbOta(output_zip, ab_partitions, partitions) 774 775 # Radio images that need to be packed into IMAGES/, and product-img.zip. 776 pack_radioimages_txt = os.path.join( 777 OPTIONS.input_tmp, "META", "pack_radioimages.txt") 778 if os.path.exists(pack_radioimages_txt): 779 with open(pack_radioimages_txt, 'r') as f: 780 AddPackRadioImages(output_zip, f.readlines()) 781 782 if output_zip: 783 common.ZipClose(output_zip) 784 if OPTIONS.replace_updated_files_list: 785 ReplaceUpdatedFiles(output_zip.filename, 786 OPTIONS.replace_updated_files_list) 787 788 789 def main(argv): 790 def option_handler(o, a): 791 if o in ("-a", "--add_missing"): 792 OPTIONS.add_missing = True 793 elif o in ("-r", "--rebuild_recovery",): 794 OPTIONS.rebuild_recovery = True 795 elif o == "--replace_verity_private_key": 796 OPTIONS.replace_verity_private_key = (True, a) 797 elif o == "--replace_verity_public_key": 798 OPTIONS.replace_verity_public_key = (True, a) 799 elif o == "--is_signing": 800 OPTIONS.is_signing = True 801 else: 802 return False 803 return True 804 805 args = common.ParseOptions( 806 argv, __doc__, extra_opts="ar", 807 extra_long_opts=["add_missing", "rebuild_recovery", 808 "replace_verity_public_key=", 809 "replace_verity_private_key=", 810 "is_signing"], 811 extra_option_handler=option_handler) 812 813 814 if len(args) != 1: 815 common.Usage(__doc__) 816 sys.exit(1) 817 818 AddImagesToTargetFiles(args[0]) 819 print("done.") 820 821 if __name__ == '__main__': 822 try: 823 common.CloseInheritedPipes() 824 main(sys.argv[1:]) 825 except common.ExternalError as e: 826 print("\n ERROR: %s\n" % (e,)) 827 sys.exit(1) 828 finally: 829 common.Cleanup() 830