add_img_to_target_files revision 94a41630ef53df6572a10b0f7b36c935be0477df
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. 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 --verity_signer_path 46 Specify the signer path to build verity metadata. 47""" 48 49import sys 50 51if sys.hexversion < 0x02070000: 52 print >> sys.stderr, "Python 2.7 or newer is required." 53 sys.exit(1) 54 55import datetime 56import errno 57import os 58import shlex 59import shutil 60import subprocess 61import tempfile 62import zipfile 63 64import build_image 65import common 66import sparse_img 67 68OPTIONS = common.OPTIONS 69 70OPTIONS.add_missing = False 71OPTIONS.rebuild_recovery = False 72OPTIONS.replace_verity_public_key = False 73OPTIONS.replace_verity_private_key = False 74OPTIONS.is_signing = False 75OPTIONS.verity_signer_path = None 76 77def GetCareMap(which, imgname): 78 """Generate care_map of system (or vendor) partition""" 79 80 assert which in ("system", "vendor") 81 _, blk_device = common.GetTypeAndDevice("/" + which, OPTIONS.info_dict) 82 83 simg = sparse_img.SparseImage(imgname) 84 care_map_list = [] 85 care_map_list.append(blk_device) 86 care_map_list.append(simg.care_map.to_string_raw()) 87 return care_map_list 88 89 90def AddSystem(output_zip, prefix="IMAGES/", recovery_img=None, boot_img=None): 91 """Turn the contents of SYSTEM into a system image and store it in 92 output_zip. Returns the name of the system image file.""" 93 94 prebuilt_path = os.path.join(OPTIONS.input_tmp, prefix, "system.img") 95 if os.path.exists(prebuilt_path): 96 print "system.img already exists in %s, no need to rebuild..." % (prefix,) 97 return prebuilt_path 98 99 def output_sink(fn, data): 100 ofile = open(os.path.join(OPTIONS.input_tmp, "SYSTEM", fn), "w") 101 ofile.write(data) 102 ofile.close() 103 104 if OPTIONS.rebuild_recovery: 105 print "Building new recovery patch" 106 common.MakeRecoveryPatch(OPTIONS.input_tmp, output_sink, recovery_img, 107 boot_img, info_dict=OPTIONS.info_dict) 108 109 block_list = common.MakeTempFile(prefix="system-blocklist-", suffix=".map") 110 imgname = BuildSystem(OPTIONS.input_tmp, OPTIONS.info_dict, 111 block_list=block_list) 112 113 # If requested, calculate and add dm-verity integrity hashes and 114 # metadata to system.img. 115 if OPTIONS.info_dict.get("board_bvb_enable", None) == "true": 116 bvbtool = os.getenv('BVBTOOL') or "bvbtool" 117 cmd = [bvbtool, "add_image_hashes", "--image", imgname] 118 args = OPTIONS.info_dict.get("board_bvb_add_image_hashes_args", None) 119 if args and args.strip(): 120 cmd.extend(shlex.split(args)) 121 p = common.Run(cmd, stdout=subprocess.PIPE) 122 p.communicate() 123 assert p.returncode == 0, "bvbtool add_image_hashes of %s image failed" % ( 124 os.path.basename(OPTIONS.input_tmp),) 125 126 common.ZipWrite(output_zip, imgname, prefix + "system.img") 127 common.ZipWrite(output_zip, block_list, prefix + "system.map") 128 return imgname 129 130 131def BuildSystem(input_dir, info_dict, block_list=None): 132 """Build the (sparse) system image and return the name of a temp 133 file containing it.""" 134 return CreateImage(input_dir, info_dict, "system", block_list=block_list) 135 136 137def AddSystemOther(output_zip, prefix="IMAGES/"): 138 """Turn the contents of SYSTEM_OTHER into a system_other image 139 and store it in output_zip.""" 140 141 prebuilt_path = os.path.join(OPTIONS.input_tmp, prefix, "system_other.img") 142 if os.path.exists(prebuilt_path): 143 print "system_other.img already exists in %s, no need to rebuild..." % (prefix,) 144 return 145 146 imgname = BuildSystemOther(OPTIONS.input_tmp, OPTIONS.info_dict) 147 common.ZipWrite(output_zip, imgname, prefix + "system_other.img") 148 149def BuildSystemOther(input_dir, info_dict): 150 """Build the (sparse) system_other image and return the name of a temp 151 file containing it.""" 152 return CreateImage(input_dir, info_dict, "system_other", block_list=None) 153 154 155def AddVendor(output_zip, prefix="IMAGES/"): 156 """Turn the contents of VENDOR into a vendor image and store in it 157 output_zip.""" 158 159 prebuilt_path = os.path.join(OPTIONS.input_tmp, prefix, "vendor.img") 160 if os.path.exists(prebuilt_path): 161 print "vendor.img already exists in %s, no need to rebuild..." % (prefix,) 162 return prebuilt_path 163 164 block_list = common.MakeTempFile(prefix="vendor-blocklist-", suffix=".map") 165 imgname = BuildVendor(OPTIONS.input_tmp, OPTIONS.info_dict, 166 block_list=block_list) 167 common.ZipWrite(output_zip, imgname, prefix + "vendor.img") 168 common.ZipWrite(output_zip, block_list, prefix + "vendor.map") 169 return imgname 170 171 172def BuildVendor(input_dir, info_dict, block_list=None): 173 """Build the (sparse) vendor image and return the name of a temp 174 file containing it.""" 175 return CreateImage(input_dir, info_dict, "vendor", block_list=block_list) 176 177 178def CreateImage(input_dir, info_dict, what, block_list=None): 179 print "creating " + what + ".img..." 180 181 img = common.MakeTempFile(prefix=what + "-", suffix=".img") 182 183 # The name of the directory it is making an image out of matters to 184 # mkyaffs2image. It wants "system" but we have a directory named 185 # "SYSTEM", so create a symlink. 186 try: 187 os.symlink(os.path.join(input_dir, what.upper()), 188 os.path.join(input_dir, what)) 189 except OSError as e: 190 # bogus error on my mac version? 191 # File "./build/tools/releasetools/img_from_target_files" 192 # os.path.join(OPTIONS.input_tmp, "system")) 193 # OSError: [Errno 17] File exists 194 if e.errno == errno.EEXIST: 195 pass 196 197 image_props = build_image.ImagePropFromGlobalDict(info_dict, what) 198 fstab = info_dict["fstab"] 199 mount_point = "/" + what 200 if fstab and mount_point in fstab: 201 image_props["fs_type"] = fstab[mount_point].fs_type 202 203 # Use a fixed timestamp (01/01/2009) when packaging the image. 204 # Bug: 24377993 205 epoch = datetime.datetime.fromtimestamp(0) 206 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds() 207 image_props["timestamp"] = int(timestamp) 208 209 if what == "system": 210 fs_config_prefix = "" 211 else: 212 fs_config_prefix = what + "_" 213 214 fs_config = os.path.join( 215 input_dir, "META/" + fs_config_prefix + "filesystem_config.txt") 216 if not os.path.exists(fs_config): 217 fs_config = None 218 219 # Override values loaded from info_dict. 220 if fs_config: 221 image_props["fs_config"] = fs_config 222 if block_list: 223 image_props["block_list"] = block_list 224 225 succ = build_image.BuildImage(os.path.join(input_dir, what), 226 image_props, img) 227 assert succ, "build " + what + ".img image failed" 228 229 return img 230 231 232def AddUserdata(output_zip, prefix="IMAGES/"): 233 """Create a userdata image and store it in output_zip. 234 235 In most case we just create and store an empty userdata.img; 236 But the invoker can also request to create userdata.img with real 237 data from the target files, by setting "userdata_img_with_data=true" 238 in OPTIONS.info_dict. 239 """ 240 241 prebuilt_path = os.path.join(OPTIONS.input_tmp, prefix, "userdata.img") 242 if os.path.exists(prebuilt_path): 243 print "userdata.img already exists in %s, no need to rebuild..." % (prefix,) 244 return 245 246 # Skip userdata.img if no size. 247 image_props = build_image.ImagePropFromGlobalDict(OPTIONS.info_dict, "data") 248 if not image_props.get("partition_size"): 249 return 250 251 print "creating userdata.img..." 252 253 # Use a fixed timestamp (01/01/2009) when packaging the image. 254 # Bug: 24377993 255 epoch = datetime.datetime.fromtimestamp(0) 256 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds() 257 image_props["timestamp"] = int(timestamp) 258 259 # The name of the directory it is making an image out of matters to 260 # mkyaffs2image. So we create a temp dir, and within it we create an 261 # empty dir named "data", or a symlink to the DATA dir, 262 # and build the image from that. 263 temp_dir = tempfile.mkdtemp() 264 user_dir = os.path.join(temp_dir, "data") 265 empty = (OPTIONS.info_dict.get("userdata_img_with_data") != "true") 266 if empty: 267 # Create an empty dir. 268 os.mkdir(user_dir) 269 else: 270 # Symlink to the DATA dir. 271 os.symlink(os.path.join(OPTIONS.input_tmp, "DATA"), 272 user_dir) 273 274 img = tempfile.NamedTemporaryFile() 275 276 fstab = OPTIONS.info_dict["fstab"] 277 if fstab: 278 image_props["fs_type"] = fstab["/data"].fs_type 279 succ = build_image.BuildImage(user_dir, image_props, img.name) 280 assert succ, "build userdata.img image failed" 281 282 common.CheckSize(img.name, "userdata.img", OPTIONS.info_dict) 283 common.ZipWrite(output_zip, img.name, prefix + "userdata.img") 284 img.close() 285 shutil.rmtree(temp_dir) 286 287 288def AddPartitionTable(output_zip, prefix="IMAGES/"): 289 """Create a partition table image and store it in output_zip.""" 290 291 _, img_file_name = tempfile.mkstemp() 292 _, bpt_file_name = tempfile.mkstemp() 293 294 # use BPTTOOL from environ, or "bpttool" if empty or not set. 295 bpttool = os.getenv("BPTTOOL") or "bpttool" 296 cmd = [bpttool, "make_table", "--output_json", bpt_file_name, 297 "--output_gpt", img_file_name] 298 input_files_str = OPTIONS.info_dict["board_bpt_input_files"] 299 input_files = input_files_str.split(" ") 300 for i in input_files: 301 cmd.extend(["--input", i]) 302 disk_size = OPTIONS.info_dict.get("board_bpt_disk_size") 303 if disk_size: 304 cmd.extend(["--disk_size", disk_size]) 305 args = OPTIONS.info_dict.get("board_bpt_make_table_args") 306 if args: 307 cmd.extend(shlex.split(args)) 308 309 p = common.Run(cmd, stdout=subprocess.PIPE) 310 p.communicate() 311 assert p.returncode == 0, "bpttool make_table failed" 312 313 common.ZipWrite(output_zip, img_file_name, prefix + "partition-table.img") 314 common.ZipWrite(output_zip, bpt_file_name, prefix + "partition-table.bpt") 315 316 317def AddCache(output_zip, prefix="IMAGES/"): 318 """Create an empty cache image and store it in output_zip.""" 319 320 prebuilt_path = os.path.join(OPTIONS.input_tmp, prefix, "cache.img") 321 if os.path.exists(prebuilt_path): 322 print "cache.img already exists in %s, no need to rebuild..." % (prefix,) 323 return 324 325 image_props = build_image.ImagePropFromGlobalDict(OPTIONS.info_dict, "cache") 326 # The build system has to explicitly request for cache.img. 327 if "fs_type" not in image_props: 328 return 329 330 print "creating cache.img..." 331 332 # Use a fixed timestamp (01/01/2009) when packaging the image. 333 # Bug: 24377993 334 epoch = datetime.datetime.fromtimestamp(0) 335 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds() 336 image_props["timestamp"] = int(timestamp) 337 338 # The name of the directory it is making an image out of matters to 339 # mkyaffs2image. So we create a temp dir, and within it we create an 340 # empty dir named "cache", and build the image from that. 341 temp_dir = tempfile.mkdtemp() 342 user_dir = os.path.join(temp_dir, "cache") 343 os.mkdir(user_dir) 344 img = tempfile.NamedTemporaryFile() 345 346 fstab = OPTIONS.info_dict["fstab"] 347 if fstab: 348 image_props["fs_type"] = fstab["/cache"].fs_type 349 succ = build_image.BuildImage(user_dir, image_props, img.name) 350 assert succ, "build cache.img image failed" 351 352 common.CheckSize(img.name, "cache.img", OPTIONS.info_dict) 353 common.ZipWrite(output_zip, img.name, prefix + "cache.img") 354 img.close() 355 os.rmdir(user_dir) 356 os.rmdir(temp_dir) 357 358 359def AddImagesToTargetFiles(filename): 360 OPTIONS.input_tmp, input_zip = common.UnzipTemp(filename) 361 362 if not OPTIONS.add_missing: 363 for n in input_zip.namelist(): 364 if n.startswith("IMAGES/"): 365 print "target_files appears to already contain images." 366 sys.exit(1) 367 368 try: 369 input_zip.getinfo("VENDOR/") 370 has_vendor = True 371 except KeyError: 372 has_vendor = False 373 374 has_system_other = "SYSTEM_OTHER/" in input_zip.namelist() 375 376 OPTIONS.info_dict = common.LoadInfoDict(input_zip, OPTIONS.input_tmp) 377 378 common.ZipClose(input_zip) 379 output_zip = zipfile.ZipFile(filename, "a", 380 compression=zipfile.ZIP_DEFLATED, 381 allowZip64=True) 382 383 has_recovery = (OPTIONS.info_dict.get("no_recovery") != "true") 384 system_root_image = (OPTIONS.info_dict.get("system_root_image", None) == "true") 385 board_bvb_enable = (OPTIONS.info_dict.get("board_bvb_enable", None) == "true") 386 387 # Brillo Verified Boot is incompatible with certain 388 # configurations. Explicitly check for these. 389 if board_bvb_enable: 390 assert not has_recovery, "has_recovery incompatible with bvb" 391 assert not system_root_image, "system_root_image incompatible with bvb" 392 assert not OPTIONS.rebuild_recovery, "rebuild_recovery incompatible with bvb" 393 assert not has_vendor, "VENDOR images currently incompatible with bvb" 394 395 def banner(s): 396 print "\n\n++++ " + s + " ++++\n\n" 397 398 prebuilt_path = os.path.join(OPTIONS.input_tmp, "IMAGES", "boot.img") 399 boot_image = None 400 if os.path.exists(prebuilt_path): 401 banner("boot") 402 print "boot.img already exists in IMAGES/, no need to rebuild..." 403 if OPTIONS.rebuild_recovery: 404 boot_image = common.GetBootableImage( 405 "IMAGES/boot.img", "boot.img", OPTIONS.input_tmp, "BOOT") 406 else: 407 if board_bvb_enable: 408 # With Brillo Verified Boot, we need to build system.img before 409 # boot.img since the latter includes the dm-verity root hash and 410 # salt for the former. 411 pass 412 else: 413 banner("boot") 414 boot_image = common.GetBootableImage( 415 "IMAGES/boot.img", "boot.img", OPTIONS.input_tmp, "BOOT") 416 if boot_image: 417 boot_image.AddToZip(output_zip) 418 419 recovery_image = None 420 if has_recovery: 421 banner("recovery") 422 prebuilt_path = os.path.join(OPTIONS.input_tmp, "IMAGES", "recovery.img") 423 if os.path.exists(prebuilt_path): 424 print "recovery.img already exists in IMAGES/, no need to rebuild..." 425 if OPTIONS.rebuild_recovery: 426 recovery_image = common.GetBootableImage( 427 "IMAGES/recovery.img", "recovery.img", OPTIONS.input_tmp, 428 "RECOVERY") 429 else: 430 recovery_image = common.GetBootableImage( 431 "IMAGES/recovery.img", "recovery.img", OPTIONS.input_tmp, "RECOVERY") 432 if recovery_image: 433 recovery_image.AddToZip(output_zip) 434 435 banner("system") 436 system_img_path = AddSystem( 437 output_zip, recovery_img=recovery_image, boot_img=boot_image) 438 if OPTIONS.info_dict.get("board_bvb_enable", None) == "true": 439 # If we're using Brillo Verified Boot, we can now build boot.img 440 # given that we have system.img. 441 banner("boot") 442 boot_image = common.GetBootableImage( 443 "IMAGES/boot.img", "boot.img", OPTIONS.input_tmp, "BOOT", 444 system_img_path=system_img_path) 445 if boot_image: 446 boot_image.AddToZip(output_zip) 447 vendor_img_path = None 448 if has_vendor: 449 banner("vendor") 450 vendor_img_path = AddVendor(output_zip) 451 if has_system_other: 452 banner("system_other") 453 AddSystemOther(output_zip) 454 if not OPTIONS.is_signing: 455 banner("userdata") 456 AddUserdata(output_zip) 457 banner("cache") 458 AddCache(output_zip) 459 if OPTIONS.info_dict.get("board_bpt_enable", None) == "true": 460 banner("partition-table") 461 AddPartitionTable(output_zip) 462 463 # For devices using A/B update, copy over images from RADIO/ and/or 464 # VENDOR_IMAGES/ to IMAGES/ and make sure we have all the needed 465 # images ready under IMAGES/. All images should have '.img' as extension. 466 banner("radio") 467 ab_partitions = os.path.join(OPTIONS.input_tmp, "META", "ab_partitions.txt") 468 if os.path.exists(ab_partitions): 469 with open(ab_partitions, 'r') as f: 470 lines = f.readlines() 471 # For devices using A/B update, generate care_map for system and vendor 472 # partitions (if present), then write this file to target_files package. 473 care_map_list = [] 474 for line in lines: 475 if line.strip() == "system" and OPTIONS.info_dict.get( 476 "system_verity_block_device", None) is not None: 477 assert os.path.exists(system_img_path) 478 care_map_list += GetCareMap("system", system_img_path) 479 if line.strip() == "vendor" and OPTIONS.info_dict.get( 480 "vendor_verity_block_device", None) is not None: 481 assert os.path.exists(vendor_img_path) 482 care_map_list += GetCareMap("vendor", vendor_img_path) 483 484 img_name = line.strip() + ".img" 485 prebuilt_path = os.path.join(OPTIONS.input_tmp, "IMAGES", img_name) 486 if os.path.exists(prebuilt_path): 487 print "%s already exists, no need to overwrite..." % (img_name,) 488 continue 489 490 img_radio_path = os.path.join(OPTIONS.input_tmp, "RADIO", img_name) 491 img_vendor_dir = os.path.join( 492 OPTIONS.input_tmp, "VENDOR_IMAGES") 493 if os.path.exists(img_radio_path): 494 common.ZipWrite(output_zip, img_radio_path, 495 os.path.join("IMAGES", img_name)) 496 else: 497 for root, _, files in os.walk(img_vendor_dir): 498 if img_name in files: 499 common.ZipWrite(output_zip, os.path.join(root, img_name), 500 os.path.join("IMAGES", img_name)) 501 break 502 503 # Zip spec says: All slashes MUST be forward slashes. 504 img_path = 'IMAGES/' + img_name 505 assert img_path in output_zip.namelist(), "cannot find " + img_name 506 507 if care_map_list: 508 file_path = "META/care_map.txt" 509 common.ZipWriteStr(output_zip, file_path, '\n'.join(care_map_list)) 510 511 common.ZipClose(output_zip) 512 513def main(argv): 514 def option_handler(o, a): 515 if o in ("-a", "--add_missing"): 516 OPTIONS.add_missing = True 517 elif o in ("-r", "--rebuild_recovery",): 518 OPTIONS.rebuild_recovery = True 519 elif o == "--replace_verity_private_key": 520 OPTIONS.replace_verity_private_key = (True, a) 521 elif o == "--replace_verity_public_key": 522 OPTIONS.replace_verity_public_key = (True, a) 523 elif o == "--is_signing": 524 OPTIONS.is_signing = True 525 elif o == "--verity_signer_path": 526 OPTIONS.verity_signer_path = a 527 else: 528 return False 529 return True 530 531 args = common.ParseOptions( 532 argv, __doc__, extra_opts="ar", 533 extra_long_opts=["add_missing", "rebuild_recovery", 534 "replace_verity_public_key=", 535 "replace_verity_private_key=", 536 "is_signing", 537 "verity_signer_path="], 538 extra_option_handler=option_handler) 539 540 541 if len(args) != 1: 542 common.Usage(__doc__) 543 sys.exit(1) 544 545 AddImagesToTargetFiles(args[0]) 546 print "done." 547 548if __name__ == '__main__': 549 try: 550 common.CloseInheritedPipes() 551 main(sys.argv[1:]) 552 except common.ExternalError as e: 553 print 554 print " ERROR: %s" % (e,) 555 print 556 sys.exit(1) 557 finally: 558 common.Cleanup() 559