1# Copyright (C) 2020 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#      http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import collections
16import logging
17import os
18import zipfile
19
20import common
21import edify_generator
22import verity_utils
23from check_target_files_vintf import CheckVintfIfTrebleEnabled, HasPartition
24from common import OPTIONS
25from ota_utils import UNZIP_PATTERN, FinalizeMetadata, GetPackageMetadata, PropertyFiles
26
27logger = logging.getLogger(__name__)
28
29
30def GetBlockDifferences(target_zip, source_zip, target_info, source_info,
31                        device_specific):
32  """Returns a ordered dict of block differences with partition name as key."""
33
34  def GetIncrementalBlockDifferenceForPartition(name):
35    if not HasPartition(source_zip, name):
36      raise RuntimeError(
37          "can't generate incremental that adds {}".format(name))
38
39    partition_src = common.GetUserImage(name, OPTIONS.source_tmp, source_zip,
40                                        info_dict=source_info,
41                                        allow_shared_blocks=allow_shared_blocks)
42
43    hashtree_info_generator = verity_utils.CreateHashtreeInfoGenerator(
44        name, 4096, target_info)
45    partition_tgt = common.GetUserImage(name, OPTIONS.target_tmp, target_zip,
46                                        info_dict=target_info,
47                                        allow_shared_blocks=allow_shared_blocks,
48                                        hashtree_info_generator=hashtree_info_generator)
49
50    # Check the first block of the source system partition for remount R/W only
51    # if the filesystem is ext4.
52    partition_source_info = source_info["fstab"]["/" + name]
53    check_first_block = partition_source_info.fs_type == "ext4"
54    # Disable using imgdiff for squashfs. 'imgdiff -z' expects input files to be
55    # in zip formats. However with squashfs, a) all files are compressed in LZ4;
56    # b) the blocks listed in block map may not contain all the bytes for a
57    # given file (because they're rounded to be 4K-aligned).
58    partition_target_info = target_info["fstab"]["/" + name]
59    disable_imgdiff = (partition_source_info.fs_type == "squashfs" or
60                       partition_target_info.fs_type == "squashfs")
61    return common.BlockDifference(name, partition_tgt, partition_src,
62                                  check_first_block,
63                                  version=blockimgdiff_version,
64                                  disable_imgdiff=disable_imgdiff)
65
66  if source_zip:
67    # See notes in common.GetUserImage()
68    allow_shared_blocks = (source_info.get('ext4_share_dup_blocks') == "true" or
69                           target_info.get('ext4_share_dup_blocks') == "true")
70    blockimgdiff_version = max(
71        int(i) for i in target_info.get(
72            "blockimgdiff_versions", "1").split(","))
73    assert blockimgdiff_version >= 3
74
75  block_diff_dict = collections.OrderedDict()
76  partition_names = ["system", "vendor", "product", "odm", "system_ext",
77                     "vendor_dlkm", "odm_dlkm"]
78  for partition in partition_names:
79    if not HasPartition(target_zip, partition):
80      continue
81    # Full OTA update.
82    if not source_zip:
83      tgt = common.GetUserImage(partition, OPTIONS.input_tmp, target_zip,
84                                info_dict=target_info,
85                                reset_file_map=True)
86      block_diff_dict[partition] = common.BlockDifference(partition, tgt,
87                                                          src=None)
88    # Incremental OTA update.
89    else:
90      block_diff_dict[partition] = GetIncrementalBlockDifferenceForPartition(
91          partition)
92  assert "system" in block_diff_dict
93
94  # Get the block diffs from the device specific script. If there is a
95  # duplicate block diff for a partition, ignore the diff in the generic script
96  # and use the one in the device specific script instead.
97  if source_zip:
98    device_specific_diffs = device_specific.IncrementalOTA_GetBlockDifferences()
99    function_name = "IncrementalOTA_GetBlockDifferences"
100  else:
101    device_specific_diffs = device_specific.FullOTA_GetBlockDifferences()
102    function_name = "FullOTA_GetBlockDifferences"
103
104  if device_specific_diffs:
105    assert all(isinstance(diff, common.BlockDifference)
106               for diff in device_specific_diffs), \
107        "{} is not returning a list of BlockDifference objects".format(
108            function_name)
109    for diff in device_specific_diffs:
110      if diff.partition in block_diff_dict:
111        logger.warning("Duplicate block difference found. Device specific block"
112                       " diff for partition '%s' overrides the one in generic"
113                       " script.", diff.partition)
114      block_diff_dict[diff.partition] = diff
115
116  return block_diff_dict
117
118
119def WriteFullOTAPackage(input_zip, output_file):
120  target_info = common.BuildInfo(OPTIONS.info_dict, OPTIONS.oem_dicts)
121
122  # We don't know what version it will be installed on top of. We expect the API
123  # just won't change very often. Similarly for fstab, it might have changed in
124  # the target build.
125  target_api_version = target_info["recovery_api_version"]
126  script = edify_generator.EdifyGenerator(target_api_version, target_info)
127
128  if target_info.oem_props and not OPTIONS.oem_no_mount:
129    target_info.WriteMountOemScript(script)
130
131  metadata = GetPackageMetadata(target_info)
132
133  if not OPTIONS.no_signing:
134    staging_file = common.MakeTempFile(suffix='.zip')
135  else:
136    staging_file = output_file
137
138  output_zip = zipfile.ZipFile(
139      staging_file, "w", compression=zipfile.ZIP_DEFLATED)
140
141  device_specific = common.DeviceSpecificParams(
142      input_zip=input_zip,
143      input_version=target_api_version,
144      output_zip=output_zip,
145      script=script,
146      input_tmp=OPTIONS.input_tmp,
147      metadata=metadata,
148      info_dict=OPTIONS.info_dict)
149
150  assert HasRecoveryPatch(input_zip, info_dict=OPTIONS.info_dict)
151
152  # Assertions (e.g. downgrade check, device properties check).
153  ts = target_info.GetBuildProp("ro.build.date.utc")
154  ts_text = target_info.GetBuildProp("ro.build.date")
155  script.AssertOlderBuild(ts, ts_text)
156
157  target_info.WriteDeviceAssertions(script, OPTIONS.oem_no_mount)
158  device_specific.FullOTA_Assertions()
159
160  block_diff_dict = GetBlockDifferences(target_zip=input_zip, source_zip=None,
161                                        target_info=target_info,
162                                        source_info=None,
163                                        device_specific=device_specific)
164
165  # Two-step package strategy (in chronological order, which is *not*
166  # the order in which the generated script has things):
167  #
168  # if stage is not "2/3" or "3/3":
169  #    write recovery image to boot partition
170  #    set stage to "2/3"
171  #    reboot to boot partition and restart recovery
172  # else if stage is "2/3":
173  #    write recovery image to recovery partition
174  #    set stage to "3/3"
175  #    reboot to recovery partition and restart recovery
176  # else:
177  #    (stage must be "3/3")
178  #    set stage to ""
179  #    do normal full package installation:
180  #       wipe and install system, boot image, etc.
181  #       set up system to update recovery partition on first boot
182  #    complete script normally
183  #    (allow recovery to mark itself finished and reboot)
184
185  recovery_img = common.GetBootableImage("recovery.img", "recovery.img",
186                                         OPTIONS.input_tmp, "RECOVERY")
187  if OPTIONS.two_step:
188    if not target_info.get("multistage_support"):
189      assert False, "two-step packages not supported by this build"
190    fs = target_info["fstab"]["/misc"]
191    assert fs.fs_type.upper() == "EMMC", \
192        "two-step packages only supported on devices with EMMC /misc partitions"
193    bcb_dev = {"bcb_dev": fs.device}
194    common.ZipWriteStr(output_zip, "recovery.img", recovery_img.data)
195    script.AppendExtra("""
196if get_stage("%(bcb_dev)s") == "2/3" then
197""" % bcb_dev)
198
199    # Stage 2/3: Write recovery image to /recovery (currently running /boot).
200    script.Comment("Stage 2/3")
201    script.WriteRawImage("/recovery", "recovery.img")
202    script.AppendExtra("""
203set_stage("%(bcb_dev)s", "3/3");
204reboot_now("%(bcb_dev)s", "recovery");
205else if get_stage("%(bcb_dev)s") == "3/3" then
206""" % bcb_dev)
207
208    # Stage 3/3: Make changes.
209    script.Comment("Stage 3/3")
210
211  # Dump fingerprints
212  script.Print("Target: {}".format(target_info.fingerprint))
213
214  device_specific.FullOTA_InstallBegin()
215
216  # All other partitions as well as the data wipe use 10% of the progress, and
217  # the update of the system partition takes the remaining progress.
218  system_progress = 0.9 - (len(block_diff_dict) - 1) * 0.1
219  if OPTIONS.wipe_user_data:
220    system_progress -= 0.1
221  progress_dict = {partition: 0.1 for partition in block_diff_dict}
222  progress_dict["system"] = system_progress
223
224  if target_info.get('use_dynamic_partitions') == "true":
225    # Use empty source_info_dict to indicate that all partitions / groups must
226    # be re-added.
227    dynamic_partitions_diff = common.DynamicPartitionsDifference(
228        info_dict=OPTIONS.info_dict,
229        block_diffs=block_diff_dict.values(),
230        progress_dict=progress_dict)
231    dynamic_partitions_diff.WriteScript(script, output_zip,
232                                        write_verify_script=OPTIONS.verify)
233  else:
234    for block_diff in block_diff_dict.values():
235      block_diff.WriteScript(script, output_zip,
236                             progress=progress_dict.get(block_diff.partition),
237                             write_verify_script=OPTIONS.verify)
238
239  CheckVintfIfTrebleEnabled(OPTIONS.input_tmp, target_info)
240
241  boot_img = common.GetBootableImage(
242      "boot.img", "boot.img", OPTIONS.input_tmp, "BOOT")
243  common.CheckSize(boot_img.data, "boot.img", target_info)
244  common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
245
246  script.WriteRawImage("/boot", "boot.img")
247
248  script.ShowProgress(0.1, 10)
249  device_specific.FullOTA_InstallEnd()
250
251  if OPTIONS.extra_script is not None:
252    script.AppendExtra(OPTIONS.extra_script)
253
254  script.UnmountAll()
255
256  if OPTIONS.wipe_user_data:
257    script.ShowProgress(0.1, 10)
258    script.FormatPartition("/data")
259
260  if OPTIONS.two_step:
261    script.AppendExtra("""
262set_stage("%(bcb_dev)s", "");
263""" % bcb_dev)
264    script.AppendExtra("else\n")
265
266    # Stage 1/3: Nothing to verify for full OTA. Write recovery image to /boot.
267    script.Comment("Stage 1/3")
268    _WriteRecoveryImageToBoot(script, output_zip)
269
270    script.AppendExtra("""
271set_stage("%(bcb_dev)s", "2/3");
272reboot_now("%(bcb_dev)s", "");
273endif;
274endif;
275""" % bcb_dev)
276
277  script.SetProgress(1)
278  script.AddToZip(input_zip, output_zip, input_path=OPTIONS.updater_binary)
279  metadata.required_cache = script.required_cache
280
281  # We haven't written the metadata entry, which will be done in
282  # FinalizeMetadata.
283  common.ZipClose(output_zip)
284
285  needed_property_files = (
286      NonAbOtaPropertyFiles(),
287  )
288  FinalizeMetadata(metadata, staging_file, output_file, needed_property_files)
289
290
291def WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_file):
292  target_info = common.BuildInfo(OPTIONS.target_info_dict, OPTIONS.oem_dicts)
293  source_info = common.BuildInfo(OPTIONS.source_info_dict, OPTIONS.oem_dicts)
294
295  target_api_version = target_info["recovery_api_version"]
296  source_api_version = source_info["recovery_api_version"]
297  if source_api_version == 0:
298    logger.warning(
299        "Generating edify script for a source that can't install it.")
300
301  script = edify_generator.EdifyGenerator(
302      source_api_version, target_info, fstab=source_info["fstab"])
303
304  if target_info.oem_props or source_info.oem_props:
305    if not OPTIONS.oem_no_mount:
306      source_info.WriteMountOemScript(script)
307
308  metadata = GetPackageMetadata(target_info, source_info)
309
310  if not OPTIONS.no_signing:
311    staging_file = common.MakeTempFile(suffix='.zip')
312  else:
313    staging_file = output_file
314
315  output_zip = zipfile.ZipFile(
316      staging_file, "w", compression=zipfile.ZIP_DEFLATED)
317
318  device_specific = common.DeviceSpecificParams(
319      source_zip=source_zip,
320      source_version=source_api_version,
321      source_tmp=OPTIONS.source_tmp,
322      target_zip=target_zip,
323      target_version=target_api_version,
324      target_tmp=OPTIONS.target_tmp,
325      output_zip=output_zip,
326      script=script,
327      metadata=metadata,
328      info_dict=source_info)
329
330  source_boot = common.GetBootableImage(
331      "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT", source_info)
332  target_boot = common.GetBootableImage(
333      "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT", target_info)
334  updating_boot = (not OPTIONS.two_step and
335                   (source_boot.data != target_boot.data))
336
337  target_recovery = common.GetBootableImage(
338      "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY")
339
340  block_diff_dict = GetBlockDifferences(target_zip=target_zip,
341                                        source_zip=source_zip,
342                                        target_info=target_info,
343                                        source_info=source_info,
344                                        device_specific=device_specific)
345
346  CheckVintfIfTrebleEnabled(OPTIONS.target_tmp, target_info)
347
348  # Assertions (e.g. device properties check).
349  target_info.WriteDeviceAssertions(script, OPTIONS.oem_no_mount)
350  device_specific.IncrementalOTA_Assertions()
351
352  # Two-step incremental package strategy (in chronological order,
353  # which is *not* the order in which the generated script has
354  # things):
355  #
356  # if stage is not "2/3" or "3/3":
357  #    do verification on current system
358  #    write recovery image to boot partition
359  #    set stage to "2/3"
360  #    reboot to boot partition and restart recovery
361  # else if stage is "2/3":
362  #    write recovery image to recovery partition
363  #    set stage to "3/3"
364  #    reboot to recovery partition and restart recovery
365  # else:
366  #    (stage must be "3/3")
367  #    perform update:
368  #       patch system files, etc.
369  #       force full install of new boot image
370  #       set up system to update recovery partition on first boot
371  #    complete script normally
372  #    (allow recovery to mark itself finished and reboot)
373
374  if OPTIONS.two_step:
375    if not source_info.get("multistage_support"):
376      assert False, "two-step packages not supported by this build"
377    fs = source_info["fstab"]["/misc"]
378    assert fs.fs_type.upper() == "EMMC", \
379        "two-step packages only supported on devices with EMMC /misc partitions"
380    bcb_dev = {"bcb_dev": fs.device}
381    common.ZipWriteStr(output_zip, "recovery.img", target_recovery.data)
382    script.AppendExtra("""
383if get_stage("%(bcb_dev)s") == "2/3" then
384""" % bcb_dev)
385
386    # Stage 2/3: Write recovery image to /recovery (currently running /boot).
387    script.Comment("Stage 2/3")
388    script.AppendExtra("sleep(20);\n")
389    script.WriteRawImage("/recovery", "recovery.img")
390    script.AppendExtra("""
391set_stage("%(bcb_dev)s", "3/3");
392reboot_now("%(bcb_dev)s", "recovery");
393else if get_stage("%(bcb_dev)s") != "3/3" then
394""" % bcb_dev)
395
396    # Stage 1/3: (a) Verify the current system.
397    script.Comment("Stage 1/3")
398
399  # Dump fingerprints
400  script.Print("Source: {}".format(source_info.fingerprint))
401  script.Print("Target: {}".format(target_info.fingerprint))
402
403  script.Print("Verifying current system...")
404
405  device_specific.IncrementalOTA_VerifyBegin()
406
407  WriteFingerprintAssertion(script, target_info, source_info)
408
409  # Check the required cache size (i.e. stashed blocks).
410  required_cache_sizes = [diff.required_cache for diff in
411                          block_diff_dict.values()]
412  if updating_boot:
413    boot_type, boot_device_expr = common.GetTypeAndDeviceExpr("/boot",
414                                                              source_info)
415    d = common.Difference(target_boot, source_boot)
416    _, _, d = d.ComputePatch()
417    if d is None:
418      include_full_boot = True
419      common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
420    else:
421      include_full_boot = False
422
423      logger.info(
424          "boot      target: %d  source: %d  diff: %d", target_boot.size,
425          source_boot.size, len(d))
426
427      common.ZipWriteStr(output_zip, "boot.img.p", d)
428
429      target_expr = 'concat("{}:",{},":{}:{}")'.format(
430          boot_type, boot_device_expr, target_boot.size, target_boot.sha1)
431      source_expr = 'concat("{}:",{},":{}:{}")'.format(
432          boot_type, boot_device_expr, source_boot.size, source_boot.sha1)
433      script.PatchPartitionExprCheck(target_expr, source_expr)
434
435      required_cache_sizes.append(target_boot.size)
436
437  if required_cache_sizes:
438    script.CacheFreeSpaceCheck(max(required_cache_sizes))
439
440  # Verify the existing partitions.
441  for diff in block_diff_dict.values():
442    diff.WriteVerifyScript(script, touched_blocks_only=True)
443
444  device_specific.IncrementalOTA_VerifyEnd()
445
446  if OPTIONS.two_step:
447    # Stage 1/3: (b) Write recovery image to /boot.
448    _WriteRecoveryImageToBoot(script, output_zip)
449
450    script.AppendExtra("""
451set_stage("%(bcb_dev)s", "2/3");
452reboot_now("%(bcb_dev)s", "");
453else
454""" % bcb_dev)
455
456    # Stage 3/3: Make changes.
457    script.Comment("Stage 3/3")
458
459  script.Comment("---- start making changes here ----")
460
461  device_specific.IncrementalOTA_InstallBegin()
462
463  progress_dict = {partition: 0.1 for partition in block_diff_dict}
464  progress_dict["system"] = 1 - len(block_diff_dict) * 0.1
465
466  if OPTIONS.source_info_dict.get("use_dynamic_partitions") == "true":
467    if OPTIONS.target_info_dict.get("use_dynamic_partitions") != "true":
468      raise RuntimeError(
469          "can't generate incremental that disables dynamic partitions")
470    dynamic_partitions_diff = common.DynamicPartitionsDifference(
471        info_dict=OPTIONS.target_info_dict,
472        source_info_dict=OPTIONS.source_info_dict,
473        block_diffs=block_diff_dict.values(),
474        progress_dict=progress_dict)
475    dynamic_partitions_diff.WriteScript(
476        script, output_zip, write_verify_script=OPTIONS.verify)
477  else:
478    for block_diff in block_diff_dict.values():
479      block_diff.WriteScript(script, output_zip,
480                             progress=progress_dict.get(block_diff.partition),
481                             write_verify_script=OPTIONS.verify)
482
483  if OPTIONS.two_step:
484    common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
485    script.WriteRawImage("/boot", "boot.img")
486    logger.info("writing full boot image (forced by two-step mode)")
487
488  if not OPTIONS.two_step:
489    if updating_boot:
490      if include_full_boot:
491        logger.info("boot image changed; including full.")
492        script.Print("Installing boot image...")
493        script.WriteRawImage("/boot", "boot.img")
494      else:
495        # Produce the boot image by applying a patch to the current
496        # contents of the boot partition, and write it back to the
497        # partition.
498        logger.info("boot image changed; including patch.")
499        script.Print("Patching boot image...")
500        script.ShowProgress(0.1, 10)
501        target_expr = 'concat("{}:",{},":{}:{}")'.format(
502            boot_type, boot_device_expr, target_boot.size, target_boot.sha1)
503        source_expr = 'concat("{}:",{},":{}:{}")'.format(
504            boot_type, boot_device_expr, source_boot.size, source_boot.sha1)
505        script.PatchPartitionExpr(target_expr, source_expr, '"boot.img.p"')
506    else:
507      logger.info("boot image unchanged; skipping.")
508
509  # Do device-specific installation (eg, write radio image).
510  device_specific.IncrementalOTA_InstallEnd()
511
512  if OPTIONS.extra_script is not None:
513    script.AppendExtra(OPTIONS.extra_script)
514
515  if OPTIONS.wipe_user_data:
516    script.Print("Erasing user data...")
517    script.FormatPartition("/data")
518
519  if OPTIONS.two_step:
520    script.AppendExtra("""
521set_stage("%(bcb_dev)s", "");
522endif;
523endif;
524""" % bcb_dev)
525
526  script.SetProgress(1)
527  # For downgrade OTAs, we prefer to use the update-binary in the source
528  # build that is actually newer than the one in the target build.
529  if OPTIONS.downgrade:
530    script.AddToZip(source_zip, output_zip, input_path=OPTIONS.updater_binary)
531  else:
532    script.AddToZip(target_zip, output_zip, input_path=OPTIONS.updater_binary)
533  metadata.required_cache = script.required_cache
534
535  # We haven't written the metadata entry yet, which will be handled in
536  # FinalizeMetadata().
537  common.ZipClose(output_zip)
538
539  # Sign the generated zip package unless no_signing is specified.
540  needed_property_files = (
541      NonAbOtaPropertyFiles(),
542  )
543  FinalizeMetadata(metadata, staging_file, output_file, needed_property_files)
544
545
546def GenerateNonAbOtaPackage(target_file, output_file, source_file=None):
547  """Generates a non-A/B OTA package."""
548  # Check the loaded info dicts first.
549  if OPTIONS.info_dict.get("no_recovery") == "true":
550    raise common.ExternalError(
551        "--- target build has specified no recovery ---")
552
553  # Non-A/B OTAs rely on /cache partition to store temporary files.
554  cache_size = OPTIONS.info_dict.get("cache_size")
555  if cache_size is None:
556    logger.warning("--- can't determine the cache partition size ---")
557  OPTIONS.cache_size = cache_size
558
559  if OPTIONS.extra_script is not None:
560    with open(OPTIONS.extra_script) as fp:
561      OPTIONS.extra_script = fp.read()
562
563  if OPTIONS.extracted_input is not None:
564    OPTIONS.input_tmp = OPTIONS.extracted_input
565  else:
566    logger.info("unzipping target target-files...")
567    OPTIONS.input_tmp = common.UnzipTemp(target_file, UNZIP_PATTERN)
568  OPTIONS.target_tmp = OPTIONS.input_tmp
569
570  # If the caller explicitly specified the device-specific extensions path via
571  # -s / --device_specific, use that. Otherwise, use META/releasetools.py if it
572  # is present in the target target_files. Otherwise, take the path of the file
573  # from 'tool_extensions' in the info dict and look for that in the local
574  # filesystem, relative to the current directory.
575  if OPTIONS.device_specific is None:
576    from_input = os.path.join(OPTIONS.input_tmp, "META", "releasetools.py")
577    if os.path.exists(from_input):
578      logger.info("(using device-specific extensions from target_files)")
579      OPTIONS.device_specific = from_input
580    else:
581      OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions")
582
583  if OPTIONS.device_specific is not None:
584    OPTIONS.device_specific = os.path.abspath(OPTIONS.device_specific)
585
586  # Generate a full OTA.
587  if source_file is None:
588    with zipfile.ZipFile(target_file) as input_zip:
589      WriteFullOTAPackage(
590          input_zip,
591          output_file)
592
593  # Generate an incremental OTA.
594  else:
595    logger.info("unzipping source target-files...")
596    OPTIONS.source_tmp = common.UnzipTemp(
597        OPTIONS.incremental_source, UNZIP_PATTERN)
598    with zipfile.ZipFile(target_file) as input_zip, \
599            zipfile.ZipFile(source_file) as source_zip:
600      WriteBlockIncrementalOTAPackage(
601          input_zip,
602          source_zip,
603          output_file)
604
605
606def WriteFingerprintAssertion(script, target_info, source_info):
607  source_oem_props = source_info.oem_props
608  target_oem_props = target_info.oem_props
609
610  if source_oem_props is None and target_oem_props is None:
611    script.AssertSomeFingerprint(
612        source_info.fingerprint, target_info.fingerprint)
613  elif source_oem_props is not None and target_oem_props is not None:
614    script.AssertSomeThumbprint(
615        target_info.GetBuildProp("ro.build.thumbprint"),
616        source_info.GetBuildProp("ro.build.thumbprint"))
617  elif source_oem_props is None and target_oem_props is not None:
618    script.AssertFingerprintOrThumbprint(
619        source_info.fingerprint,
620        target_info.GetBuildProp("ro.build.thumbprint"))
621  else:
622    script.AssertFingerprintOrThumbprint(
623        target_info.fingerprint,
624        source_info.GetBuildProp("ro.build.thumbprint"))
625
626
627class NonAbOtaPropertyFiles(PropertyFiles):
628  """The property-files for non-A/B OTA.
629
630  For non-A/B OTA, the property-files string contains the info for METADATA
631  entry, with which a system updater can be fetched the package metadata prior
632  to downloading the entire package.
633  """
634
635  def __init__(self):
636    super(NonAbOtaPropertyFiles, self).__init__()
637    self.name = 'ota-property-files'
638
639
640def _WriteRecoveryImageToBoot(script, output_zip):
641  """Find and write recovery image to /boot in two-step OTA.
642
643  In two-step OTAs, we write recovery image to /boot as the first step so that
644  we can reboot to there and install a new recovery image to /recovery.
645  A special "recovery-two-step.img" will be preferred, which encodes the correct
646  path of "/boot". Otherwise the device may show "device is corrupt" message
647  when booting into /boot.
648
649  Fall back to using the regular recovery.img if the two-step recovery image
650  doesn't exist. Note that rebuilding the special image at this point may be
651  infeasible, because we don't have the desired boot signer and keys when
652  calling ota_from_target_files.py.
653  """
654
655  recovery_two_step_img_name = "recovery-two-step.img"
656  recovery_two_step_img_path = os.path.join(
657      OPTIONS.input_tmp, "OTA", recovery_two_step_img_name)
658  if os.path.exists(recovery_two_step_img_path):
659    common.ZipWrite(
660        output_zip,
661        recovery_two_step_img_path,
662        arcname=recovery_two_step_img_name)
663    logger.info(
664        "two-step package: using %s in stage 1/3", recovery_two_step_img_name)
665    script.WriteRawImage("/boot", recovery_two_step_img_name)
666  else:
667    logger.info("two-step package: using recovery.img in stage 1/3")
668    # The "recovery.img" entry has been written into package earlier.
669    script.WriteRawImage("/boot", "recovery.img")
670
671
672def HasRecoveryPatch(target_files_zip, info_dict):
673  board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
674
675  if board_uses_vendorimage:
676    target_files_dir = "VENDOR"
677  else:
678    target_files_dir = "SYSTEM/vendor"
679
680  patch = "%s/recovery-from-boot.p" % target_files_dir
681  img = "%s/etc/recovery.img" % target_files_dir
682
683  namelist = target_files_zip.namelist()
684  return patch in namelist or img in namelist
685