add_img_to_target_files.py revision d995f4b04df46d9e9ac1be8d58ca961e94b783d1
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 AddCache(output_zip, prefix="IMAGES/"): 232 """Create an empty cache image and store it in output_zip.""" 233 234 prebuilt_path = os.path.join(OPTIONS.input_tmp, prefix, "cache.img") 235 if os.path.exists(prebuilt_path): 236 print "cache.img already exists in %s, no need to rebuild..." % (prefix,) 237 return 238 239 image_props = build_image.ImagePropFromGlobalDict(OPTIONS.info_dict, "cache") 240 # The build system has to explicitly request for cache.img. 241 if "fs_type" not in image_props: 242 return 243 244 print "creating cache.img..." 245 246 # Use a fixed timestamp (01/01/2009) when packaging the image. 247 # Bug: 24377993 248 epoch = datetime.datetime.fromtimestamp(0) 249 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds() 250 image_props["timestamp"] = int(timestamp) 251 252 # The name of the directory it is making an image out of matters to 253 # mkyaffs2image. So we create a temp dir, and within it we create an 254 # empty dir named "cache", and build the image from that. 255 temp_dir = tempfile.mkdtemp() 256 user_dir = os.path.join(temp_dir, "cache") 257 os.mkdir(user_dir) 258 img = tempfile.NamedTemporaryFile() 259 260 fstab = OPTIONS.info_dict["fstab"] 261 if fstab: 262 image_props["fs_type"] = fstab["/cache"].fs_type 263 succ = build_image.BuildImage(user_dir, image_props, img.name) 264 assert succ, "build cache.img image failed" 265 266 common.CheckSize(img.name, "cache.img", OPTIONS.info_dict) 267 common.ZipWrite(output_zip, img.name, prefix + "cache.img") 268 img.close() 269 os.rmdir(user_dir) 270 os.rmdir(temp_dir) 271 272 273def AddImagesToTargetFiles(filename): 274 OPTIONS.input_tmp, input_zip = common.UnzipTemp(filename) 275 276 if not OPTIONS.add_missing: 277 for n in input_zip.namelist(): 278 if n.startswith("IMAGES/"): 279 print "target_files appears to already contain images." 280 sys.exit(1) 281 282 try: 283 input_zip.getinfo("VENDOR/") 284 has_vendor = True 285 except KeyError: 286 has_vendor = False 287 288 OPTIONS.info_dict = common.LoadInfoDict(input_zip, OPTIONS.input_tmp) 289 290 common.ZipClose(input_zip) 291 output_zip = zipfile.ZipFile(filename, "a", 292 compression=zipfile.ZIP_DEFLATED) 293 294 has_recovery = (OPTIONS.info_dict.get("no_recovery") != "true") 295 system_root_image = (OPTIONS.info_dict.get("system_root_image", None) == "true") 296 board_bvb_enable = (OPTIONS.info_dict.get("board_bvb_enable", None) == "true") 297 298 # Brillo Verified Boot is incompatible with certain 299 # configurations. Explicitly check for these. 300 if board_bvb_enable: 301 assert not has_recovery, "has_recovery incompatible with bvb" 302 assert not system_root_image, "system_root_image incompatible with bvb" 303 assert not OPTIONS.rebuild_recovery, "rebuild_recovery incompatible with bvb" 304 assert not has_vendor, "VENDOR images currently incompatible with bvb" 305 306 def banner(s): 307 print "\n\n++++ " + s + " ++++\n\n" 308 309 prebuilt_path = os.path.join(OPTIONS.input_tmp, "IMAGES", "boot.img") 310 boot_image = None 311 if os.path.exists(prebuilt_path): 312 banner("boot") 313 print "boot.img already exists in IMAGES/, no need to rebuild..." 314 if OPTIONS.rebuild_recovery: 315 boot_image = common.GetBootableImage( 316 "IMAGES/boot.img", "boot.img", OPTIONS.input_tmp, "BOOT") 317 else: 318 if board_bvb_enable: 319 # With Brillo Verified Boot, we need to build system.img before 320 # boot.img since the latter includes the dm-verity root hash and 321 # salt for the former. 322 pass 323 else: 324 banner("boot") 325 boot_image = common.GetBootableImage( 326 "IMAGES/boot.img", "boot.img", OPTIONS.input_tmp, "BOOT") 327 if boot_image: 328 boot_image.AddToZip(output_zip) 329 330 recovery_image = None 331 if has_recovery: 332 banner("recovery") 333 prebuilt_path = os.path.join(OPTIONS.input_tmp, "IMAGES", "recovery.img") 334 if os.path.exists(prebuilt_path): 335 print "recovery.img already exists in IMAGES/, no need to rebuild..." 336 if OPTIONS.rebuild_recovery: 337 recovery_image = common.GetBootableImage( 338 "IMAGES/recovery.img", "recovery.img", OPTIONS.input_tmp, 339 "RECOVERY") 340 else: 341 recovery_image = common.GetBootableImage( 342 "IMAGES/recovery.img", "recovery.img", OPTIONS.input_tmp, "RECOVERY") 343 if recovery_image: 344 recovery_image.AddToZip(output_zip) 345 346 banner("system") 347 system_img_path = AddSystem( 348 output_zip, recovery_img=recovery_image, boot_img=boot_image) 349 if OPTIONS.info_dict.get("board_bvb_enable", None) == "true": 350 # If we're using Brillo Verified Boot, we can now build boot.img 351 # given that we have system.img. 352 banner("boot") 353 boot_image = common.GetBootableImage( 354 "IMAGES/boot.img", "boot.img", OPTIONS.input_tmp, "BOOT", 355 system_img_path=system_img_path) 356 if boot_image: 357 boot_image.AddToZip(output_zip) 358 if has_vendor: 359 banner("vendor") 360 AddVendor(output_zip) 361 banner("userdata") 362 AddUserdata(output_zip) 363 banner("cache") 364 AddCache(output_zip) 365 366 # For devices using A/B update, copy over images from RADIO/ to IMAGES/ and 367 # make sure we have all the needed images ready under IMAGES/. 368 ab_partitions = os.path.join(OPTIONS.input_tmp, "META", "ab_partitions.txt") 369 if os.path.exists(ab_partitions): 370 with open(ab_partitions, 'r') as f: 371 lines = f.readlines() 372 for line in lines: 373 img_name = line.strip() + ".img" 374 img_radio_path = os.path.join(OPTIONS.input_tmp, "RADIO", img_name) 375 if os.path.exists(img_radio_path): 376 common.ZipWrite(output_zip, img_radio_path, 377 os.path.join("IMAGES", img_name)) 378 379 # Zip spec says: All slashes MUST be forward slashes. 380 img_path = 'IMAGES/' + img_name 381 assert img_path in output_zip.namelist(), "cannot find " + img_name 382 383 common.ZipClose(output_zip) 384 385def main(argv): 386 def option_handler(o, a): 387 if o in ("-a", "--add_missing"): 388 OPTIONS.add_missing = True 389 elif o in ("-r", "--rebuild_recovery",): 390 OPTIONS.rebuild_recovery = True 391 elif o == "--replace_verity_private_key": 392 OPTIONS.replace_verity_private_key = (True, a) 393 elif o == "--replace_verity_public_key": 394 OPTIONS.replace_verity_public_key = (True, a) 395 elif o == "--verity_signer_path": 396 OPTIONS.verity_signer_path = a 397 else: 398 return False 399 return True 400 401 args = common.ParseOptions( 402 argv, __doc__, extra_opts="ar", 403 extra_long_opts=["add_missing", "rebuild_recovery", 404 "replace_verity_public_key=", 405 "replace_verity_private_key=", 406 "verity_signer_path="], 407 extra_option_handler=option_handler) 408 409 410 if len(args) != 1: 411 common.Usage(__doc__) 412 sys.exit(1) 413 414 AddImagesToTargetFiles(args[0]) 415 print "done." 416 417if __name__ == '__main__': 418 try: 419 common.CloseInheritedPipes() 420 main(sys.argv[1:]) 421 except common.ExternalError as e: 422 print 423 print " ERROR: %s" % (e,) 424 print 425 sys.exit(1) 426 finally: 427 common.Cleanup() 428