add_img_to_target_files.py revision bea20ac722f60c29fc9475b76996eab85bbcac20
1#!/usr/bin/env python
2#
3# Copyright (C) 2014 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 a target-files zipfile that does not contain images (ie, does
19not have an IMAGES/ top-level subdirectory), produce the images and
20add them to the zipfile.
21
22Usage:  add_img_to_target_files [flag] target_files
23
24  -a  (--add_missing)
25      Build and add missing images to "IMAGES/". If this option is
26      not specified, this script will simply exit when "IMAGES/"
27      directory exists in the target file.
28
29  -r  (--rebuild_recovery)
30      Rebuild the recovery patch and write it to the system image. Only
31      meaningful when system image needs to be rebuilt.
32
33  --replace_verity_private_key
34      Replace the private key used for verity signing. (same as the option
35      in sign_target_files_apks)
36
37  --replace_verity_public_key
38       Replace the certificate (public key) used for verity verification. (same
39       as the option in sign_target_files_apks)
40
41  --is_signing
42      Skip building & adding the images for "userdata" and "cache" if we
43      are signing the target files.
44"""
45
46from __future__ import print_function
47
48import datetime
49import hashlib
50import os
51import shlex
52import shutil
53import subprocess
54import sys
55import tempfile
56import uuid
57import zipfile
58
59import build_image
60import common
61import rangelib
62import sparse_img
63
64if sys.hexversion < 0x02070000:
65  print("Python 2.7 or newer is required.", file=sys.stderr)
66  sys.exit(1)
67
68OPTIONS = common.OPTIONS
69
70OPTIONS.add_missing = False
71OPTIONS.rebuild_recovery = False
72OPTIONS.replace_updated_files_list = []
73OPTIONS.replace_verity_public_key = False
74OPTIONS.replace_verity_private_key = False
75OPTIONS.is_signing = False
76
77
78class OutputFile(object):
79  def __init__(self, output_zip, input_dir, prefix, name):
80    self._output_zip = output_zip
81    self.input_name = os.path.join(input_dir, prefix, name)
82
83    if self._output_zip:
84      self._zip_name = os.path.join(prefix, name)
85
86      root, suffix = os.path.splitext(name)
87      self.name = common.MakeTempFile(prefix=root + '-', suffix=suffix)
88    else:
89      self.name = self.input_name
90
91  def Write(self):
92    if self._output_zip:
93      common.ZipWrite(self._output_zip, self.name, self._zip_name)
94
95
96def GetCareMap(which, imgname):
97  """Generate care_map of system (or vendor) partition"""
98
99  assert which in ("system", "vendor")
100
101  simg = sparse_img.SparseImage(imgname)
102  care_map_list = [which]
103
104  care_map_ranges = simg.care_map
105  key = which + "_adjusted_partition_size"
106  adjusted_blocks = OPTIONS.info_dict.get(key)
107  if adjusted_blocks:
108    assert adjusted_blocks > 0, "blocks should be positive for " + which
109    care_map_ranges = care_map_ranges.intersect(rangelib.RangeSet(
110        "0-%d" % (adjusted_blocks,)))
111
112  care_map_list.append(care_map_ranges.to_string_raw())
113  return care_map_list
114
115
116def AddSystem(output_zip, prefix="IMAGES/", recovery_img=None, boot_img=None):
117  """Turn the contents of SYSTEM into a system image and store it in
118  output_zip. Returns the name of the system image file."""
119
120  img = OutputFile(output_zip, OPTIONS.input_tmp, prefix, "system.img")
121  if os.path.exists(img.input_name):
122    print("system.img already exists in %s, no need to rebuild..." % (prefix,))
123    return img.input_name
124
125  def output_sink(fn, data):
126    ofile = open(os.path.join(OPTIONS.input_tmp, "SYSTEM", fn), "w")
127    ofile.write(data)
128    ofile.close()
129
130    arc_name = "SYSTEM/" + fn
131    if arc_name in output_zip.namelist():
132      OPTIONS.replace_updated_files_list.append(arc_name)
133    else:
134      common.ZipWrite(output_zip, ofile.name, arc_name)
135
136  if OPTIONS.rebuild_recovery:
137    print("Building new recovery patch")
138    common.MakeRecoveryPatch(OPTIONS.input_tmp, output_sink, recovery_img,
139                             boot_img, info_dict=OPTIONS.info_dict)
140
141  block_list = OutputFile(output_zip, OPTIONS.input_tmp, prefix, "system.map")
142  CreateImage(OPTIONS.input_tmp, OPTIONS.info_dict, "system", img,
143              block_list=block_list)
144
145  return img.name
146
147
148def AddSystemOther(output_zip, prefix="IMAGES/"):
149  """Turn the contents of SYSTEM_OTHER into a system_other image
150  and store it in output_zip."""
151
152  img = OutputFile(output_zip, OPTIONS.input_tmp, prefix, "system_other.img")
153  if os.path.exists(img.input_name):
154    print("system_other.img already exists in %s, no need to rebuild..." % (
155        prefix,))
156    return
157
158  CreateImage(OPTIONS.input_tmp, OPTIONS.info_dict, "system_other", img)
159
160
161def AddVendor(output_zip, prefix="IMAGES/"):
162  """Turn the contents of VENDOR into a vendor image and store in it
163  output_zip."""
164
165  img = OutputFile(output_zip, OPTIONS.input_tmp, prefix, "vendor.img")
166  if os.path.exists(img.input_name):
167    print("vendor.img already exists in %s, no need to rebuild..." % (prefix,))
168    return img.input_name
169
170  block_list = OutputFile(output_zip, OPTIONS.input_tmp, prefix, "vendor.map")
171  CreateImage(OPTIONS.input_tmp, OPTIONS.info_dict, "vendor", img,
172              block_list=block_list)
173  return img.name
174
175
176def AddDtbo(output_zip, prefix="IMAGES/"):
177  """Adds the DTBO image.
178
179  Uses the image under prefix if it already exists. Otherwise looks for the
180  image under PREBUILT_IMAGES/, signs it as needed, and returns the image name.
181  """
182
183  img = OutputFile(output_zip, OPTIONS.input_tmp, prefix, "dtbo.img")
184  if os.path.exists(img.input_name):
185    print("dtbo.img already exists in %s, no need to rebuild..." % (prefix,))
186    return img.input_name
187
188  dtbo_prebuilt_path = os.path.join(
189      OPTIONS.input_tmp, "PREBUILT_IMAGES", "dtbo.img")
190  assert os.path.exists(dtbo_prebuilt_path)
191  shutil.copy(dtbo_prebuilt_path, img.name)
192
193  # AVB-sign the image as needed.
194  if OPTIONS.info_dict.get("avb_enable") == "true":
195    avbtool = os.getenv('AVBTOOL') or OPTIONS.info_dict["avb_avbtool"]
196    part_size = OPTIONS.info_dict["dtbo_size"]
197    # The AVB hash footer will be replaced if already present.
198    cmd = [avbtool, "add_hash_footer", "--image", img.name,
199           "--partition_size", str(part_size), "--partition_name", "dtbo"]
200    common.AppendAVBSigningArgs(cmd, "dtbo")
201    args = OPTIONS.info_dict.get("avb_dtbo_add_hash_footer_args")
202    if args and args.strip():
203      cmd.extend(shlex.split(args))
204    p = common.Run(cmd, stdout=subprocess.PIPE)
205    p.communicate()
206    assert p.returncode == 0, \
207        "avbtool add_hash_footer of %s failed" % (img.name,)
208
209  img.Write()
210  return img.name
211
212
213def CreateImage(input_dir, info_dict, what, output_file, block_list=None):
214  print("creating " + what + ".img...")
215
216  image_props = build_image.ImagePropFromGlobalDict(info_dict, what)
217  fstab = info_dict["fstab"]
218  mount_point = "/" + what
219  if fstab and mount_point in fstab:
220    image_props["fs_type"] = fstab[mount_point].fs_type
221
222  # Use a fixed timestamp (01/01/2009) when packaging the image.
223  # Bug: 24377993
224  epoch = datetime.datetime.fromtimestamp(0)
225  timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
226  image_props["timestamp"] = int(timestamp)
227
228  if what == "system":
229    fs_config_prefix = ""
230  else:
231    fs_config_prefix = what + "_"
232
233  fs_config = os.path.join(
234      input_dir, "META/" + fs_config_prefix + "filesystem_config.txt")
235  if not os.path.exists(fs_config):
236    fs_config = None
237
238  # Override values loaded from info_dict.
239  if fs_config:
240    image_props["fs_config"] = fs_config
241  if block_list:
242    image_props["block_list"] = block_list.name
243
244  # Use repeatable ext4 FS UUID and hash_seed UUID (based on partition name and
245  # build fingerprint).
246  uuid_seed = what + "-"
247  if "build.prop" in info_dict:
248    build_prop = info_dict["build.prop"]
249    if "ro.build.fingerprint" in build_prop:
250      uuid_seed += build_prop["ro.build.fingerprint"]
251    elif "ro.build.thumbprint" in build_prop:
252      uuid_seed += build_prop["ro.build.thumbprint"]
253  image_props["uuid"] = str(uuid.uuid5(uuid.NAMESPACE_URL, uuid_seed))
254  hash_seed = "hash_seed-" + uuid_seed
255  image_props["hash_seed"] = str(uuid.uuid5(uuid.NAMESPACE_URL, hash_seed))
256
257  succ = build_image.BuildImage(os.path.join(input_dir, what.upper()),
258                                image_props, output_file.name)
259  assert succ, "build " + what + ".img image failed"
260
261  output_file.Write()
262  if block_list:
263    block_list.Write()
264
265  # Set the 'adjusted_partition_size' that excludes the verity blocks of the
266  # given image. When avb is enabled, this size is the max image size returned
267  # by the avb tool.
268  is_verity_partition = "verity_block_device" in image_props
269  verity_supported = (image_props.get("verity") == "true" or
270                      image_props.get("avb_enable") == "true")
271  is_avb_enable = image_props.get("avb_hashtree_enable") == "true"
272  if verity_supported and (is_verity_partition or is_avb_enable):
273    adjusted_blocks_value = image_props.get("partition_size")
274    if adjusted_blocks_value:
275      adjusted_blocks_key = what + "_adjusted_partition_size"
276      info_dict[adjusted_blocks_key] = int(adjusted_blocks_value)/4096 - 1
277
278
279def AddUserdata(output_zip, prefix="IMAGES/"):
280  """Create a userdata image and store it in output_zip.
281
282  In most case we just create and store an empty userdata.img;
283  But the invoker can also request to create userdata.img with real
284  data from the target files, by setting "userdata_img_with_data=true"
285  in OPTIONS.info_dict.
286  """
287
288  img = OutputFile(output_zip, OPTIONS.input_tmp, prefix, "userdata.img")
289  if os.path.exists(img.input_name):
290    print("userdata.img already exists in %s, no need to rebuild..." % (
291        prefix,))
292    return
293
294  # Skip userdata.img if no size.
295  image_props = build_image.ImagePropFromGlobalDict(OPTIONS.info_dict, "data")
296  if not image_props.get("partition_size"):
297    return
298
299  print("creating userdata.img...")
300
301  # Use a fixed timestamp (01/01/2009) when packaging the image.
302  # Bug: 24377993
303  epoch = datetime.datetime.fromtimestamp(0)
304  timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
305  image_props["timestamp"] = int(timestamp)
306
307  if OPTIONS.info_dict.get("userdata_img_with_data") == "true":
308    user_dir = os.path.join(OPTIONS.input_tmp, "DATA")
309  else:
310    user_dir = common.MakeTempDir()
311
312  fstab = OPTIONS.info_dict["fstab"]
313  if fstab:
314    image_props["fs_type"] = fstab["/data"].fs_type
315  succ = build_image.BuildImage(user_dir, image_props, img.name)
316  assert succ, "build userdata.img image failed"
317
318  common.CheckSize(img.name, "userdata.img", OPTIONS.info_dict)
319  img.Write()
320
321
322def AppendVBMetaArgsForPartition(cmd, partition, img_path, public_key_dir):
323  if not img_path:
324    return
325
326  # Check if chain partition is used.
327  key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
328  if key_path:
329    # extract public key in AVB format to be included in vbmeta.img
330    avbtool = os.getenv('AVBTOOL') or OPTIONS.info_dict["avb_avbtool"]
331    public_key_path = os.path.join(public_key_dir, "%s.avbpubkey" % partition)
332    p = common.Run([avbtool, "extract_public_key", "--key", key_path,
333                    "--output", public_key_path],
334                   stdout=subprocess.PIPE, stderr=subprocess.PIPE)
335    p.communicate()
336    assert p.returncode == 0, \
337        "avbtool extract_public_key fail for partition: %r" % partition
338
339    rollback_index_location = OPTIONS.info_dict[
340        "avb_" + partition + "_rollback_index_location"]
341    cmd.extend(["--chain_partition", "%s:%s:%s" % (
342        partition, rollback_index_location, public_key_path)])
343  else:
344    cmd.extend(["--include_descriptors_from_image", img_path])
345
346
347def AddVBMeta(output_zip, partitions, prefix="IMAGES/"):
348  """Creates a VBMeta image and store it in output_zip.
349
350  Args:
351    output_zip: The output zip file, which needs to be already open.
352    partitions: A dict that's keyed by partition names with image paths as
353        values. Only valid partition names are accepted, which include 'boot',
354        'recovery', 'system', 'vendor', 'dtbo'.
355  """
356  img = OutputFile(output_zip, OPTIONS.input_tmp, prefix, "vbmeta.img")
357  if os.path.exists(img.input_name):
358    print("vbmeta.img already exists in %s; not rebuilding..." % (prefix,))
359    return img.input_name
360
361  avbtool = os.getenv('AVBTOOL') or OPTIONS.info_dict["avb_avbtool"]
362  cmd = [avbtool, "make_vbmeta_image", "--output", img.name]
363  common.AppendAVBSigningArgs(cmd, "vbmeta")
364
365  public_key_dir = common.MakeTempDir(prefix="avbpubkey-")
366  for partition, path in partitions.items():
367    assert partition in common.AVB_PARTITIONS, 'Unknown partition: %s' % (
368        partition,)
369    assert os.path.exists(path), 'Failed to find %s for partition %s' % (
370        path, partition)
371    AppendVBMetaArgsForPartition(cmd, partition, path, public_key_dir)
372
373  args = OPTIONS.info_dict.get("avb_vbmeta_args")
374  if args and args.strip():
375    split_args = shlex.split(args)
376    for index, arg in enumerate(split_args[:-1]):
377      # Sanity check that the image file exists. Some images might be defined
378      # as a path relative to source tree, which may not be available at the
379      # same location when running this script (we have the input target_files
380      # zip only). For such cases, we additionally scan other locations (e.g.
381      # IMAGES/, RADIO/, etc) before bailing out.
382      if arg == '--include_descriptors_from_image':
383        image_path = split_args[index + 1]
384        if os.path.exists(image_path):
385          continue
386        found = False
387        for dir_name in ['IMAGES', 'RADIO', 'VENDOR_IMAGES', 'PREBUILT_IMAGES']:
388          alt_path = os.path.join(
389              OPTIONS.input_tmp, dir_name, os.path.basename(image_path))
390          if os.path.exists(alt_path):
391            split_args[index + 1] = alt_path
392            found = True
393            break
394        assert found, 'failed to find %s' % (image_path,)
395    cmd.extend(split_args)
396
397  p = common.Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
398  p.communicate()
399  assert p.returncode == 0, "avbtool make_vbmeta_image failed"
400  img.Write()
401
402
403def AddPartitionTable(output_zip, prefix="IMAGES/"):
404  """Create a partition table image and store it in output_zip."""
405
406  img = OutputFile(output_zip, OPTIONS.input_tmp, prefix, "partition-table.img")
407  bpt = OutputFile(output_zip, OPTIONS.input_tmp, prefix, "partition-table.bpt")
408
409  # use BPTTOOL from environ, or "bpttool" if empty or not set.
410  bpttool = os.getenv("BPTTOOL") or "bpttool"
411  cmd = [bpttool, "make_table", "--output_json", bpt.name,
412         "--output_gpt", img.name]
413  input_files_str = OPTIONS.info_dict["board_bpt_input_files"]
414  input_files = input_files_str.split(" ")
415  for i in input_files:
416    cmd.extend(["--input", i])
417  disk_size = OPTIONS.info_dict.get("board_bpt_disk_size")
418  if disk_size:
419    cmd.extend(["--disk_size", disk_size])
420  args = OPTIONS.info_dict.get("board_bpt_make_table_args")
421  if args:
422    cmd.extend(shlex.split(args))
423
424  p = common.Run(cmd, stdout=subprocess.PIPE)
425  p.communicate()
426  assert p.returncode == 0, "bpttool make_table failed"
427
428  img.Write()
429  bpt.Write()
430
431
432def AddCache(output_zip, prefix="IMAGES/"):
433  """Create an empty cache image and store it in output_zip."""
434
435  img = OutputFile(output_zip, OPTIONS.input_tmp, prefix, "cache.img")
436  if os.path.exists(img.input_name):
437    print("cache.img already exists in %s, no need to rebuild..." % (prefix,))
438    return
439
440  image_props = build_image.ImagePropFromGlobalDict(OPTIONS.info_dict, "cache")
441  # The build system has to explicitly request for cache.img.
442  if "fs_type" not in image_props:
443    return
444
445  print("creating cache.img...")
446
447  # Use a fixed timestamp (01/01/2009) when packaging the image.
448  # Bug: 24377993
449  epoch = datetime.datetime.fromtimestamp(0)
450  timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
451  image_props["timestamp"] = int(timestamp)
452
453  user_dir = common.MakeTempDir()
454
455  fstab = OPTIONS.info_dict["fstab"]
456  if fstab:
457    image_props["fs_type"] = fstab["/cache"].fs_type
458  succ = build_image.BuildImage(user_dir, image_props, img.name)
459  assert succ, "build cache.img image failed"
460
461  common.CheckSize(img.name, "cache.img", OPTIONS.info_dict)
462  img.Write()
463
464
465def AddRadioImagesForAbOta(output_zip, ab_partitions):
466  """Adds the radio images needed for A/B OTA to the output file.
467
468  It parses the list of A/B partitions, looks for the missing ones from RADIO/
469  or VENDOR_IMAGES/ dirs, and copies them to IMAGES/ of the output file (or
470  dir).
471
472  It also ensures that on returning from the function all the listed A/B
473  partitions must have their images available under IMAGES/.
474
475  Args:
476    output_zip: The output zip file (needs to be already open), or None to
477        write images to OPTIONS.input_tmp/.
478    ab_partitions: The list of A/B partitions.
479
480  Raises:
481    AssertionError: If it can't find an image.
482  """
483  for partition in ab_partitions:
484    img_name = partition.strip() + ".img"
485    prebuilt_path = os.path.join(OPTIONS.input_tmp, "IMAGES", img_name)
486    if os.path.exists(prebuilt_path):
487      print("%s already exists, no need to overwrite..." % (img_name,))
488      continue
489
490    img_radio_path = os.path.join(OPTIONS.input_tmp, "RADIO", img_name)
491    if os.path.exists(img_radio_path):
492      if output_zip:
493        common.ZipWrite(output_zip, img_radio_path,
494                        os.path.join("IMAGES", img_name))
495      else:
496        shutil.copy(img_radio_path, prebuilt_path)
497    else:
498      img_vendor_dir = os.path.join(OPTIONS.input_tmp, "VENDOR_IMAGES")
499      for root, _, files in os.walk(img_vendor_dir):
500        if img_name in files:
501          if output_zip:
502            common.ZipWrite(output_zip, os.path.join(root, img_name),
503                            os.path.join("IMAGES", img_name))
504          else:
505            shutil.copy(os.path.join(root, img_name), prebuilt_path)
506          break
507
508    if output_zip:
509      # Zip spec says: All slashes MUST be forward slashes.
510      img_path = 'IMAGES/' + img_name
511      assert img_path in output_zip.namelist(), "cannot find " + img_name
512    else:
513      img_path = os.path.join(OPTIONS.input_tmp, "IMAGES", img_name)
514      assert os.path.exists(img_path), "cannot find " + img_name
515
516
517def AddCareMapTxtForAbOta(output_zip, ab_partitions, image_paths):
518  """Generates and adds care_map.txt for system and vendor partitions.
519
520  Args:
521    output_zip: The output zip file (needs to be already open), or None to
522        write images to OPTIONS.input_tmp/.
523    ab_partitions: The list of A/B partitions.
524    image_paths: A map from the partition name to the image path.
525  """
526  care_map_list = []
527  for partition in ab_partitions:
528    partition = partition.strip()
529    if (partition == "system" and
530        ("system_verity_block_device" in OPTIONS.info_dict or
531         OPTIONS.info_dict.get("avb_system_hashtree_enable") == "true")):
532      system_img_path = image_paths[partition]
533      assert os.path.exists(system_img_path)
534      care_map_list += GetCareMap("system", system_img_path)
535    if (partition == "vendor" and
536        ("vendor_verity_block_device" in OPTIONS.info_dict or
537         OPTIONS.info_dict.get("avb_vendor_hashtree_enable") == "true")):
538      vendor_img_path = image_paths[partition]
539      assert os.path.exists(vendor_img_path)
540      care_map_list += GetCareMap("vendor", vendor_img_path)
541
542  if care_map_list:
543    care_map_path = "META/care_map.txt"
544    if output_zip and care_map_path not in output_zip.namelist():
545      common.ZipWriteStr(output_zip, care_map_path, '\n'.join(care_map_list))
546    else:
547      with open(os.path.join(OPTIONS.input_tmp, care_map_path), 'w') as fp:
548        fp.write('\n'.join(care_map_list))
549      if output_zip:
550        OPTIONS.replace_updated_files_list.append(care_map_path)
551
552
553def AddPackRadioImages(output_zip, images):
554  """Copies images listed in META/pack_radioimages.txt from RADIO/ to IMAGES/.
555
556  Args:
557    output_zip: The output zip file (needs to be already open), or None to
558        write images to OPTIONS.input_tmp/.
559    images: A list of image names.
560
561  Raises:
562    AssertionError: If a listed image can't be found.
563  """
564  for image in images:
565    img_name = image.strip()
566    _, ext = os.path.splitext(img_name)
567    if not ext:
568      img_name += ".img"
569    prebuilt_path = os.path.join(OPTIONS.input_tmp, "IMAGES", img_name)
570    if os.path.exists(prebuilt_path):
571      print("%s already exists, no need to overwrite..." % (img_name,))
572      continue
573
574    img_radio_path = os.path.join(OPTIONS.input_tmp, "RADIO", img_name)
575    assert os.path.exists(img_radio_path), \
576        "Failed to find %s at %s" % (img_name, img_radio_path)
577    if output_zip:
578      common.ZipWrite(output_zip, img_radio_path,
579                      os.path.join("IMAGES", img_name))
580    else:
581      shutil.copy(img_radio_path, prebuilt_path)
582
583
584def ReplaceUpdatedFiles(zip_filename, files_list):
585  """Updates all the ZIP entries listed in files_list.
586
587  For now the list includes META/care_map.txt, and the related files under
588  SYSTEM/ after rebuilding recovery.
589  """
590  common.ZipDelete(zip_filename, files_list)
591  output_zip = zipfile.ZipFile(zip_filename, "a",
592                               compression=zipfile.ZIP_DEFLATED,
593                               allowZip64=True)
594  for item in files_list:
595    file_path = os.path.join(OPTIONS.input_tmp, item)
596    assert os.path.exists(file_path)
597    common.ZipWrite(output_zip, file_path, arcname=item)
598  common.ZipClose(output_zip)
599
600
601def AddImagesToTargetFiles(filename):
602  """Creates and adds images (boot/recovery/system/...) to a target_files.zip.
603
604  It works with either a zip file (zip mode), or a directory that contains the
605  files to be packed into a target_files.zip (dir mode). The latter is used when
606  being called from build/make/core/Makefile.
607
608  The images will be created under IMAGES/ in the input target_files.zip.
609
610  Args:
611      filename: the target_files.zip, or the zip root directory.
612  """
613  if os.path.isdir(filename):
614    OPTIONS.input_tmp = os.path.abspath(filename)
615    input_zip = None
616  else:
617    OPTIONS.input_tmp, input_zip = common.UnzipTemp(filename)
618
619  if not OPTIONS.add_missing:
620    if os.path.isdir(os.path.join(OPTIONS.input_tmp, "IMAGES")):
621      print("target_files appears to already contain images.")
622      sys.exit(1)
623
624  # vendor.img is unlike system.img or system_other.img. Because it could be
625  # built from source, or dropped into target_files.zip as a prebuilt blob. We
626  # consider either of them as vendor.img being available, which could be used
627  # when generating vbmeta.img for AVB.
628  has_vendor = (os.path.isdir(os.path.join(OPTIONS.input_tmp, "VENDOR")) or
629                os.path.exists(os.path.join(OPTIONS.input_tmp, "IMAGES",
630                                            "vendor.img")))
631  has_system_other = os.path.isdir(os.path.join(OPTIONS.input_tmp,
632                                                "SYSTEM_OTHER"))
633
634  if input_zip:
635    OPTIONS.info_dict = common.LoadInfoDict(input_zip, OPTIONS.input_tmp)
636
637    common.ZipClose(input_zip)
638    output_zip = zipfile.ZipFile(filename, "a",
639                                 compression=zipfile.ZIP_DEFLATED,
640                                 allowZip64=True)
641  else:
642    OPTIONS.info_dict = common.LoadInfoDict(filename, filename)
643    output_zip = None
644
645  # Always make input_tmp/IMAGES available, since we may stage boot / recovery
646  # images there even under zip mode. The directory will be cleaned up as part
647  # of OPTIONS.input_tmp.
648  images_dir = os.path.join(OPTIONS.input_tmp, "IMAGES")
649  if not os.path.isdir(images_dir):
650    os.makedirs(images_dir)
651
652  has_recovery = (OPTIONS.info_dict.get("no_recovery") != "true")
653
654  if OPTIONS.info_dict.get("avb_enable") == "true":
655    fp = None
656    if "build.prop" in OPTIONS.info_dict:
657      build_prop = OPTIONS.info_dict["build.prop"]
658      if "ro.build.fingerprint" in build_prop:
659        fp = build_prop["ro.build.fingerprint"]
660      elif "ro.build.thumbprint" in build_prop:
661        fp = build_prop["ro.build.thumbprint"]
662    if fp:
663      OPTIONS.info_dict["avb_salt"] = hashlib.sha256(fp).hexdigest()
664
665  # A map between partition names and their paths, which could be used when
666  # generating AVB vbmeta image.
667  partitions = dict()
668
669  def banner(s):
670    print("\n\n++++ " + s + " ++++\n\n")
671
672  banner("boot")
673  # common.GetBootableImage() returns the image directly if present.
674  boot_image = common.GetBootableImage(
675      "IMAGES/boot.img", "boot.img", OPTIONS.input_tmp, "BOOT")
676  # boot.img may be unavailable in some targets (e.g. aosp_arm64).
677  if boot_image:
678    partitions['boot'] = os.path.join(OPTIONS.input_tmp, "IMAGES", "boot.img")
679    if not os.path.exists(partitions['boot']):
680      boot_image.WriteToDir(OPTIONS.input_tmp)
681      if output_zip:
682        boot_image.AddToZip(output_zip)
683
684  recovery_image = None
685  if has_recovery:
686    banner("recovery")
687    recovery_image = common.GetBootableImage(
688        "IMAGES/recovery.img", "recovery.img", OPTIONS.input_tmp, "RECOVERY")
689    assert recovery_image, "Failed to create recovery.img."
690    partitions['recovery'] = os.path.join(
691        OPTIONS.input_tmp, "IMAGES", "recovery.img")
692    if not os.path.exists(partitions['recovery']):
693      recovery_image.WriteToDir(OPTIONS.input_tmp)
694      if output_zip:
695        recovery_image.AddToZip(output_zip)
696
697      banner("recovery (two-step image)")
698      # The special recovery.img for two-step package use.
699      recovery_two_step_image = common.GetBootableImage(
700          "IMAGES/recovery-two-step.img", "recovery-two-step.img",
701          OPTIONS.input_tmp, "RECOVERY", two_step_image=True)
702      assert recovery_two_step_image, "Failed to create recovery-two-step.img."
703      recovery_two_step_image_path = os.path.join(
704          OPTIONS.input_tmp, "IMAGES", "recovery-two-step.img")
705      if not os.path.exists(recovery_two_step_image_path):
706        recovery_two_step_image.WriteToDir(OPTIONS.input_tmp)
707        if output_zip:
708          recovery_two_step_image.AddToZip(output_zip)
709
710  banner("system")
711  partitions['system'] = AddSystem(
712      output_zip, recovery_img=recovery_image, boot_img=boot_image)
713
714  if has_vendor:
715    banner("vendor")
716    partitions['vendor'] = AddVendor(output_zip)
717
718  if has_system_other:
719    banner("system_other")
720    AddSystemOther(output_zip)
721
722  if not OPTIONS.is_signing:
723    banner("userdata")
724    AddUserdata(output_zip)
725    banner("cache")
726    AddCache(output_zip)
727
728  if OPTIONS.info_dict.get("board_bpt_enable") == "true":
729    banner("partition-table")
730    AddPartitionTable(output_zip)
731
732  if OPTIONS.info_dict.get("has_dtbo") == "true":
733    banner("dtbo")
734    partitions['dtbo'] = AddDtbo(output_zip)
735
736  if OPTIONS.info_dict.get("avb_enable") == "true":
737    banner("vbmeta")
738    AddVBMeta(output_zip, partitions)
739
740  banner("radio")
741  ab_partitions_txt = os.path.join(OPTIONS.input_tmp, "META",
742                                   "ab_partitions.txt")
743  if os.path.exists(ab_partitions_txt):
744    with open(ab_partitions_txt, 'r') as f:
745      ab_partitions = f.readlines()
746
747    # For devices using A/B update, copy over images from RADIO/ and/or
748    # VENDOR_IMAGES/ to IMAGES/ and make sure we have all the needed
749    # images ready under IMAGES/. All images should have '.img' as extension.
750    AddRadioImagesForAbOta(output_zip, ab_partitions)
751
752    # Generate care_map.txt for system and vendor partitions (if present), then
753    # write this file to target_files package.
754    AddCareMapTxtForAbOta(output_zip, ab_partitions, partitions)
755
756  # Radio images that need to be packed into IMAGES/, and product-img.zip.
757  pack_radioimages_txt = os.path.join(
758      OPTIONS.input_tmp, "META", "pack_radioimages.txt")
759  if os.path.exists(pack_radioimages_txt):
760    with open(pack_radioimages_txt, 'r') as f:
761      AddPackRadioImages(output_zip, f.readlines())
762
763  if output_zip:
764    common.ZipClose(output_zip)
765    if OPTIONS.replace_updated_files_list:
766      ReplaceUpdatedFiles(output_zip.filename,
767                          OPTIONS.replace_updated_files_list)
768
769
770def main(argv):
771  def option_handler(o, a):
772    if o in ("-a", "--add_missing"):
773      OPTIONS.add_missing = True
774    elif o in ("-r", "--rebuild_recovery",):
775      OPTIONS.rebuild_recovery = True
776    elif o == "--replace_verity_private_key":
777      OPTIONS.replace_verity_private_key = (True, a)
778    elif o == "--replace_verity_public_key":
779      OPTIONS.replace_verity_public_key = (True, a)
780    elif o == "--is_signing":
781      OPTIONS.is_signing = True
782    else:
783      return False
784    return True
785
786  args = common.ParseOptions(
787      argv, __doc__, extra_opts="ar",
788      extra_long_opts=["add_missing", "rebuild_recovery",
789                       "replace_verity_public_key=",
790                       "replace_verity_private_key=",
791                       "is_signing"],
792      extra_option_handler=option_handler)
793
794
795  if len(args) != 1:
796    common.Usage(__doc__)
797    sys.exit(1)
798
799  AddImagesToTargetFiles(args[0])
800  print("done.")
801
802if __name__ == '__main__':
803  try:
804    common.CloseInheritedPipes()
805    main(sys.argv[1:])
806  except common.ExternalError as e:
807    print("\n   ERROR: %s\n" % (e,))
808    sys.exit(1)
809  finally:
810    common.Cleanup()
811