add_img_to_target_files.py revision cfa86223d62a6afa0eb8f5a1a215e985bb0a8c89
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 shutil 35import tempfile 36import zipfile 37 38import build_image 39import common 40import sparse_img 41 42OPTIONS = common.OPTIONS 43 44OPTIONS.add_missing = False 45OPTIONS.rebuild_recovery = False 46OPTIONS.replace_verity_public_key = False 47OPTIONS.replace_verity_private_key = False 48OPTIONS.verity_signer_path = None 49 50def 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 63def 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 90def 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 96def 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 108def 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 114def 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 131def 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 137def 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 191def 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 249def 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 291def 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 399def 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 431if __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