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 67 68 69def LoadOptions(input_file): 70 """Loads information from input_file to OPTIONS. 71 72 Args: 73 input_file: Path to the input target_files zip file. 74 """ 75 with zipfile.ZipFile(input_file) as input_zip: 76 info = OPTIONS.info_dict = common.LoadInfoDict(input_zip) 77 78 OPTIONS.put_super = info.get('super_image_in_update_package') == 'true' 79 OPTIONS.put_bootloader = info.get('bootloader_in_update_package') == 'true' 80 OPTIONS.dynamic_partition_list = info.get('dynamic_partition_list', 81 '').strip().split() 82 OPTIONS.super_device_list = info.get('super_block_devices', 83 '').strip().split() 84 OPTIONS.retrofit_dap = info.get('dynamic_partition_retrofit') == 'true' 85 OPTIONS.build_super = info.get('build_super_partition') == 'true' 86 OPTIONS.sparse_userimages = bool(info.get('extfs_sparse_flag')) 87 88 89def CopyZipEntries(input_file, output_file, entries): 90 """Copies ZIP entries between input and output files. 91 92 Args: 93 input_file: Path to the input target_files zip. 94 output_file: Output filename. 95 entries: A list of entries to copy, in a format that's accepted by zip2zip 96 (e.g. 'OTA/android-info.txt:android-info.txt', which copies 97 `OTA/android-info.txt` from input_file into output_file as 98 `android-info.txt`. Refer to the `filespec` arg in zip2zip's help 99 message). 100 """ 101 logger.info('Writing %d entries to archive...', len(entries)) 102 cmd = ['zip2zip', '-i', input_file, '-o', output_file] 103 cmd.extend(entries) 104 common.RunAndCheckOutput(cmd) 105 106 107def EntriesForUserImages(input_file): 108 """Returns the user images entries to be copied. 109 110 Args: 111 input_file: Path to the input target_files zip file. 112 """ 113 dynamic_images = [p + '.img' for p in OPTIONS.dynamic_partition_list] 114 115 # Filter out system_other for launch DAP devices because it is in super image. 116 if not OPTIONS.retrofit_dap and 'system' in OPTIONS.dynamic_partition_list: 117 dynamic_images.append('system_other.img') 118 119 entries = [ 120 'OTA/android-info.txt:android-info.txt', 121 ] 122 with zipfile.ZipFile(input_file) as input_zip: 123 namelist = input_zip.namelist() 124 125 for image_path in [name for name in namelist if name.startswith('IMAGES/')]: 126 image = os.path.basename(image_path) 127 if OPTIONS.bootable_only and image not in('boot.img', 'recovery.img', 'bootloader'): 128 continue 129 if not image.endswith('.img') and image != 'bootloader': 130 continue 131 if image == 'bootloader' and not OPTIONS.put_bootloader: 132 continue 133 # Filter out super_empty and the images that are already in super partition. 134 if OPTIONS.put_super: 135 if image == 'super_empty.img': 136 continue 137 if image in dynamic_images: 138 continue 139 entries.append('{}:{}'.format(image_path, image)) 140 return entries 141 142 143def EntriesForSplitSuperImages(input_file): 144 """Returns the entries for split super images. 145 146 This is only done for retrofit dynamic partition devices. 147 148 Args: 149 input_file: Path to the input target_files zip file. 150 """ 151 with zipfile.ZipFile(input_file) as input_zip: 152 namelist = input_zip.namelist() 153 entries = [] 154 for device in OPTIONS.super_device_list: 155 image = 'OTA/super_{}.img'.format(device) 156 assert image in namelist, 'Failed to find {}'.format(image) 157 entries.append('{}:{}'.format(image, os.path.basename(image))) 158 return entries 159 160 161def RebuildAndWriteSuperImages(input_file, output_file): 162 """Builds and writes super images to the output file.""" 163 logger.info('Building super image...') 164 165 # We need files under IMAGES/, OTA/, META/ for img_from_target_files.py. 166 # However, common.LoadInfoDict() may read additional files under BOOT/, 167 # RECOVERY/ and ROOT/. So unzip everything from the target_files.zip. 168 input_tmp = common.UnzipTemp(input_file) 169 170 super_file = common.MakeTempFile('super_', '.img') 171 BuildSuperImage(input_tmp, super_file) 172 173 logger.info('Writing super.img to archive...') 174 with zipfile.ZipFile( 175 output_file, 'a', compression=zipfile.ZIP_DEFLATED, 176 allowZip64=not OPTIONS.sparse_userimages) as output_zip: 177 common.ZipWrite(output_zip, super_file, 'super.img') 178 179 180def ImgFromTargetFiles(input_file, output_file): 181 """Creates an image archive from the input target_files zip. 182 183 Args: 184 input_file: Path to the input target_files zip. 185 output_file: Output filename. 186 187 Raises: 188 ValueError: On invalid input. 189 """ 190 if not os.path.exists(input_file): 191 raise ValueError('%s is not exist' % input_file) 192 193 if not zipfile.is_zipfile(input_file): 194 raise ValueError('%s is not a valid zipfile' % input_file) 195 196 logger.info('Building image zip from target files zip.') 197 198 LoadOptions(input_file) 199 200 # Entries to be copied into the output file. 201 entries = EntriesForUserImages(input_file) 202 203 # Only for devices that retrofit dynamic partitions there're split super 204 # images available in the target_files.zip. 205 rebuild_super = False 206 if OPTIONS.build_super and OPTIONS.put_super: 207 if OPTIONS.retrofit_dap: 208 entries += EntriesForSplitSuperImages(input_file) 209 else: 210 rebuild_super = True 211 212 # Any additional entries provided by caller. 213 entries += OPTIONS.additional_entries 214 215 CopyZipEntries(input_file, output_file, entries) 216 217 if rebuild_super: 218 RebuildAndWriteSuperImages(input_file, output_file) 219 220 221def main(argv): 222 223 def option_handler(o, a): 224 if o in ('-z', '--bootable_zip'): 225 OPTIONS.bootable_only = True 226 elif o == '--additional': 227 OPTIONS.additional_entries.append(a) 228 else: 229 return False 230 return True 231 232 args = common.ParseOptions(argv, __doc__, 233 extra_opts='z', 234 extra_long_opts=[ 235 'additional=', 236 'bootable_zip', 237 ], 238 extra_option_handler=option_handler) 239 if len(args) != 2: 240 common.Usage(__doc__) 241 sys.exit(1) 242 243 common.InitLogging() 244 245 ImgFromTargetFiles(args[0], args[1]) 246 247 logger.info('done.') 248 249 250if __name__ == '__main__': 251 try: 252 common.CloseInheritedPipes() 253 main(sys.argv[1:]) 254 except common.ExternalError as e: 255 logger.exception('\n ERROR:\n') 256 sys.exit(1) 257 finally: 258 common.Cleanup() 259