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