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