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