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