14810dcd24a922a5c5529d8f239593fd0d7580386leozwang#!/usr/bin/env python
2#
3# Copyright (C) 2008 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""
18Given a target-files zipfile, produces an OTA package that installs
19that build.  An incremental OTA is produced if -i is given, otherwise
20a full OTA is produced.
21
22Usage:  ota_from_target_files [flags] input_target_files output_ota_package
23
24  --board_config  <file>
25      Deprecated.
26
27  -k (--package_key) <key> Key to use to sign the package (default is
28      the value of default_system_dev_certificate from the input
29      target-files's META/misc_info.txt, or
30      "build/target/product/security/testkey" if that value is not
31      specified).
32
33      For incremental OTAs, the default value is based on the source
34      target-file, not the target build.
35
36  -i  (--incremental_from)  <file>
37      Generate an incremental OTA using the given target-files zip as
38      the starting build.
39
40  --full_radio
41      When generating an incremental OTA, always include a full copy of
42      radio image. This option is only meaningful when -i is specified,
43      because a full radio is always included in a full OTA if applicable.
44
45  --full_bootloader
46      Similar to --full_radio. When generating an incremental OTA, always
47      include a full copy of bootloader image.
48
49  -v  (--verify)
50      Remount and verify the checksums of the files written to the
51      system and vendor (if used) partitions.  Incremental builds only.
52
53  -o  (--oem_settings)  <main_file[,additional_files...]>
54      Comma seperated list of files used to specify the expected OEM-specific
55      properties on the OEM partition of the intended device.
56      Multiple expected values can be used by providing multiple files.
57
58  --oem_no_mount
59      For devices with OEM-specific properties but without an OEM partition,
60      do not mount the OEM partition in the updater-script. This should be
61      very rarely used, since it's expected to have a dedicated OEM partition
62      for OEM-specific properties. Only meaningful when -o is specified.
63
64  -w  (--wipe_user_data)
65      Generate an OTA package that will wipe the user data partition
66      when installed.
67
68  --downgrade
69      Intentionally generate an incremental OTA that updates from a newer
70      build to an older one (based on timestamp comparison). "post-timestamp"
71      will be replaced by "ota-downgrade=yes" in the metadata file. A data
72      wipe will always be enforced, so "ota-wipe=yes" will also be included in
73      the metadata file. The update-binary in the source build will be used in
74      the OTA package, unless --binary flag is specified. Please also check the
75      doc for --override_timestamp below.
76
77  --override_timestamp
78      Intentionally generate an incremental OTA that updates from a newer
79      build to an older one (based on timestamp comparison), by overriding the
80      timestamp in package metadata. This differs from --downgrade flag: we
81      know for sure this is NOT an actual downgrade case, but two builds are
82      cut in a reverse order. A legit use case is that we cut a new build C
83      (after having A and B), but want to enfore an update path of A -> C -> B.
84      Specifying --downgrade may not help since that would enforce a data wipe
85      for C -> B update. The value of "post-timestamp" will be set to the newer
86      timestamp plus one, so that the package can be pushed and applied.
87
88  -e  (--extra_script)  <file>
89      Insert the contents of file at the end of the update script.
90
91  -2  (--two_step)
92      Generate a 'two-step' OTA package, where recovery is updated
93      first, so that any changes made to the system partition are done
94      using the new recovery (new kernel, etc.).
95
96  --block
97      Generate a block-based OTA for non-A/B device. We have deprecated the
98      support for file-based OTA since O. Block-based OTA will be used by
99      default for all non-A/B devices. Keeping this flag here to not break
100      existing callers.
101
102  -b  (--binary)  <file>
103      Use the given binary as the update-binary in the output package,
104      instead of the binary in the build's target_files.  Use for
105      development only.
106
107  -t  (--worker_threads) <int>
108      Specifies the number of worker-threads that will be used when
109      generating patches for incremental updates (defaults to 3).
110
111  --stash_threshold <float>
112      Specifies the threshold that will be used to compute the maximum
113      allowed stash size (defaults to 0.8).
114
115  --gen_verify
116      Generate an OTA package that verifies the partitions.
117
118  --log_diff <file>
119      Generate a log file that shows the differences in the source and target
120      builds for an incremental package. This option is only meaningful when
121      -i is specified.
122
123  --payload_signer <signer>
124      Specify the signer when signing the payload and metadata for A/B OTAs.
125      By default (i.e. without this flag), it calls 'openssl pkeyutl' to sign
126      with the package private key. If the private key cannot be accessed
127      directly, a payload signer that knows how to do that should be specified.
128      The signer will be supplied with "-inkey <path_to_key>",
129      "-in <input_file>" and "-out <output_file>" parameters.
130
131  --payload_signer_args <args>
132      Specify the arguments needed for payload signer.
133"""
134
135from __future__ import print_function
136
137import sys
138
139if sys.hexversion < 0x02070000:
140  print("Python 2.7 or newer is required.", file=sys.stderr)
141  sys.exit(1)
142
143import copy
144import multiprocessing
145import os.path
146import subprocess
147import shlex
148import tempfile
149import zipfile
150
151import common
152import edify_generator
153import sparse_img
154
155OPTIONS = common.OPTIONS
156OPTIONS.package_key = None
157OPTIONS.incremental_source = None
158OPTIONS.verify = False
159OPTIONS.patch_threshold = 0.95
160OPTIONS.wipe_user_data = False
161OPTIONS.downgrade = False
162OPTIONS.timestamp = False
163OPTIONS.extra_script = None
164OPTIONS.worker_threads = multiprocessing.cpu_count() // 2
165if OPTIONS.worker_threads == 0:
166  OPTIONS.worker_threads = 1
167OPTIONS.two_step = False
168OPTIONS.no_signing = False
169OPTIONS.block_based = True
170OPTIONS.updater_binary = None
171OPTIONS.oem_source = None
172OPTIONS.oem_no_mount = False
173OPTIONS.fallback_to_full = True
174OPTIONS.full_radio = False
175OPTIONS.full_bootloader = False
176# Stash size cannot exceed cache_size * threshold.
177OPTIONS.cache_size = None
178OPTIONS.stash_threshold = 0.8
179OPTIONS.gen_verify = False
180OPTIONS.log_diff = None
181OPTIONS.payload_signer = None
182OPTIONS.payload_signer_args = []
183OPTIONS.extracted_input = None
184
185METADATA_NAME = 'META-INF/com/android/metadata'
186UNZIP_PATTERN = ['IMAGES/*', 'META/*']
187
188
189def SignOutput(temp_zip_name, output_zip_name):
190  key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
191  pw = key_passwords[OPTIONS.package_key]
192
193  common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw,
194                  whole_file=True)
195
196
197def AppendAssertions(script, info_dict, oem_dicts=None):
198  oem_props = info_dict.get("oem_fingerprint_properties")
199  if not oem_props:
200    device = GetBuildProp("ro.product.device", info_dict)
201    script.AssertDevice(device)
202  else:
203    if not oem_dicts:
204      raise common.ExternalError(
205          "No OEM file provided to answer expected assertions")
206    for prop in oem_props.split():
207      values = []
208      for oem_dict in oem_dicts:
209        if oem_dict.get(prop):
210          values.append(oem_dict[prop])
211      if not values:
212        raise common.ExternalError(
213            "The OEM file is missing the property %s" % prop)
214      script.AssertOemProperty(prop, values)
215
216
217def _LoadOemDicts(script, recovery_mount_options=None):
218  """Returns the list of loaded OEM properties dict."""
219  oem_dicts = None
220  if OPTIONS.oem_source is None:
221    raise common.ExternalError("OEM source required for this build")
222  if not OPTIONS.oem_no_mount and script:
223    script.Mount("/oem", recovery_mount_options)
224  oem_dicts = []
225  for oem_file in OPTIONS.oem_source:
226    oem_dicts.append(common.LoadDictionaryFromLines(
227        open(oem_file).readlines()))
228  return oem_dicts
229
230
231def _WriteRecoveryImageToBoot(script, output_zip):
232  """Find and write recovery image to /boot in two-step OTA.
233
234  In two-step OTAs, we write recovery image to /boot as the first step so that
235  we can reboot to there and install a new recovery image to /recovery.
236  A special "recovery-two-step.img" will be preferred, which encodes the correct
237  path of "/boot". Otherwise the device may show "device is corrupt" message
238  when booting into /boot.
239
240  Fall back to using the regular recovery.img if the two-step recovery image
241  doesn't exist. Note that rebuilding the special image at this point may be
242  infeasible, because we don't have the desired boot signer and keys when
243  calling ota_from_target_files.py.
244  """
245
246  recovery_two_step_img_name = "recovery-two-step.img"
247  recovery_two_step_img_path = os.path.join(
248      OPTIONS.input_tmp, "IMAGES", recovery_two_step_img_name)
249  if os.path.exists(recovery_two_step_img_path):
250    recovery_two_step_img = common.GetBootableImage(
251        recovery_two_step_img_name, recovery_two_step_img_name,
252        OPTIONS.input_tmp, "RECOVERY")
253    common.ZipWriteStr(
254        output_zip, recovery_two_step_img_name, recovery_two_step_img.data)
255    print("two-step package: using %s in stage 1/3" % (
256        recovery_two_step_img_name,))
257    script.WriteRawImage("/boot", recovery_two_step_img_name)
258  else:
259    print("two-step package: using recovery.img in stage 1/3")
260    # The "recovery.img" entry has been written into package earlier.
261    script.WriteRawImage("/boot", "recovery.img")
262
263
264def HasRecoveryPatch(target_files_zip):
265  namelist = [name for name in target_files_zip.namelist()]
266  return ("SYSTEM/recovery-from-boot.p" in namelist or
267          "SYSTEM/etc/recovery.img" in namelist)
268
269
270def HasVendorPartition(target_files_zip):
271  try:
272    target_files_zip.getinfo("VENDOR/")
273    return True
274  except KeyError:
275    return False
276
277
278def GetOemProperty(name, oem_props, oem_dict, info_dict):
279  if oem_props is not None and name in oem_props:
280    return oem_dict[name]
281  return GetBuildProp(name, info_dict)
282
283
284def CalculateFingerprint(oem_props, oem_dict, info_dict):
285  if oem_props is None:
286    return GetBuildProp("ro.build.fingerprint", info_dict)
287  return "%s/%s/%s:%s" % (
288      GetOemProperty("ro.product.brand", oem_props, oem_dict, info_dict),
289      GetOemProperty("ro.product.name", oem_props, oem_dict, info_dict),
290      GetOemProperty("ro.product.device", oem_props, oem_dict, info_dict),
291      GetBuildProp("ro.build.thumbprint", info_dict))
292
293
294def GetImage(which, tmpdir):
295  """Returns an image object suitable for passing to BlockImageDiff.
296
297  'which' partition must be "system" or "vendor". A prebuilt image and file
298  map must already exist in tmpdir.
299  """
300
301  assert which in ("system", "vendor")
302
303  path = os.path.join(tmpdir, "IMAGES", which + ".img")
304  mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
305
306  # The image and map files must have been created prior to calling
307  # ota_from_target_files.py (since LMP).
308  assert os.path.exists(path) and os.path.exists(mappath)
309
310  # Bug: http://b/20939131
311  # In ext4 filesystems, block 0 might be changed even being mounted
312  # R/O. We add it to clobbered_blocks so that it will be written to the
313  # target unconditionally. Note that they are still part of care_map.
314  clobbered_blocks = "0"
315
316  return sparse_img.SparseImage(path, mappath, clobbered_blocks)
317
318
319def AddCompatibilityArchive(target_zip, output_zip, system_included=True,
320                            vendor_included=True):
321  """Adds compatibility info from target files into the output zip.
322
323  Metadata used for on-device compatibility verification is retrieved from
324  target_zip then added to compatibility.zip which is added to the output_zip
325  archive.
326
327  Compatibility archive should only be included for devices with a vendor
328  partition as checking provides value when system and vendor are independently
329  versioned.
330
331  Args:
332    target_zip: Zip file containing the source files to be included for OTA.
333    output_zip: Zip file that will be sent for OTA.
334    system_included: If True, the system image will be updated and therefore
335        its metadata should be included.
336    vendor_included: If True, the vendor image will be updated and therefore
337        its metadata should be included.
338  """
339
340  # Determine what metadata we need. Files are names relative to META/.
341  compatibility_files = []
342  vendor_metadata = ("vendor_manifest.xml", "vendor_matrix.xml")
343  system_metadata = ("system_manifest.xml", "system_matrix.xml")
344  if vendor_included:
345    compatibility_files += vendor_metadata
346  if system_included:
347    compatibility_files += system_metadata
348
349  # Create new archive.
350  compatibility_archive = tempfile.NamedTemporaryFile()
351  compatibility_archive_zip = zipfile.ZipFile(compatibility_archive, "w",
352      compression=zipfile.ZIP_DEFLATED)
353
354  # Add metadata.
355  for file_name in compatibility_files:
356    target_file_name = "META/" + file_name
357
358    if target_file_name in target_zip.namelist():
359      data = target_zip.read(target_file_name)
360      common.ZipWriteStr(compatibility_archive_zip, file_name, data)
361
362  # Ensure files are written before we copy into output_zip.
363  compatibility_archive_zip.close()
364
365  # Only add the archive if we have any compatibility info.
366  if compatibility_archive_zip.namelist():
367    common.ZipWrite(output_zip, compatibility_archive.name,
368                    arcname="compatibility.zip",
369                    compress_type=zipfile.ZIP_STORED)
370
371
372def WriteFullOTAPackage(input_zip, output_zip):
373  # TODO: how to determine this?  We don't know what version it will
374  # be installed on top of. For now, we expect the API just won't
375  # change very often. Similarly for fstab, it might have changed
376  # in the target build.
377  script = edify_generator.EdifyGenerator(3, OPTIONS.info_dict)
378
379  recovery_mount_options = OPTIONS.info_dict.get("recovery_mount_options")
380  oem_props = OPTIONS.info_dict.get("oem_fingerprint_properties")
381  oem_dicts = None
382  if oem_props:
383    oem_dicts = _LoadOemDicts(script, recovery_mount_options)
384
385  target_fp = CalculateFingerprint(oem_props, oem_dicts and oem_dicts[0],
386                                   OPTIONS.info_dict)
387  metadata = {
388      "post-build": target_fp,
389      "pre-device": GetOemProperty("ro.product.device", oem_props,
390                                   oem_dicts and oem_dicts[0],
391                                   OPTIONS.info_dict),
392      "post-timestamp": GetBuildProp("ro.build.date.utc", OPTIONS.info_dict),
393  }
394
395  device_specific = common.DeviceSpecificParams(
396      input_zip=input_zip,
397      input_version=OPTIONS.info_dict["recovery_api_version"],
398      output_zip=output_zip,
399      script=script,
400      input_tmp=OPTIONS.input_tmp,
401      metadata=metadata,
402      info_dict=OPTIONS.info_dict)
403
404  assert HasRecoveryPatch(input_zip)
405
406  metadata["ota-type"] = "BLOCK"
407
408  ts = GetBuildProp("ro.build.date.utc", OPTIONS.info_dict)
409  ts_text = GetBuildProp("ro.build.date", OPTIONS.info_dict)
410  script.AssertOlderBuild(ts, ts_text)
411
412  AppendAssertions(script, OPTIONS.info_dict, oem_dicts)
413  device_specific.FullOTA_Assertions()
414
415  # Two-step package strategy (in chronological order, which is *not*
416  # the order in which the generated script has things):
417  #
418  # if stage is not "2/3" or "3/3":
419  #    write recovery image to boot partition
420  #    set stage to "2/3"
421  #    reboot to boot partition and restart recovery
422  # else if stage is "2/3":
423  #    write recovery image to recovery partition
424  #    set stage to "3/3"
425  #    reboot to recovery partition and restart recovery
426  # else:
427  #    (stage must be "3/3")
428  #    set stage to ""
429  #    do normal full package installation:
430  #       wipe and install system, boot image, etc.
431  #       set up system to update recovery partition on first boot
432  #    complete script normally
433  #    (allow recovery to mark itself finished and reboot)
434
435  recovery_img = common.GetBootableImage("recovery.img", "recovery.img",
436                                         OPTIONS.input_tmp, "RECOVERY")
437  if OPTIONS.two_step:
438    if not OPTIONS.info_dict.get("multistage_support", None):
439      assert False, "two-step packages not supported by this build"
440    fs = OPTIONS.info_dict["fstab"]["/misc"]
441    assert fs.fs_type.upper() == "EMMC", \
442        "two-step packages only supported on devices with EMMC /misc partitions"
443    bcb_dev = {"bcb_dev": fs.device}
444    common.ZipWriteStr(output_zip, "recovery.img", recovery_img.data)
445    script.AppendExtra("""
446if get_stage("%(bcb_dev)s") == "2/3" then
447""" % bcb_dev)
448
449    # Stage 2/3: Write recovery image to /recovery (currently running /boot).
450    script.Comment("Stage 2/3")
451    script.WriteRawImage("/recovery", "recovery.img")
452    script.AppendExtra("""
453set_stage("%(bcb_dev)s", "3/3");
454reboot_now("%(bcb_dev)s", "recovery");
455else if get_stage("%(bcb_dev)s") == "3/3" then
456""" % bcb_dev)
457
458    # Stage 3/3: Make changes.
459    script.Comment("Stage 3/3")
460
461  # Dump fingerprints
462  script.Print("Target: %s" % target_fp)
463
464  device_specific.FullOTA_InstallBegin()
465
466  system_progress = 0.75
467
468  if OPTIONS.wipe_user_data:
469    system_progress -= 0.1
470  if HasVendorPartition(input_zip):
471    system_progress -= 0.1
472
473  # Place a copy of file_contexts.bin into the OTA package which will be used
474  # by the recovery program.
475  if "selinux_fc" in OPTIONS.info_dict:
476    WritePolicyConfig(OPTIONS.info_dict["selinux_fc"], output_zip)
477
478  recovery_mount_options = OPTIONS.info_dict.get("recovery_mount_options")
479
480  script.ShowProgress(system_progress, 0)
481
482  # Full OTA is done as an "incremental" against an empty source image. This
483  # has the effect of writing new data from the package to the entire
484  # partition, but lets us reuse the updater code that writes incrementals to
485  # do it.
486  system_tgt = GetImage("system", OPTIONS.input_tmp)
487  system_tgt.ResetFileMap()
488  system_diff = common.BlockDifference("system", system_tgt, src=None)
489  system_diff.WriteScript(script, output_zip)
490
491  boot_img = common.GetBootableImage(
492      "boot.img", "boot.img", OPTIONS.input_tmp, "BOOT")
493
494  if HasVendorPartition(input_zip):
495    script.ShowProgress(0.1, 0)
496
497    vendor_tgt = GetImage("vendor", OPTIONS.input_tmp)
498    vendor_tgt.ResetFileMap()
499    vendor_diff = common.BlockDifference("vendor", vendor_tgt)
500    vendor_diff.WriteScript(script, output_zip)
501
502  common.CheckSize(boot_img.data, "boot.img", OPTIONS.info_dict)
503  common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
504
505  script.ShowProgress(0.05, 5)
506  script.WriteRawImage("/boot", "boot.img")
507
508  script.ShowProgress(0.2, 10)
509  device_specific.FullOTA_InstallEnd()
510
511  if OPTIONS.extra_script is not None:
512    script.AppendExtra(OPTIONS.extra_script)
513
514  script.UnmountAll()
515
516  if OPTIONS.wipe_user_data:
517    script.ShowProgress(0.1, 10)
518    script.FormatPartition("/data")
519
520  if OPTIONS.two_step:
521    script.AppendExtra("""
522set_stage("%(bcb_dev)s", "");
523""" % bcb_dev)
524    script.AppendExtra("else\n")
525
526    # Stage 1/3: Nothing to verify for full OTA. Write recovery image to /boot.
527    script.Comment("Stage 1/3")
528    _WriteRecoveryImageToBoot(script, output_zip)
529
530    script.AppendExtra("""
531set_stage("%(bcb_dev)s", "2/3");
532reboot_now("%(bcb_dev)s", "");
533endif;
534endif;
535""" % bcb_dev)
536
537  script.SetProgress(1)
538  script.AddToZip(input_zip, output_zip, input_path=OPTIONS.updater_binary)
539  metadata["ota-required-cache"] = str(script.required_cache)
540  WriteMetadata(metadata, output_zip)
541
542
543def WritePolicyConfig(file_name, output_zip):
544  common.ZipWrite(output_zip, file_name, os.path.basename(file_name))
545
546
547def WriteMetadata(metadata, output_zip):
548  value = "".join(["%s=%s\n" % kv for kv in sorted(metadata.iteritems())])
549  common.ZipWriteStr(output_zip, METADATA_NAME, value,
550                     compress_type=zipfile.ZIP_STORED)
551
552
553def GetBuildProp(prop, info_dict):
554  """Return the fingerprint of the build of a given target-files info_dict."""
555  try:
556    return info_dict.get("build.prop", {})[prop]
557  except KeyError:
558    raise common.ExternalError("couldn't find %s in build.prop" % (prop,))
559
560
561def HandleDowngradeMetadata(metadata):
562  # Only incremental OTAs are allowed to reach here.
563  assert OPTIONS.incremental_source is not None
564
565  post_timestamp = GetBuildProp("ro.build.date.utc", OPTIONS.target_info_dict)
566  pre_timestamp = GetBuildProp("ro.build.date.utc", OPTIONS.source_info_dict)
567  is_downgrade = long(post_timestamp) < long(pre_timestamp)
568
569  if OPTIONS.downgrade:
570    if not is_downgrade:
571      raise RuntimeError("--downgrade specified but no downgrade detected: "
572                         "pre: %s, post: %s" % (pre_timestamp, post_timestamp))
573    metadata["ota-downgrade"] = "yes"
574  elif OPTIONS.timestamp:
575    if not is_downgrade:
576      raise RuntimeError("--timestamp specified but no timestamp hack needed: "
577                         "pre: %s, post: %s" % (pre_timestamp, post_timestamp))
578    metadata["post-timestamp"] = str(long(pre_timestamp) + 1)
579  else:
580    if is_downgrade:
581      raise RuntimeError("Downgrade detected based on timestamp check: "
582                         "pre: %s, post: %s. Need to specify --timestamp OR "
583                         "--downgrade to allow building the incremental." % (
584                             pre_timestamp, post_timestamp))
585    metadata["post-timestamp"] = post_timestamp
586
587
588def WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_zip):
589  source_version = OPTIONS.source_info_dict["recovery_api_version"]
590  target_version = OPTIONS.target_info_dict["recovery_api_version"]
591
592  if source_version == 0:
593    print("WARNING: generating edify script for a source that "
594          "can't install it.")
595  script = edify_generator.EdifyGenerator(
596      source_version, OPTIONS.target_info_dict,
597      fstab=OPTIONS.source_info_dict["fstab"])
598
599  recovery_mount_options = OPTIONS.source_info_dict.get(
600      "recovery_mount_options")
601  source_oem_props = OPTIONS.source_info_dict.get("oem_fingerprint_properties")
602  target_oem_props = OPTIONS.target_info_dict.get("oem_fingerprint_properties")
603  oem_dicts = None
604  if source_oem_props and target_oem_props:
605    oem_dicts = _LoadOemDicts(script, recovery_mount_options)
606
607  metadata = {
608      "pre-device": GetOemProperty("ro.product.device", source_oem_props,
609                                   oem_dicts and oem_dicts[0],
610                                   OPTIONS.source_info_dict),
611      "ota-type": "BLOCK",
612  }
613
614  HandleDowngradeMetadata(metadata)
615
616  device_specific = common.DeviceSpecificParams(
617      source_zip=source_zip,
618      source_version=source_version,
619      target_zip=target_zip,
620      target_version=target_version,
621      output_zip=output_zip,
622      script=script,
623      metadata=metadata,
624      info_dict=OPTIONS.source_info_dict)
625
626  source_fp = CalculateFingerprint(source_oem_props, oem_dicts and oem_dicts[0],
627                                   OPTIONS.source_info_dict)
628  target_fp = CalculateFingerprint(target_oem_props, oem_dicts and oem_dicts[0],
629                                   OPTIONS.target_info_dict)
630  metadata["pre-build"] = source_fp
631  metadata["post-build"] = target_fp
632  metadata["pre-build-incremental"] = GetBuildProp(
633      "ro.build.version.incremental", OPTIONS.source_info_dict)
634  metadata["post-build-incremental"] = GetBuildProp(
635      "ro.build.version.incremental", OPTIONS.target_info_dict)
636
637  source_boot = common.GetBootableImage(
638      "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT",
639      OPTIONS.source_info_dict)
640  target_boot = common.GetBootableImage(
641      "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT")
642  updating_boot = (not OPTIONS.two_step and
643                   (source_boot.data != target_boot.data))
644
645  target_recovery = common.GetBootableImage(
646      "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY")
647
648  system_src = GetImage("system", OPTIONS.source_tmp)
649  system_tgt = GetImage("system", OPTIONS.target_tmp)
650
651  blockimgdiff_version = 1
652  if OPTIONS.info_dict:
653    blockimgdiff_version = max(
654        int(i) for i in
655        OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
656
657  # Check the first block of the source system partition for remount R/W only
658  # if the filesystem is ext4.
659  system_src_partition = OPTIONS.source_info_dict["fstab"]["/system"]
660  check_first_block = system_src_partition.fs_type == "ext4"
661  # Disable using imgdiff for squashfs. 'imgdiff -z' expects input files to be
662  # in zip formats. However with squashfs, a) all files are compressed in LZ4;
663  # b) the blocks listed in block map may not contain all the bytes for a given
664  # file (because they're rounded to be 4K-aligned).
665  system_tgt_partition = OPTIONS.target_info_dict["fstab"]["/system"]
666  disable_imgdiff = (system_src_partition.fs_type == "squashfs" or
667                     system_tgt_partition.fs_type == "squashfs")
668  system_diff = common.BlockDifference("system", system_tgt, system_src,
669                                       check_first_block,
670                                       version=blockimgdiff_version,
671                                       disable_imgdiff=disable_imgdiff)
672
673  if HasVendorPartition(target_zip):
674    if not HasVendorPartition(source_zip):
675      raise RuntimeError("can't generate incremental that adds /vendor")
676    vendor_src = GetImage("vendor", OPTIONS.source_tmp)
677    vendor_tgt = GetImage("vendor", OPTIONS.target_tmp)
678
679    # Check first block of vendor partition for remount R/W only if
680    # disk type is ext4
681    vendor_partition = OPTIONS.source_info_dict["fstab"]["/vendor"]
682    check_first_block = vendor_partition.fs_type == "ext4"
683    disable_imgdiff = vendor_partition.fs_type == "squashfs"
684    vendor_diff = common.BlockDifference("vendor", vendor_tgt, vendor_src,
685                                         check_first_block,
686                                         version=blockimgdiff_version,
687                                         disable_imgdiff=disable_imgdiff)
688  else:
689    vendor_diff = None
690
691  AppendAssertions(script, OPTIONS.target_info_dict, oem_dicts)
692  device_specific.IncrementalOTA_Assertions()
693
694  # Two-step incremental package strategy (in chronological order,
695  # which is *not* the order in which the generated script has
696  # things):
697  #
698  # if stage is not "2/3" or "3/3":
699  #    do verification on current system
700  #    write recovery image to boot partition
701  #    set stage to "2/3"
702  #    reboot to boot partition and restart recovery
703  # else if stage is "2/3":
704  #    write recovery image to recovery partition
705  #    set stage to "3/3"
706  #    reboot to recovery partition and restart recovery
707  # else:
708  #    (stage must be "3/3")
709  #    perform update:
710  #       patch system files, etc.
711  #       force full install of new boot image
712  #       set up system to update recovery partition on first boot
713  #    complete script normally
714  #    (allow recovery to mark itself finished and reboot)
715
716  if OPTIONS.two_step:
717    if not OPTIONS.source_info_dict.get("multistage_support", None):
718      assert False, "two-step packages not supported by this build"
719    fs = OPTIONS.source_info_dict["fstab"]["/misc"]
720    assert fs.fs_type.upper() == "EMMC", \
721        "two-step packages only supported on devices with EMMC /misc partitions"
722    bcb_dev = {"bcb_dev": fs.device}
723    common.ZipWriteStr(output_zip, "recovery.img", target_recovery.data)
724    script.AppendExtra("""
725if get_stage("%(bcb_dev)s") == "2/3" then
726""" % bcb_dev)
727
728    # Stage 2/3: Write recovery image to /recovery (currently running /boot).
729    script.Comment("Stage 2/3")
730    script.AppendExtra("sleep(20);\n")
731    script.WriteRawImage("/recovery", "recovery.img")
732    script.AppendExtra("""
733set_stage("%(bcb_dev)s", "3/3");
734reboot_now("%(bcb_dev)s", "recovery");
735else if get_stage("%(bcb_dev)s") != "3/3" then
736""" % bcb_dev)
737
738    # Stage 1/3: (a) Verify the current system.
739    script.Comment("Stage 1/3")
740
741  # Dump fingerprints
742  script.Print("Source: %s" % (source_fp,))
743  script.Print("Target: %s" % (target_fp,))
744
745  script.Print("Verifying current system...")
746
747  device_specific.IncrementalOTA_VerifyBegin()
748
749  # When blockimgdiff version is less than 3 (non-resumable block-based OTA),
750  # patching on a device that's already on the target build will damage the
751  # system. Because operations like move don't check the block state, they
752  # always apply the changes unconditionally.
753  if blockimgdiff_version <= 2:
754    if source_oem_props is None:
755      script.AssertSomeFingerprint(source_fp)
756    else:
757      script.AssertSomeThumbprint(
758          GetBuildProp("ro.build.thumbprint", OPTIONS.source_info_dict))
759
760  else: # blockimgdiff_version > 2
761    if source_oem_props is None and target_oem_props is None:
762      script.AssertSomeFingerprint(source_fp, target_fp)
763    elif source_oem_props is not None and target_oem_props is not None:
764      script.AssertSomeThumbprint(
765          GetBuildProp("ro.build.thumbprint", OPTIONS.target_info_dict),
766          GetBuildProp("ro.build.thumbprint", OPTIONS.source_info_dict))
767    elif source_oem_props is None and target_oem_props is not None:
768      script.AssertFingerprintOrThumbprint(
769          source_fp,
770          GetBuildProp("ro.build.thumbprint", OPTIONS.target_info_dict))
771    else:
772      script.AssertFingerprintOrThumbprint(
773          target_fp,
774          GetBuildProp("ro.build.thumbprint", OPTIONS.source_info_dict))
775
776  # Check the required cache size (i.e. stashed blocks).
777  size = []
778  if system_diff:
779    size.append(system_diff.required_cache)
780  if vendor_diff:
781    size.append(vendor_diff.required_cache)
782
783  if updating_boot:
784    boot_type, boot_device = common.GetTypeAndDevice(
785        "/boot", OPTIONS.source_info_dict)
786    d = common.Difference(target_boot, source_boot)
787    _, _, d = d.ComputePatch()
788    if d is None:
789      include_full_boot = True
790      common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
791    else:
792      include_full_boot = False
793
794      print("boot      target: %d  source: %d  diff: %d" % (
795          target_boot.size, source_boot.size, len(d)))
796
797      common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
798
799      script.PatchCheck("%s:%s:%d:%s:%d:%s" %
800                        (boot_type, boot_device,
801                         source_boot.size, source_boot.sha1,
802                         target_boot.size, target_boot.sha1))
803      size.append(target_boot.size)
804
805  if size:
806    script.CacheFreeSpaceCheck(max(size))
807
808  device_specific.IncrementalOTA_VerifyEnd()
809
810  if OPTIONS.two_step:
811    # Stage 1/3: (b) Write recovery image to /boot.
812    _WriteRecoveryImageToBoot(script, output_zip)
813
814    script.AppendExtra("""
815set_stage("%(bcb_dev)s", "2/3");
816reboot_now("%(bcb_dev)s", "");
817else
818""" % bcb_dev)
819
820    # Stage 3/3: Make changes.
821    script.Comment("Stage 3/3")
822
823  # Verify the existing partitions.
824  system_diff.WriteVerifyScript(script, touched_blocks_only=True)
825  if vendor_diff:
826    vendor_diff.WriteVerifyScript(script, touched_blocks_only=True)
827
828  script.Comment("---- start making changes here ----")
829
830  device_specific.IncrementalOTA_InstallBegin()
831
832  system_diff.WriteScript(script, output_zip,
833                          progress=0.8 if vendor_diff else 0.9)
834
835  if vendor_diff:
836    vendor_diff.WriteScript(script, output_zip, progress=0.1)
837
838  if OPTIONS.two_step:
839    common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
840    script.WriteRawImage("/boot", "boot.img")
841    print("writing full boot image (forced by two-step mode)")
842
843  if not OPTIONS.two_step:
844    if updating_boot:
845      if include_full_boot:
846        print("boot image changed; including full.")
847        script.Print("Installing boot image...")
848        script.WriteRawImage("/boot", "boot.img")
849      else:
850        # Produce the boot image by applying a patch to the current
851        # contents of the boot partition, and write it back to the
852        # partition.
853        print("boot image changed; including patch.")
854        script.Print("Patching boot image...")
855        script.ShowProgress(0.1, 10)
856        script.ApplyPatch("%s:%s:%d:%s:%d:%s"
857                          % (boot_type, boot_device,
858                             source_boot.size, source_boot.sha1,
859                             target_boot.size, target_boot.sha1),
860                          "-",
861                          target_boot.size, target_boot.sha1,
862                          source_boot.sha1, "patch/boot.img.p")
863    else:
864      print("boot image unchanged; skipping.")
865
866  # Do device-specific installation (eg, write radio image).
867  device_specific.IncrementalOTA_InstallEnd()
868
869  if OPTIONS.extra_script is not None:
870    script.AppendExtra(OPTIONS.extra_script)
871
872  if OPTIONS.wipe_user_data:
873    script.Print("Erasing user data...")
874    script.FormatPartition("/data")
875    metadata["ota-wipe"] = "yes"
876
877  if OPTIONS.two_step:
878    script.AppendExtra("""
879set_stage("%(bcb_dev)s", "");
880endif;
881endif;
882""" % bcb_dev)
883
884  script.SetProgress(1)
885  # For downgrade OTAs, we prefer to use the update-binary in the source
886  # build that is actually newer than the one in the target build.
887  if OPTIONS.downgrade:
888    script.AddToZip(source_zip, output_zip, input_path=OPTIONS.updater_binary)
889  else:
890    script.AddToZip(target_zip, output_zip, input_path=OPTIONS.updater_binary)
891  metadata["ota-required-cache"] = str(script.required_cache)
892  WriteMetadata(metadata, output_zip)
893
894
895def WriteVerifyPackage(input_zip, output_zip):
896  script = edify_generator.EdifyGenerator(3, OPTIONS.info_dict)
897
898  oem_props = OPTIONS.info_dict.get("oem_fingerprint_properties")
899  recovery_mount_options = OPTIONS.info_dict.get(
900      "recovery_mount_options")
901  oem_dicts = None
902  if oem_props:
903    oem_dicts = _LoadOemDicts(script, recovery_mount_options)
904
905  target_fp = CalculateFingerprint(oem_props, oem_dicts and oem_dicts[0],
906                                   OPTIONS.info_dict)
907  metadata = {
908      "post-build": target_fp,
909      "pre-device": GetOemProperty("ro.product.device", oem_props,
910                                   oem_dicts and oem_dicts[0],
911                                   OPTIONS.info_dict),
912      "post-timestamp": GetBuildProp("ro.build.date.utc", OPTIONS.info_dict),
913  }
914
915  device_specific = common.DeviceSpecificParams(
916      input_zip=input_zip,
917      input_version=OPTIONS.info_dict["recovery_api_version"],
918      output_zip=output_zip,
919      script=script,
920      input_tmp=OPTIONS.input_tmp,
921      metadata=metadata,
922      info_dict=OPTIONS.info_dict)
923
924  AppendAssertions(script, OPTIONS.info_dict, oem_dicts)
925
926  script.Print("Verifying device images against %s..." % target_fp)
927  script.AppendExtra("")
928
929  script.Print("Verifying boot...")
930  boot_img = common.GetBootableImage(
931      "boot.img", "boot.img", OPTIONS.input_tmp, "BOOT")
932  boot_type, boot_device = common.GetTypeAndDevice(
933      "/boot", OPTIONS.info_dict)
934  script.Verify("%s:%s:%d:%s" % (
935      boot_type, boot_device, boot_img.size, boot_img.sha1))
936  script.AppendExtra("")
937
938  script.Print("Verifying recovery...")
939  recovery_img = common.GetBootableImage(
940      "recovery.img", "recovery.img", OPTIONS.input_tmp, "RECOVERY")
941  recovery_type, recovery_device = common.GetTypeAndDevice(
942      "/recovery", OPTIONS.info_dict)
943  script.Verify("%s:%s:%d:%s" % (
944      recovery_type, recovery_device, recovery_img.size, recovery_img.sha1))
945  script.AppendExtra("")
946
947  system_tgt = GetImage("system", OPTIONS.input_tmp)
948  system_tgt.ResetFileMap()
949  system_diff = common.BlockDifference("system", system_tgt, src=None)
950  system_diff.WriteStrictVerifyScript(script)
951
952  if HasVendorPartition(input_zip):
953    vendor_tgt = GetImage("vendor", OPTIONS.input_tmp)
954    vendor_tgt.ResetFileMap()
955    vendor_diff = common.BlockDifference("vendor", vendor_tgt, src=None)
956    vendor_diff.WriteStrictVerifyScript(script)
957
958  # Device specific partitions, such as radio, bootloader and etc.
959  device_specific.VerifyOTA_Assertions()
960
961  script.SetProgress(1.0)
962  script.AddToZip(input_zip, output_zip, input_path=OPTIONS.updater_binary)
963  metadata["ota-required-cache"] = str(script.required_cache)
964  WriteMetadata(metadata, output_zip)
965
966
967def WriteABOTAPackageWithBrilloScript(target_file, output_file,
968                                      source_file=None):
969  """Generate an Android OTA package that has A/B update payload."""
970
971  def ComputeStreamingMetadata(zip_file, reserve_space=False,
972                               expected_length=None):
973    """Compute the streaming metadata for a given zip.
974
975    When 'reserve_space' is True, we reserve extra space for the offset and
976    length of the metadata entry itself, although we don't know the final
977    values until the package gets signed. This function will be called again
978    after signing. We then write the actual values and pad the string to the
979    length we set earlier. Note that we can't use the actual length of the
980    metadata entry in the second run. Otherwise the offsets for other entries
981    will be changing again.
982    """
983
984    def ComputeEntryOffsetSize(name):
985      """Compute the zip entry offset and size."""
986      info = zip_file.getinfo(name)
987      offset = info.header_offset + len(info.FileHeader())
988      size = info.file_size
989      return '%s:%d:%d' % (os.path.basename(name), offset, size)
990
991    # payload.bin and payload_properties.txt must exist.
992    offsets = [ComputeEntryOffsetSize('payload.bin'),
993               ComputeEntryOffsetSize('payload_properties.txt')]
994
995    # care_map.txt is available only if dm-verity is enabled.
996    if 'care_map.txt' in zip_file.namelist():
997      offsets.append(ComputeEntryOffsetSize('care_map.txt'))
998
999    if 'compatibility.zip' in zip_file.namelist():
1000      offsets.append(ComputeEntryOffsetSize('compatibility.zip'))
1001
1002    # 'META-INF/com/android/metadata' is required. We don't know its actual
1003    # offset and length (as well as the values for other entries). So we
1004    # reserve 10-byte as a placeholder, which is to cover the space for metadata
1005    # entry ('xx:xxx', since it's ZIP_STORED which should appear at the
1006    # beginning of the zip), as well as the possible value changes in other
1007    # entries.
1008    if reserve_space:
1009      offsets.append('metadata:' + ' ' * 10)
1010    else:
1011      offsets.append(ComputeEntryOffsetSize(METADATA_NAME))
1012
1013    value = ','.join(offsets)
1014    if expected_length is not None:
1015      assert len(value) <= expected_length, \
1016          'Insufficient reserved space: reserved=%d, actual=%d' % (
1017              expected_length, len(value))
1018      value += ' ' * (expected_length - len(value))
1019    return value
1020
1021  # The place where the output from the subprocess should go.
1022  log_file = sys.stdout if OPTIONS.verbose else subprocess.PIPE
1023
1024  # Setup signing keys.
1025  if OPTIONS.package_key is None:
1026    OPTIONS.package_key = OPTIONS.info_dict.get(
1027        "default_system_dev_certificate",
1028        "build/target/product/security/testkey")
1029
1030  # A/B updater expects a signing key in RSA format. Gets the key ready for
1031  # later use in step 3, unless a payload_signer has been specified.
1032  if OPTIONS.payload_signer is None:
1033    cmd = ["openssl", "pkcs8",
1034           "-in", OPTIONS.package_key + OPTIONS.private_key_suffix,
1035           "-inform", "DER", "-nocrypt"]
1036    rsa_key = common.MakeTempFile(prefix="key-", suffix=".key")
1037    cmd.extend(["-out", rsa_key])
1038    p1 = common.Run(cmd, stdout=log_file, stderr=subprocess.STDOUT)
1039    p1.communicate()
1040    assert p1.returncode == 0, "openssl pkcs8 failed"
1041
1042  # Stage the output zip package for package signing.
1043  temp_zip_file = tempfile.NamedTemporaryFile()
1044  output_zip = zipfile.ZipFile(temp_zip_file, "w",
1045                               compression=zipfile.ZIP_DEFLATED)
1046
1047  # Metadata to comply with Android OTA package format.
1048  oem_props = OPTIONS.info_dict.get("oem_fingerprint_properties", None)
1049  oem_dicts = None
1050  if oem_props:
1051    oem_dicts = _LoadOemDicts(None)
1052
1053  metadata = {
1054      "post-build": CalculateFingerprint(oem_props, oem_dicts and oem_dicts[0],
1055                                         OPTIONS.info_dict),
1056      "post-build-incremental" : GetBuildProp("ro.build.version.incremental",
1057                                              OPTIONS.info_dict),
1058      "pre-device": GetOemProperty("ro.product.device", oem_props,
1059                                   oem_dicts and oem_dicts[0],
1060                                   OPTIONS.info_dict),
1061      "ota-required-cache": "0",
1062      "ota-type": "AB",
1063  }
1064
1065  if source_file is not None:
1066    metadata["pre-build"] = CalculateFingerprint(oem_props,
1067                                                 oem_dicts and oem_dicts[0],
1068                                                 OPTIONS.source_info_dict)
1069    metadata["pre-build-incremental"] = GetBuildProp(
1070        "ro.build.version.incremental", OPTIONS.source_info_dict)
1071
1072    HandleDowngradeMetadata(metadata)
1073  else:
1074    metadata["post-timestamp"] = GetBuildProp(
1075        "ro.build.date.utc", OPTIONS.info_dict)
1076
1077  # 1. Generate payload.
1078  payload_file = common.MakeTempFile(prefix="payload-", suffix=".bin")
1079  cmd = ["brillo_update_payload", "generate",
1080         "--payload", payload_file,
1081         "--target_image", target_file]
1082  if source_file is not None:
1083    cmd.extend(["--source_image", source_file])
1084  p1 = common.Run(cmd, stdout=log_file, stderr=subprocess.STDOUT)
1085  p1.communicate()
1086  assert p1.returncode == 0, "brillo_update_payload generate failed"
1087
1088  # 2. Generate hashes of the payload and metadata files.
1089  payload_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
1090  metadata_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
1091  cmd = ["brillo_update_payload", "hash",
1092         "--unsigned_payload", payload_file,
1093         "--signature_size", "256",
1094         "--metadata_hash_file", metadata_sig_file,
1095         "--payload_hash_file", payload_sig_file]
1096  p1 = common.Run(cmd, stdout=log_file, stderr=subprocess.STDOUT)
1097  p1.communicate()
1098  assert p1.returncode == 0, "brillo_update_payload hash failed"
1099
1100  # 3. Sign the hashes and insert them back into the payload file.
1101  signed_payload_sig_file = common.MakeTempFile(prefix="signed-sig-",
1102                                                suffix=".bin")
1103  signed_metadata_sig_file = common.MakeTempFile(prefix="signed-sig-",
1104                                                 suffix=".bin")
1105  # 3a. Sign the payload hash.
1106  if OPTIONS.payload_signer is not None:
1107    cmd = [OPTIONS.payload_signer]
1108    cmd.extend(OPTIONS.payload_signer_args)
1109  else:
1110    cmd = ["openssl", "pkeyutl", "-sign",
1111           "-inkey", rsa_key,
1112           "-pkeyopt", "digest:sha256"]
1113  cmd.extend(["-in", payload_sig_file,
1114              "-out", signed_payload_sig_file])
1115  p1 = common.Run(cmd, stdout=log_file, stderr=subprocess.STDOUT)
1116  p1.communicate()
1117  assert p1.returncode == 0, "openssl sign payload failed"
1118
1119  # 3b. Sign the metadata hash.
1120  if OPTIONS.payload_signer is not None:
1121    cmd = [OPTIONS.payload_signer]
1122    cmd.extend(OPTIONS.payload_signer_args)
1123  else:
1124    cmd = ["openssl", "pkeyutl", "-sign",
1125           "-inkey", rsa_key,
1126           "-pkeyopt", "digest:sha256"]
1127  cmd.extend(["-in", metadata_sig_file,
1128              "-out", signed_metadata_sig_file])
1129  p1 = common.Run(cmd, stdout=log_file, stderr=subprocess.STDOUT)
1130  p1.communicate()
1131  assert p1.returncode == 0, "openssl sign metadata failed"
1132
1133  # 3c. Insert the signatures back into the payload file.
1134  signed_payload_file = common.MakeTempFile(prefix="signed-payload-",
1135                                            suffix=".bin")
1136  cmd = ["brillo_update_payload", "sign",
1137         "--unsigned_payload", payload_file,
1138         "--payload", signed_payload_file,
1139         "--signature_size", "256",
1140         "--metadata_signature_file", signed_metadata_sig_file,
1141         "--payload_signature_file", signed_payload_sig_file]
1142  p1 = common.Run(cmd, stdout=log_file, stderr=subprocess.STDOUT)
1143  p1.communicate()
1144  assert p1.returncode == 0, "brillo_update_payload sign failed"
1145
1146  # 4. Dump the signed payload properties.
1147  properties_file = common.MakeTempFile(prefix="payload-properties-",
1148                                        suffix=".txt")
1149  cmd = ["brillo_update_payload", "properties",
1150         "--payload", signed_payload_file,
1151         "--properties_file", properties_file]
1152  p1 = common.Run(cmd, stdout=log_file, stderr=subprocess.STDOUT)
1153  p1.communicate()
1154  assert p1.returncode == 0, "brillo_update_payload properties failed"
1155
1156  if OPTIONS.wipe_user_data:
1157    with open(properties_file, "a") as f:
1158      f.write("POWERWASH=1\n")
1159    metadata["ota-wipe"] = "yes"
1160
1161  # Add the signed payload file and properties into the zip. In order to
1162  # support streaming, we pack payload.bin, payload_properties.txt and
1163  # care_map.txt as ZIP_STORED. So these entries can be read directly with
1164  # the offset and length pairs.
1165  common.ZipWrite(output_zip, signed_payload_file, arcname="payload.bin",
1166                  compress_type=zipfile.ZIP_STORED)
1167  common.ZipWrite(output_zip, properties_file,
1168                  arcname="payload_properties.txt",
1169                  compress_type=zipfile.ZIP_STORED)
1170
1171  # If dm-verity is supported for the device, copy contents of care_map
1172  # into A/B OTA package.
1173  target_zip = zipfile.ZipFile(target_file, "r")
1174  if OPTIONS.info_dict.get("verity") == "true":
1175    care_map_path = "META/care_map.txt"
1176    namelist = target_zip.namelist()
1177    if care_map_path in namelist:
1178      care_map_data = target_zip.read(care_map_path)
1179      common.ZipWriteStr(output_zip, "care_map.txt", care_map_data,
1180          compress_type=zipfile.ZIP_STORED)
1181    else:
1182      print("Warning: cannot find care map file in target_file package")
1183
1184  if HasVendorPartition(target_zip):
1185    update_vendor = True
1186    update_system = True
1187
1188    # If incremental then figure out what is being updated so metadata only for
1189    # the updated image is included.
1190    if source_file is not None:
1191      input_tmp, input_zip = common.UnzipTemp(
1192          target_file, UNZIP_PATTERN)
1193      source_tmp, source_zip = common.UnzipTemp(
1194          source_file, UNZIP_PATTERN)
1195
1196      vendor_src = GetImage("vendor", source_tmp)
1197      vendor_tgt = GetImage("vendor", input_tmp)
1198      system_src = GetImage("system", source_tmp)
1199      system_tgt = GetImage("system", input_tmp)
1200
1201      update_system = system_src.TotalSha1() != system_tgt.TotalSha1()
1202      update_vendor = vendor_src.TotalSha1() != vendor_tgt.TotalSha1()
1203
1204      input_zip.close()
1205      source_zip.close()
1206
1207    target_zip = zipfile.ZipFile(target_file, "r")
1208    AddCompatibilityArchive(target_zip, output_zip, update_system,
1209                            update_vendor)
1210  common.ZipClose(target_zip)
1211
1212  # Write the current metadata entry with placeholders.
1213  metadata['ota-streaming-property-files'] = ComputeStreamingMetadata(
1214      output_zip, reserve_space=True)
1215  WriteMetadata(metadata, output_zip)
1216  common.ZipClose(output_zip)
1217
1218  # SignOutput(), which in turn calls signapk.jar, will possibly reorder the
1219  # zip entries, as well as padding the entry headers. We do a preliminary
1220  # signing (with an incomplete metadata entry) to allow that to happen. Then
1221  # compute the zip entry offsets, write back the final metadata and do the
1222  # final signing.
1223  prelim_signing = tempfile.NamedTemporaryFile()
1224  SignOutput(temp_zip_file.name, prelim_signing.name)
1225  common.ZipClose(temp_zip_file)
1226
1227  # Open the signed zip. Compute the final metadata that's needed for streaming.
1228  prelim_zip = zipfile.ZipFile(prelim_signing, "r",
1229                               compression=zipfile.ZIP_DEFLATED)
1230  expected_length = len(metadata['ota-streaming-property-files'])
1231  metadata['ota-streaming-property-files'] = ComputeStreamingMetadata(
1232      prelim_zip, reserve_space=False, expected_length=expected_length)
1233
1234  # Copy the zip entries, as we cannot update / delete entries with zipfile.
1235  final_signing = tempfile.NamedTemporaryFile()
1236  output_zip = zipfile.ZipFile(final_signing, "w",
1237                               compression=zipfile.ZIP_DEFLATED)
1238  for item in prelim_zip.infolist():
1239    if item.filename == METADATA_NAME:
1240      continue
1241
1242    data = prelim_zip.read(item.filename)
1243    out_info = copy.copy(item)
1244    common.ZipWriteStr(output_zip, out_info, data)
1245
1246  # Now write the final metadata entry.
1247  WriteMetadata(metadata, output_zip)
1248  common.ZipClose(prelim_zip)
1249  common.ZipClose(output_zip)
1250
1251  # Re-sign the package after updating the metadata entry.
1252  SignOutput(final_signing.name, output_file)
1253  final_signing.close()
1254
1255  # Reopen the final signed zip to double check the streaming metadata.
1256  output_zip = zipfile.ZipFile(output_file, "r")
1257  actual = metadata['ota-streaming-property-files'].strip()
1258  expected = ComputeStreamingMetadata(output_zip)
1259  assert actual == expected, \
1260      "Mismatching streaming metadata: %s vs %s." % (actual, expected)
1261  common.ZipClose(output_zip)
1262
1263
1264def main(argv):
1265
1266  def option_handler(o, a):
1267    if o == "--board_config":
1268      pass   # deprecated
1269    elif o in ("-k", "--package_key"):
1270      OPTIONS.package_key = a
1271    elif o in ("-i", "--incremental_from"):
1272      OPTIONS.incremental_source = a
1273    elif o == "--full_radio":
1274      OPTIONS.full_radio = True
1275    elif o == "--full_bootloader":
1276      OPTIONS.full_bootloader = True
1277    elif o in ("-w", "--wipe_user_data"):
1278      OPTIONS.wipe_user_data = True
1279    elif o == "--downgrade":
1280      OPTIONS.downgrade = True
1281      OPTIONS.wipe_user_data = True
1282    elif o == "--override_timestamp":
1283      OPTIONS.timestamp = True
1284    elif o in ("-o", "--oem_settings"):
1285      OPTIONS.oem_source = a.split(',')
1286    elif o == "--oem_no_mount":
1287      OPTIONS.oem_no_mount = True
1288    elif o in ("-e", "--extra_script"):
1289      OPTIONS.extra_script = a
1290    elif o in ("-t", "--worker_threads"):
1291      if a.isdigit():
1292        OPTIONS.worker_threads = int(a)
1293      else:
1294        raise ValueError("Cannot parse value %r for option %r - only "
1295                         "integers are allowed." % (a, o))
1296    elif o in ("-2", "--two_step"):
1297      OPTIONS.two_step = True
1298    elif o == "--no_signing":
1299      OPTIONS.no_signing = True
1300    elif o == "--verify":
1301      OPTIONS.verify = True
1302    elif o == "--block":
1303      OPTIONS.block_based = True
1304    elif o in ("-b", "--binary"):
1305      OPTIONS.updater_binary = a
1306    elif o in ("--no_fallback_to_full",):
1307      OPTIONS.fallback_to_full = False
1308    elif o == "--stash_threshold":
1309      try:
1310        OPTIONS.stash_threshold = float(a)
1311      except ValueError:
1312        raise ValueError("Cannot parse value %r for option %r - expecting "
1313                         "a float" % (a, o))
1314    elif o == "--gen_verify":
1315      OPTIONS.gen_verify = True
1316    elif o == "--log_diff":
1317      OPTIONS.log_diff = a
1318    elif o == "--payload_signer":
1319      OPTIONS.payload_signer = a
1320    elif o == "--payload_signer_args":
1321      OPTIONS.payload_signer_args = shlex.split(a)
1322    elif o == "--extracted_input_target_files":
1323      OPTIONS.extracted_input = a
1324    else:
1325      return False
1326    return True
1327
1328  args = common.ParseOptions(argv, __doc__,
1329                             extra_opts="b:k:i:d:we:t:2o:",
1330                             extra_long_opts=[
1331                                 "board_config=",
1332                                 "package_key=",
1333                                 "incremental_from=",
1334                                 "full_radio",
1335                                 "full_bootloader",
1336                                 "wipe_user_data",
1337                                 "downgrade",
1338                                 "override_timestamp",
1339                                 "extra_script=",
1340                                 "worker_threads=",
1341                                 "two_step",
1342                                 "no_signing",
1343                                 "block",
1344                                 "binary=",
1345                                 "oem_settings=",
1346                                 "oem_no_mount",
1347                                 "verify",
1348                                 "no_fallback_to_full",
1349                                 "stash_threshold=",
1350                                 "gen_verify",
1351                                 "log_diff=",
1352                                 "payload_signer=",
1353                                 "payload_signer_args=",
1354                                 "extracted_input_target_files=",
1355                             ], extra_option_handler=option_handler)
1356
1357  if len(args) != 2:
1358    common.Usage(__doc__)
1359    sys.exit(1)
1360
1361  if OPTIONS.downgrade:
1362    # Sanity check to enforce a data wipe.
1363    if not OPTIONS.wipe_user_data:
1364      raise ValueError("Cannot downgrade without a data wipe")
1365
1366    # We should only allow downgrading incrementals (as opposed to full).
1367    # Otherwise the device may go back from arbitrary build with this full
1368    # OTA package.
1369    if OPTIONS.incremental_source is None:
1370      raise ValueError("Cannot generate downgradable full OTAs")
1371
1372  assert not (OPTIONS.downgrade and OPTIONS.timestamp), \
1373      "Cannot have --downgrade AND --override_timestamp both"
1374
1375  # Load the dict file from the zip directly to have a peek at the OTA type.
1376  # For packages using A/B update, unzipping is not needed.
1377  if OPTIONS.extracted_input is not None:
1378    OPTIONS.info_dict = common.LoadInfoDict(OPTIONS.extracted_input, OPTIONS.extracted_input)
1379  else:
1380    input_zip = zipfile.ZipFile(args[0], "r")
1381    OPTIONS.info_dict = common.LoadInfoDict(input_zip)
1382    common.ZipClose(input_zip)
1383
1384  ab_update = OPTIONS.info_dict.get("ab_update") == "true"
1385
1386  if ab_update:
1387    if OPTIONS.incremental_source is not None:
1388      OPTIONS.target_info_dict = OPTIONS.info_dict
1389      source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r")
1390      OPTIONS.source_info_dict = common.LoadInfoDict(source_zip)
1391      common.ZipClose(source_zip)
1392
1393    if OPTIONS.verbose:
1394      print("--- target info ---")
1395      common.DumpInfoDict(OPTIONS.info_dict)
1396
1397      if OPTIONS.incremental_source is not None:
1398        print("--- source info ---")
1399        common.DumpInfoDict(OPTIONS.source_info_dict)
1400
1401    WriteABOTAPackageWithBrilloScript(
1402        target_file=args[0],
1403        output_file=args[1],
1404        source_file=OPTIONS.incremental_source)
1405
1406    print("done.")
1407    return
1408
1409  if OPTIONS.extra_script is not None:
1410    OPTIONS.extra_script = open(OPTIONS.extra_script).read()
1411
1412  if OPTIONS.extracted_input is not None:
1413    OPTIONS.input_tmp = OPTIONS.extracted_input
1414    OPTIONS.target_tmp = OPTIONS.input_tmp
1415    OPTIONS.info_dict = common.LoadInfoDict(OPTIONS.input_tmp, OPTIONS.input_tmp)
1416    input_zip = zipfile.ZipFile(args[0], "r")
1417  else:
1418    print("unzipping target target-files...")
1419    OPTIONS.input_tmp, input_zip = common.UnzipTemp(
1420        args[0], UNZIP_PATTERN)
1421
1422    OPTIONS.target_tmp = OPTIONS.input_tmp
1423    OPTIONS.info_dict = common.LoadInfoDict(input_zip, OPTIONS.target_tmp)
1424
1425  if OPTIONS.verbose:
1426    print("--- target info ---")
1427    common.DumpInfoDict(OPTIONS.info_dict)
1428
1429  # If the caller explicitly specified the device-specific extensions
1430  # path via -s/--device_specific, use that.  Otherwise, use
1431  # META/releasetools.py if it is present in the target target_files.
1432  # Otherwise, take the path of the file from 'tool_extensions' in the
1433  # info dict and look for that in the local filesystem, relative to
1434  # the current directory.
1435
1436  if OPTIONS.device_specific is None:
1437    from_input = os.path.join(OPTIONS.input_tmp, "META", "releasetools.py")
1438    if os.path.exists(from_input):
1439      print("(using device-specific extensions from target_files)")
1440      OPTIONS.device_specific = from_input
1441    else:
1442      OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions", None)
1443
1444  if OPTIONS.device_specific is not None:
1445    OPTIONS.device_specific = os.path.abspath(OPTIONS.device_specific)
1446
1447  if OPTIONS.info_dict.get("no_recovery") == "true":
1448    raise common.ExternalError(
1449        "--- target build has specified no recovery ---")
1450
1451  # Use the default key to sign the package if not specified with package_key.
1452  if not OPTIONS.no_signing:
1453    if OPTIONS.package_key is None:
1454      OPTIONS.package_key = OPTIONS.info_dict.get(
1455          "default_system_dev_certificate",
1456          "build/target/product/security/testkey")
1457
1458  # Set up the output zip. Create a temporary zip file if signing is needed.
1459  if OPTIONS.no_signing:
1460    if os.path.exists(args[1]):
1461      os.unlink(args[1])
1462    output_zip = zipfile.ZipFile(args[1], "w",
1463                                 compression=zipfile.ZIP_DEFLATED)
1464  else:
1465    temp_zip_file = tempfile.NamedTemporaryFile()
1466    output_zip = zipfile.ZipFile(temp_zip_file, "w",
1467                                 compression=zipfile.ZIP_DEFLATED)
1468
1469  # Non A/B OTAs rely on /cache partition to store temporary files.
1470  cache_size = OPTIONS.info_dict.get("cache_size", None)
1471  if cache_size is None:
1472    print("--- can't determine the cache partition size ---")
1473  OPTIONS.cache_size = cache_size
1474
1475  # Generate a verify package.
1476  if OPTIONS.gen_verify:
1477    WriteVerifyPackage(input_zip, output_zip)
1478
1479  # Generate a full OTA.
1480  elif OPTIONS.incremental_source is None:
1481    WriteFullOTAPackage(input_zip, output_zip)
1482
1483  # Generate an incremental OTA. It will fall back to generate a full OTA on
1484  # failure unless no_fallback_to_full is specified.
1485  else:
1486    print("unzipping source target-files...")
1487    OPTIONS.source_tmp, source_zip = common.UnzipTemp(
1488        OPTIONS.incremental_source,
1489        UNZIP_PATTERN)
1490    OPTIONS.target_info_dict = OPTIONS.info_dict
1491    OPTIONS.source_info_dict = common.LoadInfoDict(source_zip,
1492                                                   OPTIONS.source_tmp)
1493    if OPTIONS.verbose:
1494      print("--- source info ---")
1495      common.DumpInfoDict(OPTIONS.source_info_dict)
1496    try:
1497      WriteBlockIncrementalOTAPackage(input_zip, source_zip, output_zip)
1498      if OPTIONS.log_diff:
1499        out_file = open(OPTIONS.log_diff, 'w')
1500        import target_files_diff
1501        target_files_diff.recursiveDiff('',
1502                                        OPTIONS.source_tmp,
1503                                        OPTIONS.input_tmp,
1504                                        out_file)
1505        out_file.close()
1506    except ValueError:
1507      if not OPTIONS.fallback_to_full:
1508        raise
1509      print("--- failed to build incremental; falling back to full ---")
1510      OPTIONS.incremental_source = None
1511      WriteFullOTAPackage(input_zip, output_zip)
1512
1513  common.ZipClose(output_zip)
1514
1515  # Sign the generated zip package unless no_signing is specified.
1516  if not OPTIONS.no_signing:
1517    SignOutput(temp_zip_file.name, args[1])
1518    temp_zip_file.close()
1519
1520  print("done.")
1521
1522
1523if __name__ == '__main__':
1524  try:
1525    common.CloseInheritedPipes()
1526    main(sys.argv[1:])
1527  except common.ExternalError as e:
1528    print("\n   ERROR: %s\n" % (e,))
1529    sys.exit(1)
1530  finally:
1531    common.Cleanup()
1532