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