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