1#!/usr/bin/env python 2# 3# Copyright (C) 2008 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, produces an image zipfile suitable for 19use with 'fastboot update'. 20 21Usage: img_from_target_files [flags] input_target_files output_image_zip 22 23 -z (--bootable_zip) 24 Include only the bootable images (eg 'boot' and 'recovery') in 25 the output. 26 27""" 28 29from __future__ import print_function 30 31import logging 32import os 33import shutil 34import sys 35import zipfile 36 37import common 38from build_super_image import BuildSuperImage 39 40if sys.hexversion < 0x02070000: 41 print("Python 2.7 or newer is required.", file=sys.stderr) 42 sys.exit(1) 43 44logger = logging.getLogger(__name__) 45 46OPTIONS = common.OPTIONS 47 48 49def LoadOptions(input_file): 50 """ 51 Load information from input_file to OPTIONS. 52 53 Args: 54 input_file: A Zipfile instance of input zip file, or path to the directory 55 of extracted zip. 56 """ 57 info = OPTIONS.info_dict = common.LoadInfoDict(input_file) 58 59 OPTIONS.put_super = info.get("super_image_in_update_package") == "true" 60 OPTIONS.dynamic_partition_list = info.get("dynamic_partition_list", 61 "").strip().split() 62 OPTIONS.super_device_list = info.get("super_block_devices", 63 "").strip().split() 64 OPTIONS.retrofit_dap = info.get("dynamic_partition_retrofit") == "true" 65 OPTIONS.build_super = info.get("build_super_partition") == "true" 66 OPTIONS.sparse_userimages = bool(info.get("extfs_sparse_flag")) 67 68 69def CopyInfo(input_tmp, output_zip): 70 """Copy the android-info.txt file from the input to the output.""" 71 common.ZipWrite( 72 output_zip, os.path.join(input_tmp, "OTA", "android-info.txt"), 73 "android-info.txt") 74 75 76def CopyUserImages(input_tmp, output_zip): 77 """ 78 Copy user images from the unzipped input and write to output_zip. 79 80 Args: 81 input_tmp: path to the unzipped input. 82 output_zip: a ZipFile instance to write images to. 83 """ 84 dynamic_images = [p + ".img" for p in OPTIONS.dynamic_partition_list] 85 86 # Filter out system_other for launch DAP devices because it is in super image. 87 if not OPTIONS.retrofit_dap and "system" in OPTIONS.dynamic_partition_list: 88 dynamic_images.append("system_other.img") 89 90 images_path = os.path.join(input_tmp, "IMAGES") 91 # A target-files zip must contain the images since Lollipop. 92 assert os.path.exists(images_path) 93 for image in sorted(os.listdir(images_path)): 94 if OPTIONS.bootable_only and image not in ("boot.img", "recovery.img"): 95 continue 96 if not image.endswith(".img"): 97 continue 98 if image == "recovery-two-step.img": 99 continue 100 if OPTIONS.put_super: 101 if image == "super_empty.img": 102 continue 103 if image in dynamic_images: 104 continue 105 logger.info("writing %s to archive...", os.path.join("IMAGES", image)) 106 common.ZipWrite(output_zip, os.path.join(images_path, image), image) 107 108 109def WriteSuperImages(input_tmp, output_zip): 110 """ 111 Write super images from the unzipped input and write to output_zip. This is 112 only done if super_image_in_update_package is set to "true". 113 114 - For retrofit dynamic partition devices, copy split super images from target 115 files package. 116 - For devices launched with dynamic partitions, build super image from target 117 files package. 118 119 Args: 120 input_tmp: path to the unzipped input. 121 output_zip: a ZipFile instance to write images to. 122 """ 123 if not OPTIONS.build_super or not OPTIONS.put_super: 124 return 125 126 if OPTIONS.retrofit_dap: 127 # retrofit devices already have split super images under OTA/ 128 images_path = os.path.join(input_tmp, "OTA") 129 for device in OPTIONS.super_device_list: 130 image = "super_%s.img" % device 131 image_path = os.path.join(images_path, image) 132 assert os.path.exists(image_path) 133 logger.info("writing %s to archive...", os.path.join("OTA", image)) 134 common.ZipWrite(output_zip, image_path, image) 135 else: 136 # super image for non-retrofit devices aren't in target files package, 137 # so build it. 138 super_file = common.MakeTempFile("super_", ".img") 139 logger.info("building super image %s...", super_file) 140 BuildSuperImage(input_tmp, super_file) 141 logger.info("writing super.img to archive...") 142 common.ZipWrite(output_zip, super_file, "super.img") 143 144 145def main(argv): 146 # This allows modifying the value from inner function. 147 bootable_only_array = [False] 148 149 def option_handler(o, _): 150 if o in ("-z", "--bootable_zip"): 151 bootable_only_array[0] = True 152 else: 153 return False 154 return True 155 156 args = common.ParseOptions(argv, __doc__, 157 extra_opts="z", 158 extra_long_opts=["bootable_zip"], 159 extra_option_handler=option_handler) 160 161 OPTIONS.bootable_only = bootable_only_array[0] 162 163 if len(args) != 2: 164 common.Usage(__doc__) 165 sys.exit(1) 166 167 common.InitLogging() 168 169 # We need files under IMAGES/, OTA/, META/ for img_from_target_files.py. 170 # However, common.LoadInfoDict() may read additional files under BOOT/, 171 # RECOVERY/ and ROOT/. So unzip everything from the target_files.zip. 172 OPTIONS.input_tmp = common.UnzipTemp(args[0]) 173 LoadOptions(OPTIONS.input_tmp) 174 output_zip = zipfile.ZipFile(args[1], "w", compression=zipfile.ZIP_DEFLATED, 175 allowZip64=not OPTIONS.sparse_userimages) 176 177 try: 178 CopyInfo(OPTIONS.input_tmp, output_zip) 179 CopyUserImages(OPTIONS.input_tmp, output_zip) 180 WriteSuperImages(OPTIONS.input_tmp, output_zip) 181 finally: 182 logger.info("cleaning up...") 183 common.ZipClose(output_zip) 184 shutil.rmtree(OPTIONS.input_tmp) 185 186 logger.info("done.") 187 188 189if __name__ == '__main__': 190 try: 191 common.CloseInheritedPipes() 192 main(sys.argv[1:]) 193 except common.ExternalError as e: 194 logger.exception("\n ERROR:\n") 195 sys.exit(1) 196