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 an input target-files, produces an image zipfile suitable for use with 19'fastboot update'. 20 21Usage: img_from_target_files [flags] input_target_files output_image_zip 22 23input_target_files: Path to the input target_files zip. 24 25Flags: 26 -z (--bootable_zip) 27 Include only the bootable images (eg 'boot' and 'recovery') in 28 the output. 29 30 --additional <filespec> 31 Include an additional entry into the generated zip file. The filespec is 32 in a format that's accepted by zip2zip (e.g. 33 'OTA/android-info.txt:android-info.txt', to copy `OTA/android-info.txt` 34 from input_file into output_file as `android-info.txt`. Refer to the 35 `filespec` arg in zip2zip's help message). The option can be repeated to 36 include multiple entries. 37 38""" 39 40from __future__ import print_function 41 42import logging 43import os 44import sys 45import zipfile 46 47import common 48from build_super_image import BuildSuperImage 49 50if sys.hexversion < 0x02070000: 51 print('Python 2.7 or newer is required.', file=sys.stderr) 52 sys.exit(1) 53 54logger = logging.getLogger(__name__) 55 56OPTIONS = common.OPTIONS 57 58OPTIONS.additional_entries = [] 59OPTIONS.bootable_only = False 60OPTIONS.put_super = None 61OPTIONS.put_bootloader = None 62OPTIONS.dynamic_partition_list = None 63OPTIONS.super_device_list = None 64OPTIONS.retrofit_dap = None 65OPTIONS.build_super = None 66OPTIONS.sparse_userimages = None 67OPTIONS.use_fastboot_info = True 68OPTIONS.build_super_image = None 69 70 71def LoadOptions(input_file): 72 """Loads information from input_file to OPTIONS. 73 74 Args: 75 input_file: Path to the input target_files zip file. 76 """ 77 with zipfile.ZipFile(input_file) as input_zip: 78 info = OPTIONS.info_dict = common.LoadInfoDict(input_zip) 79 80 OPTIONS.put_super = info.get('super_image_in_update_package') == 'true' 81 OPTIONS.put_bootloader = info.get('bootloader_in_update_package') == 'true' 82 OPTIONS.dynamic_partition_list = info.get('dynamic_partition_list', 83 '').strip().split() 84 OPTIONS.super_device_list = info.get('super_block_devices', 85 '').strip().split() 86 OPTIONS.retrofit_dap = info.get('dynamic_partition_retrofit') == 'true' 87 OPTIONS.build_super = info.get('build_super_partition') == 'true' 88 OPTIONS.sparse_userimages = bool(info.get('extfs_sparse_flag')) 89 90 91def CopyZipEntries(input_file, output_file, entries): 92 """Copies ZIP entries between input and output files. 93 94 Args: 95 input_file: Path to the input target_files zip. 96 output_file: Output filename. 97 entries: A list of entries to copy, in a format that's accepted by zip2zip 98 (e.g. 'OTA/android-info.txt:android-info.txt', which copies 99 `OTA/android-info.txt` from input_file into output_file as 100 `android-info.txt`. Refer to the `filespec` arg in zip2zip's help 101 message). 102 """ 103 logger.info('Writing %d entries to archive...', len(entries)) 104 cmd = ['zip2zip', '-i', input_file, '-o', output_file] 105 cmd.extend(entries) 106 common.RunAndCheckOutput(cmd) 107 108 109def LocatePartitionEntry(partition_name, namelist): 110 for subdir in ["IMAGES", "PREBUILT_IMAGES", "RADIO"]: 111 entry_name = os.path.join(subdir, partition_name + ".img") 112 if entry_name in namelist: 113 return entry_name 114 115 116def EntriesForUserImages(input_file): 117 """Returns the user images entries to be copied. 118 119 Args: 120 input_file: Path to the input target_files zip file. 121 """ 122 dynamic_images = [p + '.img' for p in OPTIONS.dynamic_partition_list] 123 124 # Filter out system_other for launch DAP devices because it is in super image. 125 if not OPTIONS.retrofit_dap and 'system' in OPTIONS.dynamic_partition_list: 126 dynamic_images.append('system_other.img') 127 128 entries = [ 129 'OTA/android-info.txt:android-info.txt', 130 ] 131 if OPTIONS.use_fastboot_info: 132 entries.append('META/fastboot-info.txt:fastboot-info.txt') 133 ab_partitions = [] 134 with zipfile.ZipFile(input_file) as input_zip: 135 namelist = input_zip.namelist() 136 if "META/ab_partitions.txt" in namelist: 137 ab_partitions = input_zip.read( 138 "META/ab_partitions.txt").decode().strip().split() 139 if 'PREBUILT_IMAGES/kernel_16k' in namelist: 140 entries.append('PREBUILT_IMAGES/kernel_16k:kernel_16k') 141 if 'PREBUILT_IMAGES/ramdisk_16k.img' in namelist: 142 entries.append('PREBUILT_IMAGES/ramdisk_16k.img:ramdisk_16k.img') 143 144 visited_partitions = set(OPTIONS.dynamic_partition_list) 145 for image_path in [name for name in namelist if name.startswith('IMAGES/')]: 146 image = os.path.basename(image_path) 147 if OPTIONS.bootable_only and image not in ('boot.img', 'recovery.img', 'bootloader', 'init_boot.img'): 148 continue 149 if not image.endswith('.img') and image != 'bootloader': 150 continue 151 if image == 'bootloader' and not OPTIONS.put_bootloader: 152 continue 153 # Filter out super_empty and the images that are already in super partition. 154 if OPTIONS.put_super: 155 if image == 'super_empty.img': 156 continue 157 if image in dynamic_images: 158 continue 159 partition_name = image.rstrip(".img") 160 visited_partitions.add(partition_name) 161 entries.append('{}:{}'.format(image_path, image)) 162 for part in [part for part in ab_partitions if part not in visited_partitions]: 163 entry = LocatePartitionEntry(part, namelist) 164 image = os.path.basename(entry) 165 if entry is not None: 166 entries.append('{}:{}'.format(entry, image)) 167 return entries 168 169 170def EntriesForSplitSuperImages(input_file): 171 """Returns the entries for split super images. 172 173 This is only done for retrofit dynamic partition devices. 174 175 Args: 176 input_file: Path to the input target_files zip file. 177 """ 178 with zipfile.ZipFile(input_file) as input_zip: 179 namelist = input_zip.namelist() 180 entries = [] 181 for device in OPTIONS.super_device_list: 182 image = 'OTA/super_{}.img'.format(device) 183 assert image in namelist, 'Failed to find {}'.format(image) 184 entries.append('{}:{}'.format(image, os.path.basename(image))) 185 return entries 186 187 188def RebuildAndWriteSuperImages(input_file, output_file): 189 """Builds and writes super images to the output file.""" 190 logger.info('Building super image...') 191 192 # We need files under IMAGES/, OTA/, META/ for img_from_target_files.py. 193 # However, common.LoadInfoDict() may read additional files under BOOT/, 194 # RECOVERY/ and ROOT/. So unzip everything from the target_files.zip. 195 input_tmp = common.UnzipTemp(input_file) 196 197 super_file = common.MakeTempFile('super_', '.img') 198 199 # Allow overriding the BUILD_SUPER_IMAGE binary 200 if OPTIONS.build_super_image: 201 command = [OPTIONS.build_super_image, input_tmp, super_file] 202 common.RunAndCheckOutput(command) 203 else: 204 BuildSuperImage(input_tmp, super_file) 205 206 logger.info('Writing super.img to archive...') 207 with zipfile.ZipFile( 208 output_file, 'a', compression=zipfile.ZIP_DEFLATED, 209 allowZip64=True) as output_zip: 210 common.ZipWrite(output_zip, super_file, 'super.img') 211 212 213def ImgFromTargetFiles(input_file, output_file): 214 """Creates an image archive from the input target_files zip. 215 216 Args: 217 input_file: Path to the input target_files zip. 218 output_file: Output filename. 219 220 Raises: 221 ValueError: On invalid input. 222 """ 223 if not os.path.exists(input_file): 224 raise ValueError('%s is not exist' % input_file) 225 226 if not zipfile.is_zipfile(input_file): 227 raise ValueError('%s is not a valid zipfile' % input_file) 228 229 logger.info('Building image zip from target files zip.') 230 231 LoadOptions(input_file) 232 233 # Entries to be copied into the output file. 234 entries = EntriesForUserImages(input_file) 235 236 # Only for devices that retrofit dynamic partitions there're split super 237 # images available in the target_files.zip. 238 rebuild_super = False 239 if OPTIONS.build_super and OPTIONS.put_super: 240 if OPTIONS.retrofit_dap: 241 entries += EntriesForSplitSuperImages(input_file) 242 else: 243 rebuild_super = True 244 245 # Any additional entries provided by caller. 246 entries += OPTIONS.additional_entries 247 248 CopyZipEntries(input_file, output_file, entries) 249 250 if rebuild_super: 251 RebuildAndWriteSuperImages(input_file, output_file) 252 253 254def main(argv): 255 256 def option_handler(o, a): 257 if o in ('-z', '--bootable_zip'): 258 OPTIONS.bootable_only = True 259 elif o == '--additional': 260 OPTIONS.additional_entries.append(a) 261 elif o == '--build_super_image': 262 OPTIONS.build_super_image = a 263 else: 264 return False 265 return True 266 267 args = common.ParseOptions(argv, __doc__, 268 extra_opts='z', 269 extra_long_opts=[ 270 'additional=', 271 'bootable_zip', 272 'build_super_image=', 273 ], 274 extra_option_handler=option_handler) 275 if len(args) != 2: 276 common.Usage(__doc__) 277 sys.exit(1) 278 279 common.InitLogging() 280 281 ImgFromTargetFiles(args[0], args[1]) 282 283 logger.info('done.') 284 285 286if __name__ == '__main__': 287 try: 288 common.CloseInheritedPipes() 289 main(sys.argv[1:]) 290 finally: 291 common.Cleanup() 292