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