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