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