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 and there're separate
32      boot / recovery images.
33
34  --replace_verity_private_key
35      Replace the private key used for verity signing. (same as the option
36      in sign_target_files_apks)
37
38  --replace_verity_public_key
39       Replace the certificate (public key) used for verity verification. (same
40       as the option in sign_target_files_apks)
41
42  --is_signing
43      Skip building & adding the images for "userdata" and "cache" if we
44      are signing the target files.
45"""
46
47from __future__ import print_function
48
49import datetime
50import logging
51import os
52import shlex
53import shutil
54import sys
55import uuid
56import zipfile
57
58import build_image
59import build_super_image
60import common
61import rangelib
62import sparse_img
63import verity_utils
64
65if sys.hexversion < 0x02070000:
66  print("Python 2.7 or newer is required.", file=sys.stderr)
67  sys.exit(1)
68
69logger = logging.getLogger(__name__)
70
71OPTIONS = common.OPTIONS
72OPTIONS.add_missing = False
73OPTIONS.rebuild_recovery = False
74OPTIONS.replace_updated_files_list = []
75OPTIONS.replace_verity_public_key = False
76OPTIONS.replace_verity_private_key = False
77OPTIONS.is_signing = False
78
79# Use a fixed timestamp (01/01/2009 00:00:00 UTC) for files when packaging
80# images. (b/24377993, b/80600931)
81FIXED_FILE_TIMESTAMP = int((
82    datetime.datetime(2009, 1, 1, 0, 0, 0, 0, None) -
83    datetime.datetime.utcfromtimestamp(0)).total_seconds())
84
85
86class OutputFile(object):
87  """A helper class to write a generated file to the given dir or zip.
88
89  When generating images, we want the outputs to go into the given zip file, or
90  the given dir.
91
92  Attributes:
93    name: The name of the output file, regardless of the final destination.
94  """
95
96  def __init__(self, output_zip, input_dir, prefix, name):
97    # We write the intermediate output file under the given input_dir, even if
98    # the final destination is a zip archive.
99    self.name = os.path.join(input_dir, prefix, name)
100    self._output_zip = output_zip
101    if self._output_zip:
102      self._zip_name = os.path.join(prefix, name)
103
104  def Write(self):
105    if self._output_zip:
106      common.ZipWrite(self._output_zip, self.name, self._zip_name)
107
108
109def GetCareMap(which, imgname):
110  """Returns the care_map string for the given partition.
111
112  Args:
113    which: The partition name, must be listed in PARTITIONS_WITH_CARE_MAP.
114    imgname: The filename of the image.
115
116  Returns:
117    (which, care_map_ranges): care_map_ranges is the raw string of the care_map
118    RangeSet; or None.
119  """
120  assert which in common.PARTITIONS_WITH_CARE_MAP
121
122  # which + "_image_size" contains the size that the actual filesystem image
123  # resides in, which is all that needs to be verified. The additional blocks in
124  # the image file contain verity metadata, by reading which would trigger
125  # invalid reads.
126  image_size = OPTIONS.info_dict.get(which + "_image_size")
127  if not image_size:
128    return None
129
130  image_blocks = int(image_size) // 4096 - 1
131  assert image_blocks > 0, "blocks for {} must be positive".format(which)
132
133  # For sparse images, we will only check the blocks that are listed in the care
134  # map, i.e. the ones with meaningful data.
135  if "extfs_sparse_flag" in OPTIONS.info_dict:
136    simg = sparse_img.SparseImage(imgname)
137    care_map_ranges = simg.care_map.intersect(
138        rangelib.RangeSet("0-{}".format(image_blocks)))
139
140  # Otherwise for non-sparse images, we read all the blocks in the filesystem
141  # image.
142  else:
143    care_map_ranges = rangelib.RangeSet("0-{}".format(image_blocks))
144
145  return [which, care_map_ranges.to_string_raw()]
146
147
148def AddSystem(output_zip, recovery_img=None, boot_img=None):
149  """Turn the contents of SYSTEM into a system image and store it in
150  output_zip. Returns the name of the system image file."""
151
152  img = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "system.img")
153  if os.path.exists(img.name):
154    logger.info("system.img already exists; no need to rebuild...")
155    return img.name
156
157  def output_sink(fn, data):
158    output_file = os.path.join(OPTIONS.input_tmp, "SYSTEM", fn)
159    with open(output_file, "wb") as ofile:
160      ofile.write(data)
161
162    if output_zip:
163      arc_name = "SYSTEM/" + fn
164      if arc_name in output_zip.namelist():
165        OPTIONS.replace_updated_files_list.append(arc_name)
166      else:
167        common.ZipWrite(output_zip, output_file, arc_name)
168
169  board_uses_vendorimage = OPTIONS.info_dict.get(
170      "board_uses_vendorimage") == "true"
171
172  if (OPTIONS.rebuild_recovery and not board_uses_vendorimage and
173      recovery_img is not None and boot_img is not None):
174    logger.info("Building new recovery patch on system at system/vendor")
175    common.MakeRecoveryPatch(OPTIONS.input_tmp, output_sink, recovery_img,
176                             boot_img, info_dict=OPTIONS.info_dict)
177
178  block_list = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "system.map")
179  CreateImage(OPTIONS.input_tmp, OPTIONS.info_dict, "system", img,
180              block_list=block_list)
181
182  return img.name
183
184
185def AddSystemOther(output_zip):
186  """Turn the contents of SYSTEM_OTHER into a system_other image
187  and store it in output_zip."""
188
189  img = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "system_other.img")
190  if os.path.exists(img.name):
191    logger.info("system_other.img already exists; no need to rebuild...")
192    return
193
194  CreateImage(OPTIONS.input_tmp, OPTIONS.info_dict, "system_other", img)
195
196
197def AddVendor(output_zip, recovery_img=None, boot_img=None):
198  """Turn the contents of VENDOR into a vendor image and store in it
199  output_zip."""
200
201  img = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "vendor.img")
202  if os.path.exists(img.name):
203    logger.info("vendor.img already exists; no need to rebuild...")
204    return img.name
205
206  def output_sink(fn, data):
207    ofile = open(os.path.join(OPTIONS.input_tmp, "VENDOR", fn), "w")
208    ofile.write(data)
209    ofile.close()
210
211    if output_zip:
212      arc_name = "VENDOR/" + fn
213      if arc_name in output_zip.namelist():
214        OPTIONS.replace_updated_files_list.append(arc_name)
215      else:
216        common.ZipWrite(output_zip, ofile.name, arc_name)
217
218  board_uses_vendorimage = OPTIONS.info_dict.get(
219      "board_uses_vendorimage") == "true"
220
221  if (OPTIONS.rebuild_recovery and board_uses_vendorimage and
222      recovery_img is not None and boot_img is not None):
223    logger.info("Building new recovery patch on vendor")
224    common.MakeRecoveryPatch(OPTIONS.input_tmp, output_sink, recovery_img,
225                             boot_img, info_dict=OPTIONS.info_dict)
226
227  block_list = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "vendor.map")
228  CreateImage(OPTIONS.input_tmp, OPTIONS.info_dict, "vendor", img,
229              block_list=block_list)
230  return img.name
231
232
233def AddProduct(output_zip):
234  """Turn the contents of PRODUCT into a product image and store it in
235  output_zip."""
236
237  img = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "product.img")
238  if os.path.exists(img.name):
239    logger.info("product.img already exists; no need to rebuild...")
240    return img.name
241
242  block_list = OutputFile(
243      output_zip, OPTIONS.input_tmp, "IMAGES", "product.map")
244  CreateImage(
245      OPTIONS.input_tmp, OPTIONS.info_dict, "product", img,
246      block_list=block_list)
247  return img.name
248
249
250def AddSystemExt(output_zip):
251  """Turn the contents of SYSTEM_EXT into a system_ext image and store it in
252  output_zip."""
253
254  img = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES",
255                   "system_ext.img")
256  if os.path.exists(img.name):
257    logger.info("system_ext.img already exists; no need to rebuild...")
258    return img.name
259
260  block_list = OutputFile(
261      output_zip, OPTIONS.input_tmp, "IMAGES", "system_ext.map")
262  CreateImage(
263      OPTIONS.input_tmp, OPTIONS.info_dict, "system_ext", img,
264      block_list=block_list)
265  return img.name
266
267
268def AddOdm(output_zip):
269  """Turn the contents of ODM into an odm image and store it in output_zip."""
270
271  img = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "odm.img")
272  if os.path.exists(img.name):
273    logger.info("odm.img already exists; no need to rebuild...")
274    return img.name
275
276  block_list = OutputFile(
277      output_zip, OPTIONS.input_tmp, "IMAGES", "odm.map")
278  CreateImage(
279      OPTIONS.input_tmp, OPTIONS.info_dict, "odm", img,
280      block_list=block_list)
281  return img.name
282
283
284def AddVendorDlkm(output_zip):
285  """Turn the contents of VENDOR_DLKM into an vendor_dlkm image and store it in output_zip."""
286
287  img = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "vendor_dlkm.img")
288  if os.path.exists(img.name):
289    logger.info("vendor_dlkm.img already exists; no need to rebuild...")
290    return img.name
291
292  block_list = OutputFile(
293      output_zip, OPTIONS.input_tmp, "IMAGES", "vendor_dlkm.map")
294  CreateImage(
295      OPTIONS.input_tmp, OPTIONS.info_dict, "vendor_dlkm", img,
296      block_list=block_list)
297  return img.name
298
299def AddOdmDlkm(output_zip):
300  """Turn the contents of OdmDlkm into an odm_dlkm image and store it in output_zip."""
301
302  img = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "odm_dlkm.img")
303  if os.path.exists(img.name):
304    logger.info("odm_dlkm.img already exists; no need to rebuild...")
305    return img.name
306
307  block_list = OutputFile(
308      output_zip, OPTIONS.input_tmp, "IMAGES", "odm_dlkm.map")
309  CreateImage(
310      OPTIONS.input_tmp, OPTIONS.info_dict, "odm_dlkm", img,
311      block_list=block_list)
312  return img.name
313
314
315def AddDtbo(output_zip):
316  """Adds the DTBO image.
317
318  Uses the image under IMAGES/ if it already exists. Otherwise looks for the
319  image under PREBUILT_IMAGES/, signs it as needed, and returns the image name.
320  """
321  img = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "dtbo.img")
322  if os.path.exists(img.name):
323    logger.info("dtbo.img already exists; no need to rebuild...")
324    return img.name
325
326  dtbo_prebuilt_path = os.path.join(
327      OPTIONS.input_tmp, "PREBUILT_IMAGES", "dtbo.img")
328  assert os.path.exists(dtbo_prebuilt_path)
329  shutil.copy(dtbo_prebuilt_path, img.name)
330
331  # AVB-sign the image as needed.
332  if OPTIONS.info_dict.get("avb_enable") == "true":
333    avbtool = OPTIONS.info_dict["avb_avbtool"]
334    part_size = OPTIONS.info_dict["dtbo_size"]
335    # The AVB hash footer will be replaced if already present.
336    cmd = [avbtool, "add_hash_footer", "--image", img.name,
337           "--partition_size", str(part_size), "--partition_name", "dtbo"]
338    common.AppendAVBSigningArgs(cmd, "dtbo")
339    args = OPTIONS.info_dict.get("avb_dtbo_add_hash_footer_args")
340    if args and args.strip():
341      cmd.extend(shlex.split(args))
342    common.RunAndCheckOutput(cmd)
343
344  img.Write()
345  return img.name
346
347def AddCustomImages(output_zip, partition_name):
348  """Adds and signs custom images in IMAGES/.
349
350  Args:
351    output_zip: The output zip file (needs to be already open), or None to
352        write images to OPTIONS.input_tmp/.
353
354  Uses the image under IMAGES/ if it already exists. Otherwise looks for the
355  image under PREBUILT_IMAGES/, signs it as needed, and returns the image name.
356
357  Raises:
358    AssertionError: If image can't be found.
359  """
360
361  partition_size = OPTIONS.info_dict.get(
362      "avb_{}_partition_size".format(partition_name))
363  key_path = OPTIONS.info_dict.get("avb_{}_key_path".format(partition_name))
364  algorithm = OPTIONS.info_dict.get("avb_{}_algorithm".format(partition_name))
365  extra_args = OPTIONS.info_dict.get(
366      "avb_{}_add_hashtree_footer_args".format(partition_name))
367  partition_size = OPTIONS.info_dict.get(
368      "avb_{}_partition_size".format(partition_name))
369
370  builder = verity_utils.CreateCustomImageBuilder(
371      OPTIONS.info_dict, partition_name, partition_size,
372      key_path, algorithm, extra_args)
373
374  for img_name in OPTIONS.info_dict.get(
375      "avb_{}_image_list".format(partition_name)).split():
376    custom_image = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", img_name)
377    if os.path.exists(custom_image.name):
378      continue
379
380    custom_image_prebuilt_path = os.path.join(
381        OPTIONS.input_tmp, "PREBUILT_IMAGES", img_name)
382    assert os.path.exists(custom_image_prebuilt_path), \
383      "Failed to find %s at %s" % (img_name, custom_image_prebuilt_path)
384
385    shutil.copy(custom_image_prebuilt_path, custom_image.name)
386
387    if builder is not None:
388      builder.Build(custom_image.name)
389
390    custom_image.Write()
391
392  default = os.path.join(OPTIONS.input_tmp, "IMAGES", partition_name + ".img")
393  assert os.path.exists(default), \
394      "There should be one %s.img" % (partition_name)
395  return default
396
397
398def CreateImage(input_dir, info_dict, what, output_file, block_list=None):
399  logger.info("creating %s.img...", what)
400
401  image_props = build_image.ImagePropFromGlobalDict(info_dict, what)
402  image_props["timestamp"] = FIXED_FILE_TIMESTAMP
403
404  if what == "system":
405    fs_config_prefix = ""
406  else:
407    fs_config_prefix = what + "_"
408
409  fs_config = os.path.join(
410      input_dir, "META/" + fs_config_prefix + "filesystem_config.txt")
411  if not os.path.exists(fs_config):
412    fs_config = None
413
414  # Override values loaded from info_dict.
415  if fs_config:
416    image_props["fs_config"] = fs_config
417  if block_list:
418    image_props["block_list"] = block_list.name
419
420  # Use repeatable ext4 FS UUID and hash_seed UUID (based on partition name and
421  # build fingerprint).
422  build_info = common.BuildInfo(info_dict)
423  uuid_seed = what + "-" + build_info.GetPartitionFingerprint(what)
424  image_props["uuid"] = str(uuid.uuid5(uuid.NAMESPACE_URL, uuid_seed))
425  hash_seed = "hash_seed-" + uuid_seed
426  image_props["hash_seed"] = str(uuid.uuid5(uuid.NAMESPACE_URL, hash_seed))
427
428  build_image.BuildImage(
429      os.path.join(input_dir, what.upper()), image_props, output_file.name)
430
431  output_file.Write()
432  if block_list:
433    block_list.Write()
434
435  # Set the '_image_size' for given image size.
436  is_verity_partition = "verity_block_device" in image_props
437  verity_supported = (image_props.get("verity") == "true" or
438                      image_props.get("avb_enable") == "true")
439  is_avb_enable = image_props.get("avb_hashtree_enable") == "true"
440  if verity_supported and (is_verity_partition or is_avb_enable):
441    image_size = image_props.get("image_size")
442    if image_size:
443      image_size_key = what + "_image_size"
444      info_dict[image_size_key] = int(image_size)
445
446  use_dynamic_size = (
447      info_dict.get("use_dynamic_partition_size") == "true" and
448      what in shlex.split(info_dict.get("dynamic_partition_list", "").strip()))
449  if use_dynamic_size:
450    info_dict.update(build_image.GlobalDictFromImageProp(image_props, what))
451
452
453def AddUserdata(output_zip):
454  """Create a userdata image and store it in output_zip.
455
456  In most case we just create and store an empty userdata.img;
457  But the invoker can also request to create userdata.img with real
458  data from the target files, by setting "userdata_img_with_data=true"
459  in OPTIONS.info_dict.
460  """
461
462  img = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "userdata.img")
463  if os.path.exists(img.name):
464    logger.info("userdata.img already exists; no need to rebuild...")
465    return
466
467  # Skip userdata.img if no size.
468  image_props = build_image.ImagePropFromGlobalDict(OPTIONS.info_dict, "data")
469  if not image_props.get("partition_size"):
470    return
471
472  logger.info("creating userdata.img...")
473
474  image_props["timestamp"] = FIXED_FILE_TIMESTAMP
475
476  if OPTIONS.info_dict.get("userdata_img_with_data") == "true":
477    user_dir = os.path.join(OPTIONS.input_tmp, "DATA")
478  else:
479    user_dir = common.MakeTempDir()
480
481  build_image.BuildImage(user_dir, image_props, img.name)
482
483  common.CheckSize(img.name, "userdata.img", OPTIONS.info_dict)
484  img.Write()
485
486
487def AddVBMeta(output_zip, partitions, name, needed_partitions):
488  """Creates a VBMeta image and stores it in output_zip.
489
490  It generates the requested VBMeta image. The requested image could be for
491  top-level or chained VBMeta image, which is determined based on the name.
492
493  Args:
494    output_zip: The output zip file, which needs to be already open.
495    partitions: A dict that's keyed by partition names with image paths as
496        values. Only valid partition names are accepted, as partitions listed
497        in common.AVB_PARTITIONS and custom partitions listed in
498        OPTIONS.info_dict.get("avb_custom_images_partition_list")
499    name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
500    needed_partitions: Partitions whose descriptors should be included into the
501        generated VBMeta image.
502
503  Returns:
504    Path to the created image.
505
506  Raises:
507    AssertionError: On invalid input args.
508  """
509  assert needed_partitions, "Needed partitions must be specified"
510
511  img = OutputFile(
512      output_zip, OPTIONS.input_tmp, "IMAGES", "{}.img".format(name))
513  if os.path.exists(img.name):
514    logger.info("%s.img already exists; not rebuilding...", name)
515    return img.name
516
517  common.BuildVBMeta(img.name, partitions, name, needed_partitions)
518  img.Write()
519  return img.name
520
521
522def AddPartitionTable(output_zip):
523  """Create a partition table image and store it in output_zip."""
524
525  img = OutputFile(
526      output_zip, OPTIONS.input_tmp, "IMAGES", "partition-table.img")
527  bpt = OutputFile(
528      output_zip, OPTIONS.input_tmp, "META", "partition-table.bpt")
529
530  # use BPTTOOL from environ, or "bpttool" if empty or not set.
531  bpttool = os.getenv("BPTTOOL") or "bpttool"
532  cmd = [bpttool, "make_table", "--output_json", bpt.name,
533         "--output_gpt", img.name]
534  input_files_str = OPTIONS.info_dict["board_bpt_input_files"]
535  input_files = input_files_str.split(" ")
536  for i in input_files:
537    cmd.extend(["--input", i])
538  disk_size = OPTIONS.info_dict.get("board_bpt_disk_size")
539  if disk_size:
540    cmd.extend(["--disk_size", disk_size])
541  args = OPTIONS.info_dict.get("board_bpt_make_table_args")
542  if args:
543    cmd.extend(shlex.split(args))
544  common.RunAndCheckOutput(cmd)
545
546  img.Write()
547  bpt.Write()
548
549
550def AddCache(output_zip):
551  """Create an empty cache image and store it in output_zip."""
552
553  img = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "cache.img")
554  if os.path.exists(img.name):
555    logger.info("cache.img already exists; no need to rebuild...")
556    return
557
558  image_props = build_image.ImagePropFromGlobalDict(OPTIONS.info_dict, "cache")
559  # The build system has to explicitly request for cache.img.
560  if "fs_type" not in image_props:
561    return
562
563  logger.info("creating cache.img...")
564
565  image_props["timestamp"] = FIXED_FILE_TIMESTAMP
566
567  user_dir = common.MakeTempDir()
568  build_image.BuildImage(user_dir, image_props, img.name)
569
570  common.CheckSize(img.name, "cache.img", OPTIONS.info_dict)
571  img.Write()
572
573
574def CheckAbOtaImages(output_zip, ab_partitions):
575  """Checks that all the listed A/B partitions have their images available.
576
577  The images need to be available under IMAGES/ or RADIO/, with the former takes
578  a priority.
579
580  Args:
581    output_zip: The output zip file (needs to be already open), or None to
582        find images in OPTIONS.input_tmp/.
583    ab_partitions: The list of A/B partitions.
584
585  Raises:
586    AssertionError: If it can't find an image.
587  """
588  for partition in ab_partitions:
589    img_name = partition.strip() + ".img"
590
591    # Assert that the image is present under IMAGES/ now.
592    if output_zip:
593      # Zip spec says: All slashes MUST be forward slashes.
594      images_path = "IMAGES/" + img_name
595      radio_path = "RADIO/" + img_name
596      available = (images_path in output_zip.namelist() or
597                   radio_path in output_zip.namelist())
598    else:
599      images_path = os.path.join(OPTIONS.input_tmp, "IMAGES", img_name)
600      radio_path = os.path.join(OPTIONS.input_tmp, "RADIO", img_name)
601      available = os.path.exists(images_path) or os.path.exists(radio_path)
602
603    assert available, "Failed to find " + img_name
604
605
606def AddCareMapForAbOta(output_zip, ab_partitions, image_paths):
607  """Generates and adds care_map.pb for a/b partition that has care_map.
608
609  Args:
610    output_zip: The output zip file (needs to be already open), or None to
611        write care_map.pb to OPTIONS.input_tmp/.
612    ab_partitions: The list of A/B partitions.
613    image_paths: A map from the partition name to the image path.
614  """
615  care_map_list = []
616  for partition in ab_partitions:
617    partition = partition.strip()
618    if partition not in common.PARTITIONS_WITH_CARE_MAP:
619      continue
620
621    verity_block_device = "{}_verity_block_device".format(partition)
622    avb_hashtree_enable = "avb_{}_hashtree_enable".format(partition)
623    if (verity_block_device in OPTIONS.info_dict or
624        OPTIONS.info_dict.get(avb_hashtree_enable) == "true"):
625      image_path = image_paths[partition]
626      assert os.path.exists(image_path)
627
628      care_map = GetCareMap(partition, image_path)
629      if not care_map:
630        continue
631      care_map_list += care_map
632
633      # adds fingerprint field to the care_map
634      # TODO(xunchang) revisit the fingerprint calculation for care_map.
635      partition_props = OPTIONS.info_dict.get(partition + ".build.prop")
636      prop_name_list = ["ro.{}.build.fingerprint".format(partition),
637                        "ro.{}.build.thumbprint".format(partition)]
638
639      present_props = [x for x in prop_name_list if
640                       partition_props and partition_props.GetProp(x)]
641      if not present_props:
642        logger.warning("fingerprint is not present for partition %s", partition)
643        property_id, fingerprint = "unknown", "unknown"
644      else:
645        property_id = present_props[0]
646        fingerprint = partition_props.GetProp(property_id)
647      care_map_list += [property_id, fingerprint]
648
649  if not care_map_list:
650    return
651
652  # Converts the list into proto buf message by calling care_map_generator; and
653  # writes the result to a temp file.
654  temp_care_map_text = common.MakeTempFile(prefix="caremap_text-",
655                                           suffix=".txt")
656  with open(temp_care_map_text, 'w') as text_file:
657    text_file.write('\n'.join(care_map_list))
658
659  temp_care_map = common.MakeTempFile(prefix="caremap-", suffix=".pb")
660  care_map_gen_cmd = ["care_map_generator", temp_care_map_text, temp_care_map]
661  common.RunAndCheckOutput(care_map_gen_cmd)
662
663  care_map_path = "META/care_map.pb"
664  if output_zip and care_map_path not in output_zip.namelist():
665    common.ZipWrite(output_zip, temp_care_map, arcname=care_map_path)
666  else:
667    shutil.copy(temp_care_map, os.path.join(OPTIONS.input_tmp, care_map_path))
668    if output_zip:
669      OPTIONS.replace_updated_files_list.append(care_map_path)
670
671
672def AddPackRadioImages(output_zip, images):
673  """Copies images listed in META/pack_radioimages.txt from RADIO/ to IMAGES/.
674
675  Args:
676    output_zip: The output zip file (needs to be already open), or None to
677        write images to OPTIONS.input_tmp/.
678    images: A list of image names.
679
680  Raises:
681    AssertionError: If a listed image can't be found.
682  """
683  for image in images:
684    img_name = image.strip()
685    _, ext = os.path.splitext(img_name)
686    if not ext:
687      img_name += ".img"
688
689    prebuilt_path = os.path.join(OPTIONS.input_tmp, "IMAGES", img_name)
690    if os.path.exists(prebuilt_path):
691      logger.info("%s already exists, no need to overwrite...", img_name)
692      continue
693
694    img_radio_path = os.path.join(OPTIONS.input_tmp, "RADIO", img_name)
695    assert os.path.exists(img_radio_path), \
696        "Failed to find %s at %s" % (img_name, img_radio_path)
697
698    if output_zip:
699      common.ZipWrite(output_zip, img_radio_path, "IMAGES/" + img_name)
700    else:
701      shutil.copy(img_radio_path, prebuilt_path)
702
703
704def AddSuperEmpty(output_zip):
705  """Create a super_empty.img and store it in output_zip."""
706
707  img = OutputFile(output_zip, OPTIONS.input_tmp, "IMAGES", "super_empty.img")
708  build_super_image.BuildSuperImage(OPTIONS.info_dict, img.name)
709  img.Write()
710
711
712def AddSuperSplit(output_zip):
713  """Create split super_*.img and store it in output_zip."""
714
715  outdir = os.path.join(OPTIONS.input_tmp, "OTA")
716  built = build_super_image.BuildSuperImage(OPTIONS.input_tmp, outdir)
717
718  if built:
719    for dev in OPTIONS.info_dict['super_block_devices'].strip().split():
720      img = OutputFile(output_zip, OPTIONS.input_tmp, "OTA",
721                       "super_" + dev + ".img")
722      img.Write()
723
724
725def ReplaceUpdatedFiles(zip_filename, files_list):
726  """Updates all the ZIP entries listed in files_list.
727
728  For now the list includes META/care_map.pb, and the related files under
729  SYSTEM/ after rebuilding recovery.
730  """
731  common.ZipDelete(zip_filename, files_list)
732  output_zip = zipfile.ZipFile(zip_filename, "a",
733                               compression=zipfile.ZIP_DEFLATED,
734                               allowZip64=True)
735  for item in files_list:
736    file_path = os.path.join(OPTIONS.input_tmp, item)
737    assert os.path.exists(file_path)
738    common.ZipWrite(output_zip, file_path, arcname=item)
739  common.ZipClose(output_zip)
740
741
742def AddImagesToTargetFiles(filename):
743  """Creates and adds images (boot/recovery/system/...) to a target_files.zip.
744
745  It works with either a zip file (zip mode), or a directory that contains the
746  files to be packed into a target_files.zip (dir mode). The latter is used when
747  being called from build/make/core/Makefile.
748
749  The images will be created under IMAGES/ in the input target_files.zip.
750
751  Args:
752    filename: the target_files.zip, or the zip root directory.
753  """
754  if os.path.isdir(filename):
755    OPTIONS.input_tmp = os.path.abspath(filename)
756  else:
757    OPTIONS.input_tmp = common.UnzipTemp(filename)
758
759  if not OPTIONS.add_missing:
760    if os.path.isdir(os.path.join(OPTIONS.input_tmp, "IMAGES")):
761      logger.warning("target_files appears to already contain images.")
762      sys.exit(1)
763
764  OPTIONS.info_dict = common.LoadInfoDict(OPTIONS.input_tmp, repacking=True)
765
766  has_recovery = OPTIONS.info_dict.get("no_recovery") != "true"
767  has_boot = OPTIONS.info_dict.get("no_boot") != "true"
768  has_vendor_boot = OPTIONS.info_dict.get("vendor_boot") == "true"
769
770  # {vendor,odm,product,system_ext,vendor_dlkm,odm_dlkm}.img
771  # are unlike system.img or
772  # system_other.img, because it could be built from source, or  dropped into
773  # target_files.zip as a prebuilt blob. We consider either of them as
774  # {vendor,product,system_ext}.img being available, which could be
775  # used when generating vbmeta.img for AVB.
776  has_vendor = ((os.path.isdir(os.path.join(OPTIONS.input_tmp, "VENDOR")) and
777                 OPTIONS.info_dict.get("building_vendor_image") == "true") or
778                os.path.exists(
779                    os.path.join(OPTIONS.input_tmp, "IMAGES", "vendor.img")))
780  has_odm = ((os.path.isdir(os.path.join(OPTIONS.input_tmp, "ODM")) and
781              OPTIONS.info_dict.get("building_odm_image") == "true") or
782             os.path.exists(
783                 os.path.join(OPTIONS.input_tmp, "IMAGES", "odm.img")))
784  has_vendor_dlkm = ((os.path.isdir(os.path.join(OPTIONS.input_tmp,
785                                                 "VENDOR_DLKM")) and
786                      OPTIONS.info_dict.get("building_vendor_dlkm_image")
787                      == "true") or
788                     os.path.exists(
789                         os.path.join(OPTIONS.input_tmp, "IMAGES",
790                                      "vendor_dlkm.img")))
791  has_odm_dlkm = ((os.path.isdir(os.path.join(OPTIONS.input_tmp,
792                                              "ODM_DLKM")) and
793                   OPTIONS.info_dict.get("building_odm_dlkm_image")
794                   == "true") or
795                  os.path.exists(os.path.join(OPTIONS.input_tmp, "IMAGES",
796                                              "odm_dlkm.img")))
797  has_product = ((os.path.isdir(os.path.join(OPTIONS.input_tmp, "PRODUCT")) and
798                  OPTIONS.info_dict.get("building_product_image") == "true") or
799                 os.path.exists(
800                     os.path.join(OPTIONS.input_tmp, "IMAGES", "product.img")))
801  has_system_ext = (
802      (os.path.isdir(os.path.join(OPTIONS.input_tmp, "SYSTEM_EXT")) and
803       OPTIONS.info_dict.get("building_system_ext_image") == "true") or
804      os.path.exists(
805          os.path.join(OPTIONS.input_tmp, "IMAGES", "system_ext.img")))
806  has_system = (
807      os.path.isdir(os.path.join(OPTIONS.input_tmp, "SYSTEM")) and
808      OPTIONS.info_dict.get("building_system_image") == "true")
809
810  has_system_other = (
811      os.path.isdir(os.path.join(OPTIONS.input_tmp, "SYSTEM_OTHER")) and
812      OPTIONS.info_dict.get("building_system_other_image") == "true")
813  has_userdata = OPTIONS.info_dict.get("building_userdata_image") == "true"
814  has_cache = OPTIONS.info_dict.get("building_cache_image") == "true"
815
816  # Set up the output destination. It writes to the given directory for dir
817  # mode; otherwise appends to the given ZIP.
818  if os.path.isdir(filename):
819    output_zip = None
820  else:
821    output_zip = zipfile.ZipFile(filename, "a",
822                                 compression=zipfile.ZIP_DEFLATED,
823                                 allowZip64=True)
824
825  # Always make input_tmp/IMAGES available, since we may stage boot / recovery
826  # images there even under zip mode. The directory will be cleaned up as part
827  # of OPTIONS.input_tmp.
828  images_dir = os.path.join(OPTIONS.input_tmp, "IMAGES")
829  if not os.path.isdir(images_dir):
830    os.makedirs(images_dir)
831
832  # A map between partition names and their paths, which could be used when
833  # generating AVB vbmeta image.
834  partitions = {}
835
836  def banner(s):
837    logger.info("\n\n++++ %s  ++++\n\n", s)
838
839  boot_image = None
840  if has_boot:
841    banner("boot")
842    boot_images = OPTIONS.info_dict.get("boot_images")
843    if boot_images is None:
844      boot_images = "boot.img"
845    for index,b in enumerate(boot_images.split()):
846      # common.GetBootableImage() returns the image directly if present.
847      boot_image = common.GetBootableImage(
848          "IMAGES/" + b, b, OPTIONS.input_tmp, "BOOT")
849      # boot.img may be unavailable in some targets (e.g. aosp_arm64).
850      if boot_image:
851        boot_image_path = os.path.join(OPTIONS.input_tmp, "IMAGES", b)
852        # Although multiple boot images can be generated, include the image
853        # descriptor of only the first boot image in vbmeta
854        if index == 0:
855          partitions['boot'] = boot_image_path
856        if not os.path.exists(boot_image_path):
857          boot_image.WriteToDir(OPTIONS.input_tmp)
858          if output_zip:
859            boot_image.AddToZip(output_zip)
860
861  if has_vendor_boot:
862    banner("vendor_boot")
863    vendor_boot_image = common.GetVendorBootImage(
864        "IMAGES/vendor_boot.img", "vendor_boot.img", OPTIONS.input_tmp,
865        "VENDOR_BOOT")
866    if vendor_boot_image:
867      partitions['vendor_boot'] = os.path.join(OPTIONS.input_tmp, "IMAGES",
868                                               "vendor_boot.img")
869      if not os.path.exists(partitions['vendor_boot']):
870        vendor_boot_image.WriteToDir(OPTIONS.input_tmp)
871        if output_zip:
872          vendor_boot_image.AddToZip(output_zip)
873
874  recovery_image = None
875  if has_recovery:
876    banner("recovery")
877    recovery_image = common.GetBootableImage(
878        "IMAGES/recovery.img", "recovery.img", OPTIONS.input_tmp, "RECOVERY")
879    assert recovery_image, "Failed to create recovery.img."
880    partitions['recovery'] = os.path.join(
881        OPTIONS.input_tmp, "IMAGES", "recovery.img")
882    if not os.path.exists(partitions['recovery']):
883      recovery_image.WriteToDir(OPTIONS.input_tmp)
884      if output_zip:
885        recovery_image.AddToZip(output_zip)
886
887      banner("recovery (two-step image)")
888      # The special recovery.img for two-step package use.
889      recovery_two_step_image = common.GetBootableImage(
890          "OTA/recovery-two-step.img", "recovery-two-step.img",
891          OPTIONS.input_tmp, "RECOVERY", two_step_image=True)
892      assert recovery_two_step_image, "Failed to create recovery-two-step.img."
893      recovery_two_step_image_path = os.path.join(
894          OPTIONS.input_tmp, "OTA", "recovery-two-step.img")
895      if not os.path.exists(recovery_two_step_image_path):
896        recovery_two_step_image.WriteToDir(OPTIONS.input_tmp)
897        if output_zip:
898          recovery_two_step_image.AddToZip(output_zip)
899
900  if has_system:
901    banner("system")
902    partitions['system'] = AddSystem(
903        output_zip, recovery_img=recovery_image, boot_img=boot_image)
904
905  if has_vendor:
906    banner("vendor")
907    partitions['vendor'] = AddVendor(
908        output_zip, recovery_img=recovery_image, boot_img=boot_image)
909
910  if has_product:
911    banner("product")
912    partitions['product'] = AddProduct(output_zip)
913
914  if has_system_ext:
915    banner("system_ext")
916    partitions['system_ext'] = AddSystemExt(output_zip)
917
918  if has_odm:
919    banner("odm")
920    partitions['odm'] = AddOdm(output_zip)
921
922  if has_vendor_dlkm:
923    banner("vendor_dlkm")
924    partitions['vendor_dlkm'] = AddVendorDlkm(output_zip)
925
926  if has_odm_dlkm:
927    banner("odm_dlkm")
928    partitions['odm_dlkm'] = AddOdmDlkm(output_zip)
929
930  if has_system_other:
931    banner("system_other")
932    AddSystemOther(output_zip)
933
934  if not OPTIONS.is_signing:
935    banner("userdata")
936    AddUserdata(output_zip)
937    banner("cache")
938    AddCache(output_zip)
939
940  if OPTIONS.info_dict.get("board_bpt_enable") == "true":
941    banner("partition-table")
942    AddPartitionTable(output_zip)
943
944  if OPTIONS.info_dict.get("has_dtbo") == "true":
945    banner("dtbo")
946    partitions['dtbo'] = AddDtbo(output_zip)
947
948  # Custom images.
949  custom_partitions = OPTIONS.info_dict.get(
950      "avb_custom_images_partition_list", "").strip().split()
951  for partition_name in custom_partitions:
952    partition_name = partition_name.strip()
953    banner("custom images for " + partition_name)
954    partitions[partition_name] = AddCustomImages(output_zip, partition_name)
955
956  if OPTIONS.info_dict.get("avb_enable") == "true":
957    # vbmeta_partitions includes the partitions that should be included into
958    # top-level vbmeta.img, which are the ones that are not included in any
959    # chained VBMeta image plus the chained VBMeta images themselves.
960    # Currently custom_partitions are all chained to VBMeta image.
961    vbmeta_partitions = common.AVB_PARTITIONS[:] + tuple(custom_partitions)
962
963    vbmeta_system = OPTIONS.info_dict.get("avb_vbmeta_system", "").strip()
964    if vbmeta_system:
965      banner("vbmeta_system")
966      partitions["vbmeta_system"] = AddVBMeta(
967          output_zip, partitions, "vbmeta_system", vbmeta_system.split())
968      vbmeta_partitions = [
969          item for item in vbmeta_partitions
970          if item not in vbmeta_system.split()]
971      vbmeta_partitions.append("vbmeta_system")
972
973    vbmeta_vendor = OPTIONS.info_dict.get("avb_vbmeta_vendor", "").strip()
974    if vbmeta_vendor:
975      banner("vbmeta_vendor")
976      partitions["vbmeta_vendor"] = AddVBMeta(
977          output_zip, partitions, "vbmeta_vendor", vbmeta_vendor.split())
978      vbmeta_partitions = [
979          item for item in vbmeta_partitions
980          if item not in vbmeta_vendor.split()]
981      vbmeta_partitions.append("vbmeta_vendor")
982
983    banner("vbmeta")
984    AddVBMeta(output_zip, partitions, "vbmeta", vbmeta_partitions)
985
986  if OPTIONS.info_dict.get("use_dynamic_partitions") == "true":
987    banner("super_empty")
988    AddSuperEmpty(output_zip)
989
990  if OPTIONS.info_dict.get("build_super_partition") == "true":
991    if OPTIONS.info_dict.get(
992        "build_retrofit_dynamic_partitions_ota_package") == "true":
993      banner("super split images")
994      AddSuperSplit(output_zip)
995
996  banner("radio")
997  ab_partitions_txt = os.path.join(OPTIONS.input_tmp, "META",
998                                   "ab_partitions.txt")
999  if os.path.exists(ab_partitions_txt):
1000    with open(ab_partitions_txt) as f:
1001      ab_partitions = f.readlines()
1002
1003    # For devices using A/B update, make sure we have all the needed images
1004    # ready under IMAGES/ or RADIO/.
1005    CheckAbOtaImages(output_zip, ab_partitions)
1006
1007    # Generate care_map.pb for ab_partitions, then write this file to
1008    # target_files package.
1009    AddCareMapForAbOta(output_zip, ab_partitions, partitions)
1010
1011  # Radio images that need to be packed into IMAGES/, and product-img.zip.
1012  pack_radioimages_txt = os.path.join(
1013      OPTIONS.input_tmp, "META", "pack_radioimages.txt")
1014  if os.path.exists(pack_radioimages_txt):
1015    with open(pack_radioimages_txt) as f:
1016      AddPackRadioImages(output_zip, f.readlines())
1017
1018  if output_zip:
1019    common.ZipClose(output_zip)
1020    if OPTIONS.replace_updated_files_list:
1021      ReplaceUpdatedFiles(output_zip.filename,
1022                          OPTIONS.replace_updated_files_list)
1023
1024
1025def main(argv):
1026  def option_handler(o, a):
1027    if o in ("-a", "--add_missing"):
1028      OPTIONS.add_missing = True
1029    elif o in ("-r", "--rebuild_recovery",):
1030      OPTIONS.rebuild_recovery = True
1031    elif o == "--replace_verity_private_key":
1032      OPTIONS.replace_verity_private_key = (True, a)
1033    elif o == "--replace_verity_public_key":
1034      OPTIONS.replace_verity_public_key = (True, a)
1035    elif o == "--is_signing":
1036      OPTIONS.is_signing = True
1037    else:
1038      return False
1039    return True
1040
1041  args = common.ParseOptions(
1042      argv, __doc__, extra_opts="ar",
1043      extra_long_opts=["add_missing", "rebuild_recovery",
1044                       "replace_verity_public_key=",
1045                       "replace_verity_private_key=",
1046                       "is_signing"],
1047      extra_option_handler=option_handler)
1048
1049  if len(args) != 1:
1050    common.Usage(__doc__)
1051    sys.exit(1)
1052
1053  common.InitLogging()
1054
1055  AddImagesToTargetFiles(args[0])
1056  logger.info("done.")
1057
1058if __name__ == '__main__':
1059  try:
1060    common.CloseInheritedPipes()
1061    main(sys.argv[1:])
1062  except common.ExternalError:
1063    logger.exception("\n   ERROR:\n")
1064    sys.exit(1)
1065  finally:
1066    common.Cleanup()
1067